001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.portal.kernel.lar.PortletDataContext;
018 import com.liferay.portal.kernel.log.Log;
019 import com.liferay.portal.kernel.log.LogFactoryUtil;
020 import com.liferay.portal.kernel.messaging.DestinationNames;
021 import com.liferay.portal.kernel.process.ClassPathUtil;
022 import com.liferay.portal.kernel.process.ProcessCallable;
023 import com.liferay.portal.kernel.process.ProcessException;
024 import com.liferay.portal.kernel.process.ProcessExecutor;
025 import com.liferay.portal.kernel.repository.model.FileEntry;
026 import com.liferay.portal.kernel.repository.model.FileVersion;
027 import com.liferay.portal.kernel.util.FileUtil;
028 import com.liferay.portal.kernel.util.PropsKeys;
029 import com.liferay.portal.kernel.util.ServerDetector;
030 import com.liferay.portal.kernel.util.SetUtil;
031 import com.liferay.portal.kernel.util.StreamUtil;
032 import com.liferay.portal.kernel.util.StringBundler;
033 import com.liferay.portal.kernel.util.StringPool;
034 import com.liferay.portal.kernel.util.SystemEnv;
035 import com.liferay.portal.kernel.util.Validator;
036 import com.liferay.portal.kernel.xml.Element;
037 import com.liferay.portal.kernel.xuggler.XugglerUtil;
038 import com.liferay.portal.log.Log4jLogFactoryImpl;
039 import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
040 import com.liferay.portal.util.PropsUtil;
041 import com.liferay.portal.util.PropsValues;
042 import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
043 import com.liferay.portlet.documentlibrary.store.DLStoreUtil;
044 import com.liferay.util.log4j.Log4JUtil;
045
046 import java.io.File;
047 import java.io.InputStream;
048
049 import java.util.List;
050 import java.util.Map;
051 import java.util.Properties;
052 import java.util.Set;
053 import java.util.Vector;
054 import java.util.concurrent.CancellationException;
055 import java.util.concurrent.Future;
056
057 import org.apache.commons.lang.time.StopWatch;
058
059
065 public class AudioProcessorImpl
066 extends DLPreviewableProcessor implements AudioProcessor {
067
068 public void afterPropertiesSet() {
069 boolean valid = true;
070
071 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
072 valid = false;
073 }
074 else {
075 for (String previewType : _PREVIEW_TYPES) {
076 if (!previewType.equals("mp3") && !previewType.equals("ogg")) {
077 valid = false;
078
079 break;
080 }
081 }
082 }
083
084 if (!valid && _log.isWarnEnabled()) {
085 StringBundler sb = new StringBundler(5);
086
087 sb.append("Liferay is incorrectly configured to generate video ");
088 sb.append("previews using video containers other than MP3 or ");
089 sb.append("OGG. Please change the property ");
090 sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS);
091 sb.append(" in portal-ext.properties.");
092
093 _log.warn(sb.toString());
094 }
095
096 FileUtil.mkdirs(PREVIEW_TMP_PATH);
097 }
098
099 public void generateAudio(
100 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
101 throws Exception {
102
103 _generateAudio(sourceFileVersion, destinationFileVersion);
104 }
105
106 public Set<String> getAudioMimeTypes() {
107 return _audioMimeTypes;
108 }
109
110 public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
111 throws Exception {
112
113 return doGetPreviewAsStream(fileVersion, type);
114 }
115
116 public long getPreviewFileSize(FileVersion fileVersion, String type)
117 throws Exception {
118
119 return doGetPreviewFileSize(fileVersion, type);
120 }
121
122 public boolean hasAudio(FileVersion fileVersion) {
123 boolean hasAudio = false;
124
125 try {
126 hasAudio = _hasAudio(fileVersion);
127
128 if (!hasAudio && isSupported(fileVersion)) {
129 _queueGeneration(null, fileVersion);
130 }
131 }
132 catch (Exception e) {
133 _log.error(e, e);
134 }
135
136 return hasAudio;
137 }
138
139 public boolean isAudioSupported(FileVersion fileVersion) {
140 return isSupported(fileVersion);
141 }
142
143 public boolean isAudioSupported(String mimeType) {
144 return isSupported(mimeType);
145 }
146
147 public boolean isSupported(String mimeType) {
148 if (Validator.isNull(mimeType)) {
149 return false;
150 }
151
152 try {
153 if (XugglerUtil.isEnabled()) {
154 return _audioMimeTypes.contains(mimeType);
155 }
156 }
157 catch (Exception e) {
158 }
159
160 return false;
161 }
162
163 @Override
164 public void trigger(
165 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
166
167 super.trigger(sourceFileVersion, destinationFileVersion);
168
169 _queueGeneration(sourceFileVersion, destinationFileVersion);
170 }
171
172 @Override
173 protected void deletePreviews(
174 long companyId, long groupId, long fileEntryId, long fileVersionId) {
175
176 String pathSegment = getPathSegment(
177 groupId, fileEntryId, fileVersionId, true);
178
179 for (String previewType : _PREVIEW_TYPES) {
180 String path = pathSegment + StringPool.PERIOD + previewType;
181
182 try {
183 DLStoreUtil.deleteDirectory(companyId, REPOSITORY_ID, path);
184 }
185 catch (Exception e) {
186 }
187 }
188 }
189
190 @Override
191 protected void doExportGeneratedFiles(
192 PortletDataContext portletDataContext, FileEntry fileEntry,
193 Element fileEntryElement)
194 throws Exception {
195
196 exportPreviews(portletDataContext, fileEntry, fileEntryElement);
197 }
198
199 @Override
200 protected void doImportGeneratedFiles(
201 PortletDataContext portletDataContext, FileEntry fileEntry,
202 FileEntry importedFileEntry, Element fileEntryElement)
203 throws Exception {
204
205 importPreviews(
206 portletDataContext, fileEntry, importedFileEntry, fileEntryElement);
207 }
208
209 protected void exportPreviews(
210 PortletDataContext portletDataContext, FileEntry fileEntry,
211 Element fileEntryElement)
212 throws Exception {
213
214 FileVersion fileVersion = fileEntry.getFileVersion();
215
216 if (!isSupported(fileVersion) || !hasPreviews(fileVersion)) {
217 return;
218 }
219
220 if (!portletDataContext.isPerformDirectBinaryImport()) {
221 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
222 return;
223 }
224
225 for (String previewType : _PREVIEW_TYPES) {
226 if (previewType.equals("mp3") || previewType.equals("ogg")) {
227 exportPreview(
228 portletDataContext, fileEntry, fileEntryElement,
229 "audio", previewType);
230 }
231 }
232 }
233 }
234
235 @Override
236 protected List<Long> getFileVersionIds() {
237 return _fileVersionIds;
238 }
239
240 @Override
241 protected String getPreviewType(FileVersion fileVersion) {
242 return _PREVIEW_TYPES[0];
243 }
244
245 @Override
246 protected String[] getPreviewTypes() {
247 return _PREVIEW_TYPES;
248 }
249
250 @Override
251 protected String getThumbnailType(FileVersion fileVersion) {
252 return null;
253 }
254
255 protected void importPreviews(
256 PortletDataContext portletDataContext, FileEntry fileEntry,
257 FileEntry importedFileEntry, Element fileEntryElement)
258 throws Exception {
259
260 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
261 return;
262 }
263
264 for (String previewType : _PREVIEW_TYPES) {
265 if (previewType.equals("mp3") || previewType.equals("ogg")) {
266 importPreview(
267 portletDataContext, fileEntry, importedFileEntry,
268 fileEntryElement, "audio", previewType);
269 }
270 }
271 }
272
273 private void _generateAudio(
274 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
275 throws Exception {
276
277 String tempFileId = DLUtil.getTempFileId(
278 destinationFileVersion.getFileEntryId(),
279 destinationFileVersion.getVersion());
280
281 File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
282
283 for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
284 previewTempFiles[i] = getPreviewTempFile(
285 tempFileId, _PREVIEW_TYPES[i]);
286 }
287
288 File audioTempFile = null;
289
290 InputStream inputStream = null;
291
292 try {
293 if (sourceFileVersion != null) {
294 copy(sourceFileVersion, destinationFileVersion);
295
296 return;
297 }
298
299 if (!XugglerUtil.isEnabled() || _hasAudio(destinationFileVersion)) {
300 return;
301 }
302
303 audioTempFile = FileUtil.createTempFile(
304 destinationFileVersion.getExtension());
305
306 if (!hasPreviews(destinationFileVersion)) {
307 File file = null;
308
309 if (destinationFileVersion instanceof LiferayFileVersion) {
310 try {
311 LiferayFileVersion liferayFileVersion =
312 (LiferayFileVersion)destinationFileVersion;
313
314 file = liferayFileVersion.getFile(false);
315 }
316 catch (UnsupportedOperationException uoe) {
317 }
318 }
319
320 if (file == null) {
321 inputStream = destinationFileVersion.getContentStream(
322 false);
323
324 FileUtil.write(audioTempFile, inputStream);
325
326 file = audioTempFile;
327 }
328
329 try {
330 _generateAudioXuggler(
331 destinationFileVersion, file, previewTempFiles);
332 }
333 catch (Exception e) {
334 _log.error(e, e);
335 }
336 }
337 }
338 catch (NoSuchFileEntryException nsfee) {
339 }
340 finally {
341 StreamUtil.cleanUp(inputStream);
342
343 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
344
345 for (int i = 0; i < previewTempFiles.length; i++) {
346 FileUtil.delete(previewTempFiles[i]);
347 }
348
349 FileUtil.delete(audioTempFile);
350 }
351 }
352
353 private void _generateAudioXuggler(
354 FileVersion fileVersion, File srcFile, File destFile,
355 String containerType)
356 throws Exception {
357
358 if (hasPreview(fileVersion, containerType)) {
359 return;
360 }
361
362 StopWatch stopWatch = null;
363
364 if (_log.isInfoEnabled()) {
365 stopWatch = new StopWatch();
366
367 stopWatch.start();
368 }
369
370 try {
371 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
372 ProcessCallable<String> processCallable =
373 new LiferayAudioProcessCallable(
374 ServerDetector.getServerId(),
375 PropsUtil.get(PropsKeys.LIFERAY_HOME),
376 Log4JUtil.getCustomLogSettings(),
377 srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
378 containerType,
379 PropsUtil.getProperties(
380 PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
381
382 Future<String> future = ProcessExecutor.execute(
383 ClassPathUtil.getPortalClassPath(), processCallable);
384
385 String processIdentity = String.valueOf(
386 fileVersion.getFileVersionId());
387
388 futures.put(processIdentity, future);
389
390 future.get();
391 }
392 else {
393 LiferayConverter liferayConverter = new LiferayAudioConverter(
394 srcFile.getCanonicalPath(), destFile.getCanonicalPath(),
395 containerType,
396 PropsUtil.getProperties(
397 PropsKeys.DL_FILE_ENTRY_PREVIEW_AUDIO, false));
398
399 liferayConverter.convert();
400 }
401 }
402 catch (CancellationException ce) {
403 if (_log.isInfoEnabled()) {
404 _log.info(
405 "Cancellation received for " +
406 fileVersion.getFileVersionId() + " " +
407 fileVersion.getTitle());
408 }
409 }
410 catch (Exception e) {
411 _log.error(e, e);
412 }
413
414 addFileToStore(
415 fileVersion.getCompanyId(), PREVIEW_PATH,
416 getPreviewFilePath(fileVersion, containerType), destFile);
417
418 if (_log.isInfoEnabled()) {
419 _log.info(
420 "Xuggler generated a " + containerType + " preview audio for " +
421 fileVersion.getFileVersionId() + " in " +
422 stopWatch.getTime() + "ms");
423 }
424 }
425
426 private void _generateAudioXuggler(
427 FileVersion fileVersion, File srcFile, File[] destFiles) {
428
429 try {
430 for (int i = 0; i < destFiles.length; i++) {
431 _generateAudioXuggler(
432 fileVersion, srcFile, destFiles[i], _PREVIEW_TYPES[i]);
433 }
434 }
435 catch (Exception e) {
436 _log.error(e, e);
437 }
438 }
439
440 private boolean _hasAudio(FileVersion fileVersion) throws Exception {
441 if (!isSupported(fileVersion)) {
442 return false;
443 }
444
445 return hasPreviews(fileVersion);
446 }
447
448 private void _queueGeneration(
449 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
450
451 if (_fileVersionIds.contains(
452 destinationFileVersion.getFileVersionId()) ||
453 !isSupported(destinationFileVersion)) {
454
455 return;
456 }
457
458 _fileVersionIds.add(destinationFileVersion.getFileVersionId());
459
460 sendGenerationMessage(
461 DestinationNames.DOCUMENT_LIBRARY_AUDIO_PROCESSOR,
462 PropsValues.DL_FILE_ENTRY_PROCESSORS_TRIGGER_SYNCHRONOUSLY,
463 sourceFileVersion, destinationFileVersion);
464 }
465
466 private static final String[] _PREVIEW_TYPES =
467 PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_CONTAINERS;
468
469 private static Log _log = LogFactoryUtil.getLog(AudioProcessor.class);
470
471 private Set<String> _audioMimeTypes = SetUtil.fromArray(
472 PropsValues.DL_FILE_ENTRY_PREVIEW_AUDIO_MIME_TYPES);
473 private List<Long> _fileVersionIds = new Vector<Long>();
474
475 private static class LiferayAudioProcessCallable
476 implements ProcessCallable<String> {
477
478 public LiferayAudioProcessCallable(
479 String serverId, String liferayHome,
480 Map<String, String> customLogSettings, String inputURL,
481 String outputURL, String audioContainer,
482 Properties audioProperties) {
483
484 _serverId = serverId;
485 _liferayHome = liferayHome;
486 _customLogSettings = customLogSettings;
487 _inputURL = inputURL;
488 _outputURL = outputURL;
489 _audioContainer = audioContainer;
490 _audioProperties = audioProperties;
491 }
492
493 public String call() throws ProcessException {
494 Properties systemProperties = System.getProperties();
495
496 SystemEnv.setProperties(systemProperties);
497
498 Class<?> clazz = getClass();
499
500 ClassLoader classLoader = clazz.getClassLoader();
501
502 Log4JUtil.initLog4J(
503 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
504 _customLogSettings);
505
506 try {
507 LiferayConverter liferayConverter = new LiferayAudioConverter(
508 _inputURL, _outputURL, _audioContainer, _audioProperties);
509
510 liferayConverter.convert();
511 }
512 catch (Exception e) {
513 throw new ProcessException(e);
514 }
515
516 return StringPool.BLANK;
517 }
518
519 private String _audioContainer;
520 private Properties _audioProperties;
521 private Map<String, String> _customLogSettings;
522 private String _inputURL;
523 private String _liferayHome;
524 private String _outputURL;
525 private String _serverId;
526
527 }
528
529 }