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