001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.portal.kernel.exception.SystemException;
018 import com.liferay.portal.kernel.image.ImageBag;
019 import com.liferay.portal.kernel.image.ImageToolUtil;
020 import com.liferay.portal.kernel.lar.PortletDataContext;
021 import com.liferay.portal.kernel.log.Log;
022 import com.liferay.portal.kernel.log.LogFactoryUtil;
023 import com.liferay.portal.kernel.messaging.DestinationNames;
024 import com.liferay.portal.kernel.process.ClassPathUtil;
025 import com.liferay.portal.kernel.process.ProcessCallable;
026 import com.liferay.portal.kernel.process.ProcessException;
027 import com.liferay.portal.kernel.process.ProcessExecutor;
028 import com.liferay.portal.kernel.repository.model.FileEntry;
029 import com.liferay.portal.kernel.repository.model.FileVersion;
030 import com.liferay.portal.kernel.util.FileUtil;
031 import com.liferay.portal.kernel.util.PropsKeys;
032 import com.liferay.portal.kernel.util.ServerDetector;
033 import com.liferay.portal.kernel.util.SetUtil;
034 import com.liferay.portal.kernel.util.StreamUtil;
035 import com.liferay.portal.kernel.util.StringBundler;
036 import com.liferay.portal.kernel.util.StringPool;
037 import com.liferay.portal.kernel.util.SystemEnv;
038 import com.liferay.portal.kernel.util.Validator;
039 import com.liferay.portal.kernel.xml.Element;
040 import com.liferay.portal.kernel.xuggler.XugglerUtil;
041 import com.liferay.portal.log.Log4jLogFactoryImpl;
042 import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
043 import com.liferay.portal.util.PropsUtil;
044 import com.liferay.portal.util.PropsValues;
045 import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
046 import com.liferay.util.log4j.Log4JUtil;
047
048 import java.awt.image.RenderedImage;
049
050 import java.io.File;
051 import java.io.InputStream;
052
053 import java.util.List;
054 import java.util.Map;
055 import java.util.Properties;
056 import java.util.Set;
057 import java.util.Vector;
058 import java.util.concurrent.CancellationException;
059 import java.util.concurrent.Future;
060
061 import org.apache.commons.lang.time.StopWatch;
062
063
069 public class VideoProcessorImpl
070 extends DLPreviewableProcessor implements VideoProcessor {
071
072 @Override
073 public void afterPropertiesSet() {
074 boolean valid = true;
075
076 if ((_PREVIEW_TYPES.length == 0) || (_PREVIEW_TYPES.length > 2)) {
077 valid = false;
078 }
079 else {
080 for (String previewType : _PREVIEW_TYPES) {
081 if (!previewType.equals("mp4") && !previewType.equals("ogv")) {
082 valid = false;
083
084 break;
085 }
086 }
087 }
088
089 if (!valid && _log.isWarnEnabled()) {
090 StringBundler sb = new StringBundler(5);
091
092 sb.append("Liferay is incorrectly configured to generate video ");
093 sb.append("previews using video containers other than MP4 or ");
094 sb.append("OGV. Please change the property ");
095 sb.append(PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS);
096 sb.append(" in portal-ext.properties.");
097
098 _log.warn(sb.toString());
099 }
100
101 FileUtil.mkdirs(PREVIEW_TMP_PATH);
102 FileUtil.mkdirs(THUMBNAIL_TMP_PATH);
103 }
104
105 @Override
106 public void generateVideo(
107 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
108 throws Exception {
109
110 _generateVideo(sourceFileVersion, destinationFileVersion);
111 }
112
113 @Override
114 public InputStream getPreviewAsStream(FileVersion fileVersion, String type)
115 throws Exception {
116
117 return doGetPreviewAsStream(fileVersion, type);
118 }
119
120 @Override
121 public long getPreviewFileSize(FileVersion fileVersion, String type)
122 throws Exception {
123
124 return doGetPreviewFileSize(fileVersion, type);
125 }
126
127 @Override
128 public InputStream getThumbnailAsStream(FileVersion fileVersion, int index)
129 throws Exception {
130
131 return doGetThumbnailAsStream(fileVersion, index);
132 }
133
134 @Override
135 public long getThumbnailFileSize(FileVersion fileVersion, int index)
136 throws Exception {
137
138 return doGetThumbnailFileSize(fileVersion, index);
139 }
140
141 @Override
142 public Set<String> getVideoMimeTypes() {
143 return _videoMimeTypes;
144 }
145
146 @Override
147 public boolean hasVideo(FileVersion fileVersion) {
148 boolean hasVideo = false;
149
150 try {
151 hasVideo = _hasVideo(fileVersion);
152
153 if (!hasVideo && isSupported(fileVersion)) {
154 _queueGeneration(null, fileVersion);
155 }
156 }
157 catch (Exception e) {
158 _log.error(e, e);
159 }
160
161 return hasVideo;
162 }
163
164 @Override
165 public boolean isSupported(String mimeType) {
166 if (Validator.isNull(mimeType)) {
167 return false;
168 }
169
170 try {
171 if (XugglerUtil.isEnabled()) {
172 return _videoMimeTypes.contains(mimeType);
173 }
174 }
175 catch (Exception e) {
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(),
336 file.getCanonicalPath(), thumbnailTempFile,
337 THUMBNAIL_TYPE, height, width,
338 PropsValues.
339 DL_FILE_ENTRY_THUMBNAIL_VIDEO_FRAME_PERCENTAGE);
340
341 Future<String> future = ProcessExecutor.execute(
342 ClassPathUtil.getPortalClassPath(), processCallable);
343
344 String processIdentity = String.valueOf(
345 fileVersion.getFileVersionId());
346
347 futures.put(processIdentity, future);
348
349 future.get();
350 }
351 else {
352 LiferayConverter liferayConverter =
353 new LiferayVideoThumbnailConverter(
354 file.getCanonicalPath(), thumbnailTempFile,
355 THUMBNAIL_TYPE, height, width,
356 PropsValues.
357 DL_FILE_ENTRY_THUMBNAIL_VIDEO_FRAME_PERCENTAGE);
358
359 liferayConverter.convert();
360 }
361 }
362 catch (CancellationException ce) {
363 if (_log.isInfoEnabled()) {
364 _log.info(
365 "Cancellation received for " +
366 fileVersion.getFileVersionId() + " " +
367 fileVersion.getTitle());
368 }
369 }
370 catch (Exception e) {
371 _log.error(e, e);
372 }
373
374 storeThumbnailImages(fileVersion, thumbnailTempFile);
375
376 if (_log.isInfoEnabled()) {
377 _log.info(
378 "Xuggler generated a thumbnail for " +
379 fileVersion.getTitle() + " in " + stopWatch.getTime() +
380 " ms");
381 }
382 }
383 catch (Exception e) {
384 throw new SystemException(e);
385 }
386 finally {
387 FileUtil.delete(thumbnailTempFile);
388 }
389 }
390
391 private void _generateVideo(
392 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
393 throws Exception {
394
395 if (!XugglerUtil.isEnabled() || _hasVideo(destinationFileVersion)) {
396 return;
397 }
398
399 InputStream inputStream = null;
400
401 File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
402
403 File videoTempFile = null;
404
405 try {
406 if (sourceFileVersion != null) {
407 copy(sourceFileVersion, destinationFileVersion);
408
409 return;
410 }
411
412 File file = null;
413
414 if (!hasPreviews(destinationFileVersion) ||
415 !hasThumbnails(destinationFileVersion)) {
416
417 if (destinationFileVersion instanceof LiferayFileVersion) {
418 try {
419 LiferayFileVersion liferayFileVersion =
420 (LiferayFileVersion)destinationFileVersion;
421
422 file = liferayFileVersion.getFile(false);
423 }
424 catch (UnsupportedOperationException uoe) {
425 }
426 }
427
428 if (file == null) {
429 inputStream = destinationFileVersion.getContentStream(
430 false);
431
432 videoTempFile = FileUtil.createTempFile(
433 destinationFileVersion.getExtension());
434
435 FileUtil.write(videoTempFile, inputStream);
436
437 file = videoTempFile;
438 }
439 }
440
441 if (!hasPreviews(destinationFileVersion)) {
442 String tempFileId = DLUtil.getTempFileId(
443 destinationFileVersion.getFileEntryId(),
444 destinationFileVersion.getVersion());
445
446 for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
447 previewTempFiles[i] = getPreviewTempFile(
448 tempFileId, _PREVIEW_TYPES[i]);
449 }
450
451 try {
452 _generateVideoXuggler(
453 destinationFileVersion, file, previewTempFiles,
454 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
455 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
456 }
457 catch (Exception e) {
458 _log.error(e, e);
459 }
460 }
461
462 if (!hasThumbnails(destinationFileVersion)) {
463 try {
464 _generateThumbnailXuggler(
465 destinationFileVersion, file,
466 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
467 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
468 }
469 catch (Exception e) {
470 _log.error(e, e);
471 }
472 }
473 }
474 catch (NoSuchFileEntryException nsfee) {
475 }
476 finally {
477 StreamUtil.cleanUp(inputStream);
478
479 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
480
481 for (int i = 0; i < previewTempFiles.length; i++) {
482 FileUtil.delete(previewTempFiles[i]);
483 }
484
485 FileUtil.delete(videoTempFile);
486 }
487 }
488
489 private void _generateVideoXuggler(
490 FileVersion fileVersion, File sourceFile, File destinationFile,
491 String containerType)
492 throws Exception {
493
494 if (hasPreview(fileVersion, containerType)) {
495 return;
496 }
497
498 StopWatch stopWatch = new StopWatch();
499
500 stopWatch.start();
501
502 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
503 ProcessCallable<String> processCallable =
504 new LiferayVideoProcessCallable(
505 ServerDetector.getServerId(),
506 PropsUtil.get(PropsKeys.LIFERAY_HOME),
507 Log4JUtil.getCustomLogSettings(),
508 sourceFile.getCanonicalPath(),
509 destinationFile.getCanonicalPath(), containerType,
510 PropsUtil.getProperties(
511 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
512 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
513
514 Future<String> future = ProcessExecutor.execute(
515 ClassPathUtil.getPortalClassPath(), processCallable);
516
517 String processIdentity = Long.toString(
518 fileVersion.getFileVersionId());
519
520 futures.put(processIdentity, future);
521
522 future.get();
523 }
524 else {
525 LiferayConverter liferayConverter = new LiferayVideoConverter(
526 sourceFile.getCanonicalPath(),
527 destinationFile.getCanonicalPath(), containerType,
528 PropsUtil.getProperties(
529 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
530 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
531
532 liferayConverter.convert();
533 }
534
535 addFileToStore(
536 fileVersion.getCompanyId(), PREVIEW_PATH,
537 getPreviewFilePath(fileVersion, containerType), destinationFile);
538
539 if (_log.isInfoEnabled()) {
540 _log.info(
541 "Xuggler generated a " + containerType + " preview video for " +
542 fileVersion.getTitle() + " in " + stopWatch.getTime() +
543 " ms");
544 }
545 }
546
547 private void _generateVideoXuggler(
548 FileVersion fileVersion, File sourceFile, File[] destinationFiles,
549 int height, int width) {
550
551 try {
552 for (int i = 0; i < destinationFiles.length; i++) {
553 _generateVideoXuggler(
554 fileVersion, sourceFile, destinationFiles[i],
555 _PREVIEW_TYPES[i]);
556 }
557 }
558 catch (CancellationException ce) {
559 if (_log.isInfoEnabled()) {
560 _log.info(
561 "Cancellation received for " +
562 fileVersion.getFileVersionId() + " " +
563 fileVersion.getTitle());
564 }
565 }
566 catch (Exception e) {
567 _log.error(e, e);
568 }
569 }
570
571 private boolean _hasVideo(FileVersion fileVersion) throws Exception {
572 if (!isSupported(fileVersion)) {
573 return false;
574 }
575
576 return hasPreviews(fileVersion) && hasThumbnails(fileVersion);
577 }
578
579 private void _queueGeneration(
580 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
581
582 if (_fileVersionIds.contains(
583 destinationFileVersion.getFileVersionId()) ||
584 !isSupported(destinationFileVersion)) {
585
586 return;
587 }
588
589 _fileVersionIds.add(destinationFileVersion.getFileVersionId());
590
591 sendGenerationMessage(
592 DestinationNames.DOCUMENT_LIBRARY_VIDEO_PROCESSOR,
593 sourceFileVersion, destinationFileVersion);
594 }
595
596 private static final String[] _PREVIEW_TYPES =
597 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS;
598
599 private static Log _log = LogFactoryUtil.getLog(VideoProcessorImpl.class);
600
601 private List<Long> _fileVersionIds = new Vector<Long>();
602 private Set<String> _videoMimeTypes = SetUtil.fromArray(
603 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_MIME_TYPES);
604
605 private static class LiferayVideoProcessCallable
606 implements ProcessCallable<String> {
607
608 public LiferayVideoProcessCallable(
609 String serverId, String liferayHome,
610 Map<String, String> customLogSettings, String inputURL,
611 String outputURL, String videoContainer, Properties videoProperties,
612 Properties ffpresetProperties) {
613
614 _serverId = serverId;
615 _liferayHome = liferayHome;
616 _customLogSettings = customLogSettings;
617 _inputURL = inputURL;
618 _outputURL = outputURL;
619 _videoContainer = videoContainer;
620 _videoProperties = videoProperties;
621 _ffpresetProperties = ffpresetProperties;
622 }
623
624 @Override
625 public String call() throws ProcessException {
626 Properties systemProperties = System.getProperties();
627
628 SystemEnv.setProperties(systemProperties);
629
630 Class<?> clazz = getClass();
631
632 ClassLoader classLoader = clazz.getClassLoader();
633
634 Log4JUtil.initLog4J(
635 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
636 _customLogSettings);
637
638 try {
639 LiferayConverter liferayConverter = new LiferayVideoConverter(
640 _inputURL, _outputURL, _videoContainer, _videoProperties,
641 _ffpresetProperties);
642
643 liferayConverter.convert();
644 }
645 catch (Exception e) {
646 throw new ProcessException(e);
647 }
648
649 return StringPool.BLANK;
650 }
651
652 private static final long serialVersionUID = 1L;
653
654 private Map<String, String> _customLogSettings;
655 private Properties _ffpresetProperties;
656 private String _inputURL;
657 private String _liferayHome;
658 private String _outputURL;
659 private String _serverId;
660 private String _videoContainer;
661 private Properties _videoProperties;
662
663 }
664
665 private static class LiferayVideoThumbnailProcessCallable
666 implements ProcessCallable<String> {
667
668 public LiferayVideoThumbnailProcessCallable(
669 String serverId, String liferayHome,
670 Map<String, String> customLogSettings, String inputURL,
671 File outputFile, String extension, int height, int width,
672 int percentage) {
673
674 _serverId = serverId;
675 _liferayHome = liferayHome;
676 _customLogSettings = customLogSettings;
677 _inputURL = inputURL;
678 _outputFile = outputFile;
679 _extension = extension;
680 _height = height;
681 _width = width;
682 _percentage = percentage;
683 }
684
685 @Override
686 public String call() throws ProcessException {
687 Class<?> clazz = getClass();
688
689 ClassLoader classLoader = clazz.getClassLoader();
690
691 Properties systemProperties = System.getProperties();
692 SystemEnv.setProperties(systemProperties);
693
694 Log4JUtil.initLog4J(
695 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
696 _customLogSettings);
697
698 try {
699 LiferayConverter liferayConverter =
700 new LiferayVideoThumbnailConverter(
701 _inputURL, _outputFile, _extension, _height, _width,
702 _percentage);
703
704 liferayConverter.convert();
705 }
706 catch (Exception e) {
707 throw new ProcessException(e);
708 }
709
710 return StringPool.BLANK;
711 }
712
713 private static final long serialVersionUID = 1L;
714
715 private Map<String, String> _customLogSettings;
716 private String _extension;
717 private int _height;
718 private String _inputURL;
719 private String _liferayHome;
720 private File _outputFile;
721 private int _percentage;
722 private String _serverId;
723 private int _width;
724
725 }
726
727 }