001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.document.library.kernel.exception.NoSuchFileEntryException;
018 import com.liferay.document.library.kernel.model.DLProcessorConstants;
019 import com.liferay.document.library.kernel.util.DLPreviewableProcessor;
020 import com.liferay.document.library.kernel.util.DLUtil;
021 import com.liferay.document.library.kernel.util.VideoProcessor;
022 import com.liferay.exportimport.kernel.lar.PortletDataContext;
023 import com.liferay.portal.fabric.InputResource;
024 import com.liferay.portal.fabric.OutputResource;
025 import com.liferay.portal.kernel.exception.SystemException;
026 import com.liferay.portal.kernel.image.ImageBag;
027 import com.liferay.portal.kernel.image.ImageToolUtil;
028 import com.liferay.portal.kernel.log.Log;
029 import com.liferay.portal.kernel.log.LogFactoryUtil;
030 import com.liferay.portal.kernel.messaging.DestinationNames;
031 import com.liferay.portal.kernel.process.ClassPathUtil;
032 import com.liferay.portal.kernel.process.ProcessCallable;
033 import com.liferay.portal.kernel.process.ProcessChannel;
034 import com.liferay.portal.kernel.process.ProcessException;
035 import com.liferay.portal.kernel.process.ProcessExecutorUtil;
036 import com.liferay.portal.kernel.repository.model.FileEntry;
037 import com.liferay.portal.kernel.repository.model.FileVersion;
038 import com.liferay.portal.kernel.util.FileUtil;
039 import com.liferay.portal.kernel.util.PropsKeys;
040 import com.liferay.portal.kernel.util.ServerDetector;
041 import com.liferay.portal.kernel.util.SetUtil;
042 import com.liferay.portal.kernel.util.StreamUtil;
043 import com.liferay.portal.kernel.util.StringBundler;
044 import com.liferay.portal.kernel.util.StringPool;
045 import com.liferay.portal.kernel.util.SystemEnv;
046 import com.liferay.portal.kernel.xml.Element;
047 import com.liferay.portal.kernel.xuggler.XugglerUtil;
048 import com.liferay.portal.log.Log4jLogFactoryImpl;
049 import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
050 import com.liferay.portal.util.PropsUtil;
051 import com.liferay.portal.util.PropsValues;
052 import com.liferay.util.log4j.Log4JUtil;
053
054 import java.awt.image.RenderedImage;
055
056 import java.io.File;
057 import java.io.InputStream;
058
059 import java.util.List;
060 import java.util.Map;
061 import java.util.Properties;
062 import java.util.Set;
063 import java.util.Vector;
064 import java.util.concurrent.CancellationException;
065 import java.util.concurrent.Future;
066
067 import org.apache.commons.lang.time.StopWatch;
068
069
075 public class VideoProcessorImpl
076 extends DLPreviewableProcessor implements VideoProcessor {
077
078 @Override
079 public void afterPropertiesSet() {
080 boolean valid = true;
081
082 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
083 valid = false;
084 }
085 else {
086 for (String previewType : _PREVIEW_TYPES) {
087 if (!previewType.equals("mp4") && !previewType.equals("ogv")) {
088 valid = false;
089
090 break;
091 }
092 }
093 }
094
095 if (!valid && _log.isWarnEnabled()) {
096 StringBundler sb = new StringBundler(5);
097
098 sb.append("Liferay is incorrectly configured to generate video ");
099 sb.append("previews using video containers other than MP4 or ");
100 sb.append("OGV. Please change the property ");
101 sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS);
102 sb.append(" in portal-ext.properties.");
103
104 _log.warn(sb.toString());
105 }
106
107 FileUtil.mkdirs(PREVIEW_TMP_PATH);
108 FileUtil.mkdirs(THUMBNAIL_TMP_PATH);
109 }
110
111 @Override
112 public void generateVideo(
113 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
114 throws Exception {
115
116 _generateVideo(sourceFileVersion, destinationFileVersion);
117 }
118
119 @Override
120 public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
121 throws Exception {
122
123 return doGetPreviewAsStream(fileVersion, type);
124 }
125
126 @Override
127 public long getPreviewFileSize(FileVersion fileVersion, String type)
128 throws Exception {
129
130 return doGetPreviewFileSize(fileVersion, type);
131 }
132
133 @Override
134 public InputStream getThumbnailAsStream(FileVersion fileVersion, int index)
135 throws Exception {
136
137 return doGetThumbnailAsStream(fileVersion, index);
138 }
139
140 @Override
141 public long getThumbnailFileSize(FileVersion fileVersion, int index)
142 throws Exception {
143
144 return doGetThumbnailFileSize(fileVersion, index);
145 }
146
147 @Override
148 public String getType() {
149 return DLProcessorConstants.VIDEO_PROCESSOR;
150 }
151
152 @Override
153 public Set<String> getVideoMimeTypes() {
154 return _videoMimeTypes;
155 }
156
157 @Override
158 public boolean hasVideo(FileVersion fileVersion) {
159 boolean hasVideo = false;
160
161 try {
162 hasVideo = _hasVideo(fileVersion);
163
164 if (!hasVideo && isSupported(fileVersion)) {
165 _queueGeneration(null, fileVersion);
166 }
167 }
168 catch (Exception e) {
169 _log.error(e, e);
170 }
171
172 return hasVideo;
173 }
174
175 @Override
176 public boolean isSupported(String mimeType) {
177 if (_videoMimeTypes.contains(mimeType) && XugglerUtil.isEnabled()) {
178 return true;
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 }
462 catch (Exception e) {
463 _log.error(e, e);
464 }
465 }
466
467 if (!hasThumbnails(destinationFileVersion)) {
468 try {
469 _generateThumbnailXuggler(
470 destinationFileVersion, file,
471 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
472 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
473 }
474 catch (Exception e) {
475 _log.error(e, e);
476 }
477 }
478 }
479 catch (NoSuchFileEntryException nsfee) {
480 if (_log.isDebugEnabled()) {
481 _log.debug(nsfee, nsfee);
482 }
483 }
484 finally {
485 StreamUtil.cleanUp(inputStream);
486
487 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
488
489 for (int i = 0; i < previewTempFiles.length; i++) {
490 FileUtil.delete(previewTempFiles[i]);
491 }
492
493 FileUtil.delete(videoTempFile);
494 }
495 }
496
497 private void _generateVideoXuggler(
498 FileVersion fileVersion, File sourceFile, File destinationFile,
499 String containerType)
500 throws Exception {
501
502 if (hasPreview(fileVersion, containerType)) {
503 return;
504 }
505
506 StopWatch stopWatch = new StopWatch();
507
508 stopWatch.start();
509
510 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
511 ProcessCallable<String> processCallable =
512 new LiferayVideoProcessCallable(
513 ServerDetector.getServerId(),
514 PropsUtil.get(PropsKeys.LIFERAY_HOME),
515 Log4JUtil.getCustomLogSettings(), sourceFile,
516 destinationFile, containerType,
517 PropsUtil.getProperties(
518 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
519 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
520
521 ProcessChannel<String> processChannel = ProcessExecutorUtil.execute(
522 ClassPathUtil.getPortalProcessConfig(), processCallable);
523
524 Future<String> future = processChannel.getProcessNoticeableFuture();
525
526 String processIdentity = String.valueOf(
527 fileVersion.getFileVersionId());
528
529 futures.put(processIdentity, future);
530
531 future.get();
532 }
533 else {
534 LiferayConverter liferayConverter = new LiferayVideoConverter(
535 sourceFile.getCanonicalPath(),
536 destinationFile.getCanonicalPath(), containerType,
537 PropsUtil.getProperties(
538 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
539 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
540
541 liferayConverter.convert();
542 }
543
544 addFileToStore(
545 fileVersion.getCompanyId(), PREVIEW_PATH,
546 getPreviewFilePath(fileVersion, containerType), destinationFile);
547
548 if (_log.isInfoEnabled()) {
549 _log.info(
550 "Xuggler generated a " + containerType + " preview video for " +
551 fileVersion.getTitle() + " in " + stopWatch.getTime() +
552 " ms");
553 }
554 }
555
556 private void _generateVideoXuggler(
557 FileVersion fileVersion, File sourceFile, File[] destinationFiles) {
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 final Log _log = LogFactoryUtil.getLog(
608 VideoProcessorImpl.class);
609
610 private final List<Long> _fileVersionIds = new Vector<>();
611 private final Set<String> _videoMimeTypes = SetUtil.fromArray(
612 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_MIME_TYPES);
613
614 private static class LiferayVideoProcessCallable
615 implements ProcessCallable<String> {
616
617 public LiferayVideoProcessCallable(
618 String serverId, String liferayHome,
619 Map<String, String> customLogSettings, File inputFile,
620 File outputFile, String videoContainer, Properties videoProperties,
621 Properties ffpresetProperties) {
622
623 _serverId = serverId;
624 _liferayHome = liferayHome;
625 _customLogSettings = customLogSettings;
626 _inputFile = inputFile;
627 _outputFile = outputFile;
628 _videoContainer = videoContainer;
629 _videoProperties = videoProperties;
630 _ffpresetProperties = ffpresetProperties;
631 }
632
633 @Override
634 public String call() throws ProcessException {
635 XugglerAutoInstallHelper.installNativeLibraries();
636
637 Properties systemProperties = System.getProperties();
638
639 SystemEnv.setProperties(systemProperties);
640
641 Class<?> clazz = getClass();
642
643 ClassLoader classLoader = clazz.getClassLoader();
644
645 Log4JUtil.initLog4J(
646 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
647 _customLogSettings);
648
649 try {
650 LiferayConverter liferayConverter = new LiferayVideoConverter(
651 _inputFile.getCanonicalPath(),
652 _outputFile.getCanonicalPath(), _videoContainer,
653 _videoProperties, _ffpresetProperties);
654
655 liferayConverter.convert();
656 }
657 catch (Exception e) {
658 throw new ProcessException(e);
659 }
660
661 return StringPool.BLANK;
662 }
663
664 private static final long serialVersionUID = 1L;
665
666 private Map<String, String> _customLogSettings;
667 private final Properties _ffpresetProperties;
668
669 @InputResource
670 private File _inputFile;
671
672 private String _liferayHome;
673
674 @OutputResource
675 private File _outputFile;
676
677 private String _serverId;
678 private final String _videoContainer;
679 private final Properties _videoProperties;
680
681 }
682
683 private static class LiferayVideoThumbnailProcessCallable
684 implements ProcessCallable<String> {
685
686 public LiferayVideoThumbnailProcessCallable(
687 String serverId, String liferayHome,
688 Map<String, String> customLogSettings, File inputFile,
689 File outputFile, String extension, int height, int width,
690 int percentage) {
691
692 _serverId = serverId;
693 _liferayHome = liferayHome;
694 _customLogSettings = customLogSettings;
695 _inputFile = inputFile;
696 _outputFile = outputFile;
697 _extension = extension;
698 _height = height;
699 _width = width;
700 _percentage = percentage;
701 }
702
703 @Override
704 public String call() throws ProcessException {
705 XugglerAutoInstallHelper.installNativeLibraries();
706
707 Class<?> clazz = getClass();
708
709 ClassLoader classLoader = clazz.getClassLoader();
710
711 Properties systemProperties = System.getProperties();
712 SystemEnv.setProperties(systemProperties);
713
714 Log4JUtil.initLog4J(
715 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
716 _customLogSettings);
717
718 try {
719 LiferayConverter liferayConverter =
720 new LiferayVideoThumbnailConverter(
721 _inputFile.getCanonicalPath(), _outputFile, _extension,
722 _height, _width, _percentage);
723
724 liferayConverter.convert();
725 }
726 catch (Exception e) {
727 throw new ProcessException(e);
728 }
729
730 return StringPool.BLANK;
731 }
732
733 private static final long serialVersionUID = 1L;
734
735 private Map<String, String> _customLogSettings;
736 private final String _extension;
737 private final int _height;
738
739 @InputResource
740 private File _inputFile;
741
742 private String _liferayHome;
743
744 @OutputResource
745 private File _outputFile;
746
747 private final int _percentage;
748 private String _serverId;
749 private final int _width;
750
751 }
752
753 }