001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.portal.fabric.InputResource;
018 import com.liferay.portal.fabric.OutputResource;
019 import com.liferay.portal.kernel.exception.SystemException;
020 import com.liferay.portal.kernel.image.ImageBag;
021 import com.liferay.portal.kernel.image.ImageToolUtil;
022 import com.liferay.portal.kernel.lar.PortletDataContext;
023 import com.liferay.portal.kernel.log.Log;
024 import com.liferay.portal.kernel.log.LogFactoryUtil;
025 import com.liferay.portal.kernel.messaging.DestinationNames;
026 import com.liferay.portal.kernel.process.ClassPathUtil;
027 import com.liferay.portal.kernel.process.ProcessCallable;
028 import com.liferay.portal.kernel.process.ProcessChannel;
029 import com.liferay.portal.kernel.process.ProcessException;
030 import com.liferay.portal.kernel.process.ProcessExecutorUtil;
031 import com.liferay.portal.kernel.repository.model.FileEntry;
032 import com.liferay.portal.kernel.repository.model.FileVersion;
033 import com.liferay.portal.kernel.util.FileUtil;
034 import com.liferay.portal.kernel.util.PropsKeys;
035 import com.liferay.portal.kernel.util.ServerDetector;
036 import com.liferay.portal.kernel.util.SetUtil;
037 import com.liferay.portal.kernel.util.StreamUtil;
038 import com.liferay.portal.kernel.util.StringBundler;
039 import com.liferay.portal.kernel.util.StringPool;
040 import com.liferay.portal.kernel.util.SystemEnv;
041 import com.liferay.portal.kernel.util.Validator;
042 import com.liferay.portal.kernel.xml.Element;
043 import com.liferay.portal.kernel.xuggler.XugglerUtil;
044 import com.liferay.portal.log.Log4jLogFactoryImpl;
045 import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
046 import com.liferay.portal.util.PropsUtil;
047 import com.liferay.portal.util.PropsValues;
048 import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
049 import com.liferay.util.log4j.Log4JUtil;
050
051 import java.awt.image.RenderedImage;
052
053 import java.io.File;
054 import java.io.InputStream;
055
056 import java.util.List;
057 import java.util.Map;
058 import java.util.Properties;
059 import java.util.Set;
060 import java.util.Vector;
061 import java.util.concurrent.CancellationException;
062 import java.util.concurrent.Future;
063
064 import org.apache.commons.lang.time.StopWatch;
065
066
072 public class VideoProcessorImpl
073 extends DLPreviewableProcessor implements VideoProcessor {
074
075 @Override
076 public void afterPropertiesSet() {
077 boolean valid = true;
078
079 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
080 valid = false;
081 }
082 else {
083 for (String previewType : _PREVIEW_TYPES) {
084 if (!previewType.equals("mp4") && !previewType.equals("ogv")) {
085 valid = false;
086
087 break;
088 }
089 }
090 }
091
092 if (!valid && _log.isWarnEnabled()) {
093 StringBundler sb = new StringBundler(5);
094
095 sb.append("Liferay is incorrectly configured to generate video ");
096 sb.append("previews using video containers other than MP4 or ");
097 sb.append("OGV. Please change the property ");
098 sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS);
099 sb.append(" in portal-ext.properties.");
100
101 _log.warn(sb.toString());
102 }
103
104 FileUtil.mkdirs(PREVIEW_TMP_PATH);
105 FileUtil.mkdirs(THUMBNAIL_TMP_PATH);
106 }
107
108 @Override
109 public void generateVideo(
110 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
111 throws Exception {
112
113 _generateVideo(sourceFileVersion, destinationFileVersion);
114 }
115
116 @Override
117 public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
118 throws Exception {
119
120 return doGetPreviewAsStream(fileVersion, type);
121 }
122
123 @Override
124 public long getPreviewFileSize(FileVersion fileVersion, String type)
125 throws Exception {
126
127 return doGetPreviewFileSize(fileVersion, type);
128 }
129
130 @Override
131 public InputStream getThumbnailAsStream(FileVersion fileVersion, int index)
132 throws Exception {
133
134 return doGetThumbnailAsStream(fileVersion, index);
135 }
136
137 @Override
138 public long getThumbnailFileSize(FileVersion fileVersion, int index)
139 throws Exception {
140
141 return doGetThumbnailFileSize(fileVersion, index);
142 }
143
144 @Override
145 public Set<String> getVideoMimeTypes() {
146 return _videoMimeTypes;
147 }
148
149 @Override
150 public boolean hasVideo(FileVersion fileVersion) {
151 boolean hasVideo = false;
152
153 try {
154 hasVideo = _hasVideo(fileVersion);
155
156 if (!hasVideo && isSupported(fileVersion)) {
157 _queueGeneration(null, fileVersion);
158 }
159 }
160 catch (Exception e) {
161 _log.error(e, e);
162 }
163
164 return hasVideo;
165 }
166
167 @Override
168 public boolean isSupported(String mimeType) {
169 if (Validator.isNull(mimeType)) {
170 return false;
171 }
172
173 try {
174 if (XugglerUtil.isEnabled()) {
175 return _videoMimeTypes.contains(mimeType);
176 }
177 }
178 catch (Exception e) {
179 }
180
181 return false;
182 }
183
184 @Override
185 public boolean isVideoSupported(FileVersion fileVersion) {
186 return isSupported(fileVersion);
187 }
188
189 @Override
190 public boolean isVideoSupported(String mimeType) {
191 return isSupported(mimeType);
192 }
193
194 @Override
195 public void trigger(
196 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
197
198 super.trigger(sourceFileVersion, destinationFileVersion);
199
200 _queueGeneration(sourceFileVersion, destinationFileVersion);
201 }
202
203 @Override
204 protected void doExportGeneratedFiles(
205 PortletDataContext portletDataContext, FileEntry fileEntry,
206 Element fileEntryElement)
207 throws Exception {
208
209 exportThumbnails(
210 portletDataContext, fileEntry, fileEntryElement, "video");
211
212 exportPreviews(portletDataContext, fileEntry, fileEntryElement);
213 }
214
215 @Override
216 protected void doImportGeneratedFiles(
217 PortletDataContext portletDataContext, FileEntry fileEntry,
218 FileEntry importedFileEntry, Element fileEntryElement)
219 throws Exception {
220
221 importThumbnails(
222 portletDataContext, fileEntry, importedFileEntry, fileEntryElement,
223 "video");
224
225 importPreviews(
226 portletDataContext, fileEntry, importedFileEntry, fileEntryElement);
227 }
228
229 protected void exportPreviews(
230 PortletDataContext portletDataContext, FileEntry fileEntry,
231 Element fileEntryElement)
232 throws Exception {
233
234 FileVersion fileVersion = fileEntry.getFileVersion();
235
236 if (!isSupported(fileVersion) || !hasPreviews(fileVersion)) {
237 return;
238 }
239
240 if (!portletDataContext.isPerformDirectBinaryImport()) {
241 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
242 return;
243 }
244
245 for (String previewType : _PREVIEW_TYPES) {
246 if (previewType.equals("mp4") || previewType.equals("ogv")) {
247 exportPreview(
248 portletDataContext, fileEntry, fileEntryElement,
249 "video", previewType);
250 }
251 }
252 }
253 }
254
255 @Override
256 protected List<Long> getFileVersionIds() {
257 return _fileVersionIds;
258 }
259
260 @Override
261 protected String getPreviewType(FileVersion fileVersion) {
262 return _PREVIEW_TYPES[0];
263 }
264
265 @Override
266 protected String[] getPreviewTypes() {
267 return _PREVIEW_TYPES;
268 }
269
270 @Override
271 protected String getThumbnailType(FileVersion fileVersion) {
272 return THUMBNAIL_TYPE;
273 }
274
275 protected void importPreviews(
276 PortletDataContext portletDataContext, FileEntry fileEntry,
277 FileEntry importedFileEntry, Element fileEntryElement)
278 throws Exception {
279
280 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
281 return;
282 }
283
284 for (String previewType : _PREVIEW_TYPES) {
285 if (previewType.equals("mp4") || previewType.equals("ogv")) {
286 importPreview(
287 portletDataContext, fileEntry, importedFileEntry,
288 fileEntryElement, "video", previewType);
289 }
290 }
291 }
292
293 @Override
294 protected void storeThumbnailImages(FileVersion fileVersion, File file)
295 throws Exception {
296
297 if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_DEFAULT)) {
298 addFileToStore(
299 fileVersion.getCompanyId(), THUMBNAIL_PATH,
300 getThumbnailFilePath(fileVersion, THUMBNAIL_INDEX_DEFAULT),
301 file);
302 }
303
304 if (isThumbnailEnabled(THUMBNAIL_INDEX_CUSTOM_1) ||
305 isThumbnailEnabled(THUMBNAIL_INDEX_CUSTOM_2)) {
306
307 ImageBag imageBag = ImageToolUtil.read(file);
308
309 RenderedImage renderedImage = imageBag.getRenderedImage();
310
311 storeThumbnailImage(
312 fileVersion, renderedImage, THUMBNAIL_INDEX_CUSTOM_1);
313 storeThumbnailImage(
314 fileVersion, renderedImage, THUMBNAIL_INDEX_CUSTOM_2);
315 }
316 }
317
318 private void _generateThumbnailXuggler(
319 FileVersion fileVersion, File file, int height, int width)
320 throws Exception {
321
322 StopWatch stopWatch = new StopWatch();
323
324 stopWatch.start();
325
326 String tempFileId = DLUtil.getTempFileId(
327 fileVersion.getFileEntryId(), fileVersion.getVersion());
328
329 File thumbnailTempFile = getThumbnailTempFile(tempFileId);
330
331 try {
332 try {
333 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
334 ProcessCallable<String> processCallable =
335 new LiferayVideoThumbnailProcessCallable(
336 ServerDetector.getServerId(),
337 PropsUtil.get(PropsKeys.LIFERAY_HOME),
338 Log4JUtil.getCustomLogSettings(), file,
339 thumbnailTempFile, THUMBNAIL_TYPE, height, width,
340 PropsValues.
341 DL_FILE_ENTRY_THUMBNAIL_VIDEO_FRAME_PERCENTAGE);
342
343 ProcessChannel<String> processChannel =
344 ProcessExecutorUtil.execute(
345 ClassPathUtil.getPortalProcessConfig(),
346 processCallable);
347
348 Future<String> future =
349 processChannel.getProcessNoticeableFuture();
350
351 String processIdentity = String.valueOf(
352 fileVersion.getFileVersionId());
353
354 futures.put(processIdentity, future);
355
356 future.get();
357 }
358 else {
359 LiferayConverter liferayConverter =
360 new LiferayVideoThumbnailConverter(
361 file.getCanonicalPath(), thumbnailTempFile,
362 THUMBNAIL_TYPE, height, width,
363 PropsValues.
364 DL_FILE_ENTRY_THUMBNAIL_VIDEO_FRAME_PERCENTAGE);
365
366 liferayConverter.convert();
367 }
368 }
369 catch (CancellationException ce) {
370 if (_log.isInfoEnabled()) {
371 _log.info(
372 "Cancellation received for " +
373 fileVersion.getFileVersionId() + " " +
374 fileVersion.getTitle());
375 }
376 }
377 catch (Exception e) {
378 _log.error(e, e);
379 }
380
381 storeThumbnailImages(fileVersion, thumbnailTempFile);
382
383 if (_log.isInfoEnabled()) {
384 _log.info(
385 "Xuggler generated a thumbnail for " +
386 fileVersion.getTitle() + " in " + stopWatch.getTime() +
387 " ms");
388 }
389 }
390 catch (Exception e) {
391 throw new SystemException(e);
392 }
393 finally {
394 FileUtil.delete(thumbnailTempFile);
395 }
396 }
397
398 private void _generateVideo(
399 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
400 throws Exception {
401
402 if (!XugglerUtil.isEnabled() || _hasVideo(destinationFileVersion)) {
403 return;
404 }
405
406 InputStream inputStream = null;
407
408 File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
409
410 File videoTempFile = null;
411
412 try {
413 if (sourceFileVersion != null) {
414 copy(sourceFileVersion, destinationFileVersion);
415
416 return;
417 }
418
419 File file = null;
420
421 if (!hasPreviews(destinationFileVersion) ||
422 !hasThumbnails(destinationFileVersion)) {
423
424 if (destinationFileVersion instanceof LiferayFileVersion) {
425 try {
426 LiferayFileVersion liferayFileVersion =
427 (LiferayFileVersion)destinationFileVersion;
428
429 file = liferayFileVersion.getFile(false);
430 }
431 catch (UnsupportedOperationException uoe) {
432 }
433 }
434
435 if (file == null) {
436 inputStream = destinationFileVersion.getContentStream(
437 false);
438
439 videoTempFile = FileUtil.createTempFile(
440 destinationFileVersion.getExtension());
441
442 FileUtil.write(videoTempFile, inputStream);
443
444 file = videoTempFile;
445 }
446 }
447
448 if (!hasPreviews(destinationFileVersion)) {
449 String tempFileId = DLUtil.getTempFileId(
450 destinationFileVersion.getFileEntryId(),
451 destinationFileVersion.getVersion());
452
453 for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
454 previewTempFiles[i] = getPreviewTempFile(
455 tempFileId, _PREVIEW_TYPES[i]);
456 }
457
458 try {
459 _generateVideoXuggler(
460 destinationFileVersion, file, previewTempFiles,
461 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
462 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
463 }
464 catch (Exception e) {
465 _log.error(e, e);
466 }
467 }
468
469 if (!hasThumbnails(destinationFileVersion)) {
470 try {
471 _generateThumbnailXuggler(
472 destinationFileVersion, file,
473 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
474 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
475 }
476 catch (Exception e) {
477 _log.error(e, e);
478 }
479 }
480 }
481 catch (NoSuchFileEntryException nsfee) {
482 }
483 finally {
484 StreamUtil.cleanUp(inputStream);
485
486 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
487
488 for (int i = 0; i < previewTempFiles.length; i++) {
489 FileUtil.delete(previewTempFiles[i]);
490 }
491
492 FileUtil.delete(videoTempFile);
493 }
494 }
495
496 private void _generateVideoXuggler(
497 FileVersion fileVersion, File sourceFile, File destinationFile,
498 String containerType)
499 throws Exception {
500
501 if (hasPreview(fileVersion, containerType)) {
502 return;
503 }
504
505 StopWatch stopWatch = new StopWatch();
506
507 stopWatch.start();
508
509 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
510 ProcessCallable<String> processCallable =
511 new LiferayVideoProcessCallable(
512 ServerDetector.getServerId(),
513 PropsUtil.get(PropsKeys.LIFERAY_HOME),
514 Log4JUtil.getCustomLogSettings(), sourceFile,
515 destinationFile, containerType,
516 PropsUtil.getProperties(
517 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
518 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
519
520 ProcessChannel<String> processChannel = ProcessExecutorUtil.execute(
521 ClassPathUtil.getPortalProcessConfig(), processCallable);
522
523 Future<String> future = processChannel.getProcessNoticeableFuture();
524
525 String processIdentity = String.valueOf(
526 fileVersion.getFileVersionId());
527
528 futures.put(processIdentity, future);
529
530 future.get();
531 }
532 else {
533 LiferayConverter liferayConverter = new LiferayVideoConverter(
534 sourceFile.getCanonicalPath(),
535 destinationFile.getCanonicalPath(), containerType,
536 PropsUtil.getProperties(
537 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
538 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
539
540 liferayConverter.convert();
541 }
542
543 addFileToStore(
544 fileVersion.getCompanyId(), PREVIEW_PATH,
545 getPreviewFilePath(fileVersion, containerType), destinationFile);
546
547 if (_log.isInfoEnabled()) {
548 _log.info(
549 "Xuggler generated a " + containerType + " preview video for " +
550 fileVersion.getTitle() + " in " + stopWatch.getTime() +
551 " ms");
552 }
553 }
554
555 private void _generateVideoXuggler(
556 FileVersion fileVersion, File sourceFile, File[] destinationFiles,
557 int height, int width) {
558
559 try {
560 for (int i = 0; i < destinationFiles.length; i++) {
561 _generateVideoXuggler(
562 fileVersion, sourceFile, destinationFiles[i],
563 _PREVIEW_TYPES[i]);
564 }
565 }
566 catch (CancellationException ce) {
567 if (_log.isInfoEnabled()) {
568 _log.info(
569 "Cancellation received for " +
570 fileVersion.getFileVersionId() + " " +
571 fileVersion.getTitle());
572 }
573 }
574 catch (Exception e) {
575 _log.error(e, e);
576 }
577 }
578
579 private boolean _hasVideo(FileVersion fileVersion) throws Exception {
580 if (!isSupported(fileVersion)) {
581 return false;
582 }
583
584 return hasPreviews(fileVersion) && hasThumbnails(fileVersion);
585 }
586
587 private void _queueGeneration(
588 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
589
590 if (_fileVersionIds.contains(
591 destinationFileVersion.getFileVersionId()) ||
592 !isSupported(destinationFileVersion)) {
593
594 return;
595 }
596
597 _fileVersionIds.add(destinationFileVersion.getFileVersionId());
598
599 sendGenerationMessage(
600 DestinationNames.DOCUMENT_LIBRARY_VIDEO_PROCESSOR,
601 sourceFileVersion, destinationFileVersion);
602 }
603
604 private static final String[] _PREVIEW_TYPES =
605 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS;
606
607 private static Log _log = LogFactoryUtil.getLog(VideoProcessorImpl.class);
608
609 private List<Long> _fileVersionIds = new Vector<Long>();
610 private Set<String> _videoMimeTypes = SetUtil.fromArray(
611 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_MIME_TYPES);
612
613 private static class LiferayVideoProcessCallable
614 implements ProcessCallable<String> {
615
616 public LiferayVideoProcessCallable(
617 String serverId, String liferayHome,
618 Map<String, String> customLogSettings, File inputFile,
619 File outputFile, String videoContainer, Properties videoProperties,
620 Properties ffpresetProperties) {
621
622 _serverId = serverId;
623 _liferayHome = liferayHome;
624 _customLogSettings = customLogSettings;
625 _inputFile = inputFile;
626 _outputFile = outputFile;
627 _videoContainer = videoContainer;
628 _videoProperties = videoProperties;
629 _ffpresetProperties = ffpresetProperties;
630 }
631
632 @Override
633 public String call() throws ProcessException {
634 Properties systemProperties = System.getProperties();
635
636 SystemEnv.setProperties(systemProperties);
637
638 Class<?> clazz = getClass();
639
640 ClassLoader classLoader = clazz.getClassLoader();
641
642 Log4JUtil.initLog4J(
643 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
644 _customLogSettings);
645
646 try {
647 LiferayConverter liferayConverter = new LiferayVideoConverter(
648 _inputFile.getCanonicalPath(),
649 _outputFile.getCanonicalPath(), _videoContainer,
650 _videoProperties, _ffpresetProperties);
651
652 liferayConverter.convert();
653 }
654 catch (Exception e) {
655 throw new ProcessException(e);
656 }
657
658 return StringPool.BLANK;
659 }
660
661 private static final long serialVersionUID = 1L;
662
663 private Map<String, String> _customLogSettings;
664 private Properties _ffpresetProperties;
665
666 @InputResource
667 private File _inputFile;
668
669 private String _liferayHome;
670
671 @OutputResource
672 private File _outputFile;
673
674 private String _serverId;
675 private String _videoContainer;
676 private Properties _videoProperties;
677
678 }
679
680 private static class LiferayVideoThumbnailProcessCallable
681 implements ProcessCallable<String> {
682
683 public LiferayVideoThumbnailProcessCallable(
684 String serverId, String liferayHome,
685 Map<String, String> customLogSettings, File inputFile,
686 File outputFile, String extension, int height, int width,
687 int percentage) {
688
689 _serverId = serverId;
690 _liferayHome = liferayHome;
691 _customLogSettings = customLogSettings;
692 _inputFile = inputFile;
693 _outputFile = outputFile;
694 _extension = extension;
695 _height = height;
696 _width = width;
697 _percentage = percentage;
698 }
699
700 @Override
701 public String call() throws ProcessException {
702 Class<?> clazz = getClass();
703
704 ClassLoader classLoader = clazz.getClassLoader();
705
706 Properties systemProperties = System.getProperties();
707 SystemEnv.setProperties(systemProperties);
708
709 Log4JUtil.initLog4J(
710 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
711 _customLogSettings);
712
713 try {
714 LiferayConverter liferayConverter =
715 new LiferayVideoThumbnailConverter(
716 _inputFile.getCanonicalPath(), _outputFile, _extension,
717 _height, _width, _percentage);
718
719 liferayConverter.convert();
720 }
721 catch (Exception e) {
722 throw new ProcessException(e);
723 }
724
725 return StringPool.BLANK;
726 }
727
728 private static final long serialVersionUID = 1L;
729
730 private Map<String, String> _customLogSettings;
731 private String _extension;
732 private int _height;
733
734 @InputResource
735 private File _inputFile;
736
737 private String _liferayHome;
738
739 @OutputResource
740 private File _outputFile;
741
742 private int _percentage;
743 private String _serverId;
744 private int _width;
745
746 }
747
748 }