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.exception.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 if (_log.isDebugEnabled()) {
478 _log.debug(nsfee, nsfee);
479 }
480 }
481 finally {
482 StreamUtil.cleanUp(inputStream);
483
484 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
485
486 for (int i = 0; i < previewTempFiles.length; i++) {
487 FileUtil.delete(previewTempFiles[i]);
488 }
489
490 FileUtil.delete(videoTempFile);
491 }
492 }
493
494 private void _generateVideoXuggler(
495 FileVersion fileVersion, File sourceFile, File destinationFile,
496 String containerType)
497 throws Exception {
498
499 if (hasPreview(fileVersion, containerType)) {
500 return;
501 }
502
503 StopWatch stopWatch = new StopWatch();
504
505 stopWatch.start();
506
507 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
508 ProcessCallable<String> processCallable =
509 new LiferayVideoProcessCallable(
510 ServerDetector.getServerId(),
511 PropsUtil.get(PropsKeys.LIFERAY_HOME),
512 Log4JUtil.getCustomLogSettings(), sourceFile,
513 destinationFile, containerType,
514 PropsUtil.getProperties(
515 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
516 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
517
518 ProcessChannel<String> processChannel = ProcessExecutorUtil.execute(
519 ClassPathUtil.getPortalProcessConfig(), processCallable);
520
521 Future<String> future = processChannel.getProcessNoticeableFuture();
522
523 String processIdentity = String.valueOf(
524 fileVersion.getFileVersionId());
525
526 futures.put(processIdentity, future);
527
528 future.get();
529 }
530 else {
531 LiferayConverter liferayConverter = new LiferayVideoConverter(
532 sourceFile.getCanonicalPath(),
533 destinationFile.getCanonicalPath(), containerType,
534 PropsUtil.getProperties(
535 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
536 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
537
538 liferayConverter.convert();
539 }
540
541 addFileToStore(
542 fileVersion.getCompanyId(), PREVIEW_PATH,
543 getPreviewFilePath(fileVersion, containerType), destinationFile);
544
545 if (_log.isInfoEnabled()) {
546 _log.info(
547 "Xuggler generated a " + containerType + " preview video for " +
548 fileVersion.getTitle() + " in " + stopWatch.getTime() +
549 " ms");
550 }
551 }
552
553 private void _generateVideoXuggler(
554 FileVersion fileVersion, File sourceFile, File[] destinationFiles) {
555
556 try {
557 for (int i = 0; i < destinationFiles.length; i++) {
558 _generateVideoXuggler(
559 fileVersion, sourceFile, destinationFiles[i],
560 _PREVIEW_TYPES[i]);
561 }
562 }
563 catch (CancellationException ce) {
564 if (_log.isInfoEnabled()) {
565 _log.info(
566 "Cancellation received for " +
567 fileVersion.getFileVersionId() + " " +
568 fileVersion.getTitle());
569 }
570 }
571 catch (Exception e) {
572 _log.error(e, e);
573 }
574 }
575
576 private boolean _hasVideo(FileVersion fileVersion) throws Exception {
577 if (!isSupported(fileVersion)) {
578 return false;
579 }
580
581 return hasPreviews(fileVersion) && hasThumbnails(fileVersion);
582 }
583
584 private void _queueGeneration(
585 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
586
587 if (_fileVersionIds.contains(
588 destinationFileVersion.getFileVersionId()) ||
589 !isSupported(destinationFileVersion)) {
590
591 return;
592 }
593
594 _fileVersionIds.add(destinationFileVersion.getFileVersionId());
595
596 sendGenerationMessage(
597 DestinationNames.DOCUMENT_LIBRARY_VIDEO_PROCESSOR,
598 sourceFileVersion, destinationFileVersion);
599 }
600
601 private static final String[] _PREVIEW_TYPES =
602 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS;
603
604 private static final Log _log = LogFactoryUtil.getLog(
605 VideoProcessorImpl.class);
606
607 private final List<Long> _fileVersionIds = new Vector<>();
608 private final Set<String> _videoMimeTypes = SetUtil.fromArray(
609 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_MIME_TYPES);
610
611 private static class LiferayVideoProcessCallable
612 implements ProcessCallable<String> {
613
614 public LiferayVideoProcessCallable(
615 String serverId, String liferayHome,
616 Map<String, String> customLogSettings, File inputFile,
617 File outputFile, String videoContainer, Properties videoProperties,
618 Properties ffpresetProperties) {
619
620 _serverId = serverId;
621 _liferayHome = liferayHome;
622 _customLogSettings = customLogSettings;
623 _inputFile = inputFile;
624 _outputFile = outputFile;
625 _videoContainer = videoContainer;
626 _videoProperties = videoProperties;
627 _ffpresetProperties = ffpresetProperties;
628 }
629
630 @Override
631 public String call() throws ProcessException {
632 XugglerAutoInstallHelper.installNativeLibraries();
633
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 final 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 final String _videoContainer;
676 private final 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 XugglerAutoInstallHelper.installNativeLibraries();
703
704 Class<?> clazz = getClass();
705
706 ClassLoader classLoader = clazz.getClassLoader();
707
708 Properties systemProperties = System.getProperties();
709 SystemEnv.setProperties(systemProperties);
710
711 Log4JUtil.initLog4J(
712 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
713 _customLogSettings);
714
715 try {
716 LiferayConverter liferayConverter =
717 new LiferayVideoThumbnailConverter(
718 _inputFile.getCanonicalPath(), _outputFile, _extension,
719 _height, _width, _percentage);
720
721 liferayConverter.convert();
722 }
723 catch (Exception e) {
724 throw new ProcessException(e);
725 }
726
727 return StringPool.BLANK;
728 }
729
730 private static final long serialVersionUID = 1L;
731
732 private Map<String, String> _customLogSettings;
733 private final String _extension;
734 private final int _height;
735
736 @InputResource
737 private File _inputFile;
738
739 private String _liferayHome;
740
741 @OutputResource
742 private File _outputFile;
743
744 private final int _percentage;
745 private String _serverId;
746 private final int _width;
747
748 }
749
750 }