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