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 storeThumbnailmage(
309 fileVersion, renderedImage, THUMBNAIL_INDEX_CUSTOM_1);
310 storeThumbnailmage(
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 = null;
320
321 if (_log.isInfoEnabled()) {
322 stopWatch = new StopWatch();
323
324 stopWatch.start();
325 }
326
327 String tempFileId = DLUtil.getTempFileId(
328 fileVersion.getFileEntryId(), fileVersion.getVersion());
329
330 File thumbnailTempFile = getThumbnailTempFile(tempFileId);
331
332 try {
333 try {
334 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
335 ProcessCallable<String> processCallable =
336 new LiferayVideoThumbnailProcessCallable(
337 ServerDetector.getServerId(),
338 PropsUtil.get(PropsKeys.LIFERAY_HOME),
339 Log4JUtil.getCustomLogSettings(),
340 file.getCanonicalPath(), thumbnailTempFile,
341 THUMBNAIL_TYPE, height, width,
342 PropsValues.
343 DL_FILE_ENTRY_THUMBNAIL_VIDEO_FRAME_PERCENTAGE);
344
345 Future<String> future = ProcessExecutor.execute(
346 ClassPathUtil.getPortalClassPath(), processCallable);
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);
384 }
385 }
386 catch (Exception e) {
387 throw new SystemException(e);
388 }
389 finally {
390 FileUtil.delete(thumbnailTempFile);
391 }
392 }
393
394 private void _generateVideo(
395 FileVersion sourceFileVersion, FileVersion destinationFileVersion)
396 throws Exception {
397
398 if (!XugglerUtil.isEnabled() || _hasVideo(destinationFileVersion)) {
399 return;
400 }
401
402 InputStream inputStream = null;
403
404 File[] previewTempFiles = new File[_PREVIEW_TYPES.length];
405
406 File videoTempFile = null;
407
408 try {
409 if (sourceFileVersion != null) {
410 copy(sourceFileVersion, destinationFileVersion);
411
412 return;
413 }
414
415 File file = null;
416
417 if (!hasPreviews(destinationFileVersion) ||
418 !hasThumbnails(destinationFileVersion)) {
419
420 if (destinationFileVersion instanceof LiferayFileVersion) {
421 try {
422 LiferayFileVersion liferayFileVersion =
423 (LiferayFileVersion)destinationFileVersion;
424
425 file = liferayFileVersion.getFile(false);
426 }
427 catch (UnsupportedOperationException uoe) {
428 }
429 }
430
431 if (file == null) {
432 inputStream = destinationFileVersion.getContentStream(
433 false);
434
435 videoTempFile = FileUtil.createTempFile(
436 destinationFileVersion.getExtension());
437
438 FileUtil.write(videoTempFile, inputStream);
439
440 file = videoTempFile;
441 }
442 }
443
444 if (!hasPreviews(destinationFileVersion)) {
445 String tempFileId = DLUtil.getTempFileId(
446 destinationFileVersion.getFileEntryId(),
447 destinationFileVersion.getVersion());
448
449 for (int i = 0; i < _PREVIEW_TYPES.length; i++) {
450 previewTempFiles[i] = getPreviewTempFile(
451 tempFileId, _PREVIEW_TYPES[i]);
452 }
453
454 try {
455 _generateVideoXuggler(
456 destinationFileVersion, file, previewTempFiles,
457 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
458 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
459 }
460 catch (Exception e) {
461 _log.error(e, e);
462 }
463 }
464
465 if (!hasThumbnails(destinationFileVersion)) {
466 try {
467 _generateThumbnailXuggler(
468 destinationFileVersion, file,
469 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT,
470 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH);
471 }
472 catch (Exception e) {
473 _log.error(e, e);
474 }
475 }
476 }
477 catch (NoSuchFileEntryException nsfee) {
478 }
479 finally {
480 StreamUtil.cleanUp(inputStream);
481
482 _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
483
484 for (int i = 0; i < previewTempFiles.length; i++) {
485 FileUtil.delete(previewTempFiles[i]);
486 }
487
488 FileUtil.delete(videoTempFile);
489 }
490 }
491
492 private void _generateVideoXuggler(
493 FileVersion fileVersion, File sourceFile, File destinationFile,
494 String containerType)
495 throws Exception {
496
497 if (hasPreview(fileVersion, containerType)) {
498 return;
499 }
500
501 StopWatch stopWatch = null;
502
503 if (_log.isInfoEnabled()) {
504 stopWatch = new StopWatch();
505
506 stopWatch.start();
507 }
508
509 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
510 ProcessCallable<String> processCallable =
511 new LiferayVideoProcessCallable(
512 ServerDetector.getServerId(),
513 PropsUtil.get(PropsKeys.LIFERAY_HOME),
514 Log4JUtil.getCustomLogSettings(),
515 sourceFile.getCanonicalPath(),
516 destinationFile.getCanonicalPath(), containerType,
517 PropsUtil.getProperties(
518 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
519 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
520
521 Future<String> future = ProcessExecutor.execute(
522 ClassPathUtil.getPortalClassPath(), processCallable);
523
524 String processIdentity = Long.toString(
525 fileVersion.getFileVersionId());
526
527 futures.put(processIdentity, future);
528
529 future.get();
530 }
531 else {
532 LiferayConverter liferayConverter = new LiferayVideoConverter(
533 sourceFile.getCanonicalPath(),
534 destinationFile.getCanonicalPath(), containerType,
535 PropsUtil.getProperties(
536 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO, false),
537 PropsUtil.getProperties(PropsKeys.XUGGLER_FFPRESET, true));
538
539 liferayConverter.convert();
540 }
541
542 addFileToStore(
543 fileVersion.getCompanyId(), PREVIEW_PATH,
544 getPreviewFilePath(fileVersion, containerType), destinationFile);
545
546 if (_log.isInfoEnabled()) {
547 _log.info(
548 "Xuggler generated a " + containerType + " preview video for " +
549 fileVersion.getTitle() + " in " + stopWatch);
550 }
551 }
552
553 private void _generateVideoXuggler(
554 FileVersion fileVersion, File sourceFile, File[] destinationFiles,
555 int height, int width) {
556
557 try {
558 for (int i = 0; i < destinationFiles.length; i++) {
559 _generateVideoXuggler(
560 fileVersion, sourceFile, destinationFiles[i],
561 _PREVIEW_TYPES[i]);
562 }
563 }
564 catch (CancellationException ce) {
565 if (_log.isInfoEnabled()) {
566 _log.info(
567 "Cancellation received for " +
568 fileVersion.getFileVersionId() + " " +
569 fileVersion.getTitle());
570 }
571 }
572 catch (Exception e) {
573 _log.error(e, e);
574 }
575 }
576
577 private boolean _hasVideo(FileVersion fileVersion) throws Exception {
578 if (!isSupported(fileVersion)) {
579 return false;
580 }
581
582 return hasPreviews(fileVersion) && hasThumbnails(fileVersion);
583 }
584
585 private void _queueGeneration(
586 FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
587
588 if (_fileVersionIds.contains(
589 destinationFileVersion.getFileVersionId()) ||
590 !isSupported(destinationFileVersion)) {
591
592 return;
593 }
594
595 _fileVersionIds.add(destinationFileVersion.getFileVersionId());
596
597 sendGenerationMessage(
598 DestinationNames.DOCUMENT_LIBRARY_VIDEO_PROCESSOR,
599 sourceFileVersion, destinationFileVersion);
600 }
601
602 private static final String[] _PREVIEW_TYPES =
603 PropsValues.DL_FILE_ENTRY_PREVIEW_VIDEO_CONTAINERS;
604
605 private static Log _log = LogFactoryUtil.getLog(VideoProcessorImpl.class);
606
607 private List<Long> _fileVersionIds = new Vector<Long>();
608 private 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, String inputURL,
617 String outputURL, String videoContainer, Properties videoProperties,
618 Properties ffpresetProperties) {
619
620 _serverId = serverId;
621 _liferayHome = liferayHome;
622 _customLogSettings = customLogSettings;
623 _inputURL = inputURL;
624 _outputURL = outputURL;
625 _videoContainer = videoContainer;
626 _videoProperties = videoProperties;
627 _ffpresetProperties = ffpresetProperties;
628 }
629
630 @Override
631 public String call() throws ProcessException {
632 Properties systemProperties = System.getProperties();
633
634 SystemEnv.setProperties(systemProperties);
635
636 Class<?> clazz = getClass();
637
638 ClassLoader classLoader = clazz.getClassLoader();
639
640 Log4JUtil.initLog4J(
641 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
642 _customLogSettings);
643
644 try {
645 LiferayConverter liferayConverter = new LiferayVideoConverter(
646 _inputURL, _outputURL, _videoContainer, _videoProperties,
647 _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 Properties _ffpresetProperties;
662 private String _inputURL;
663 private String _liferayHome;
664 private String _outputURL;
665 private String _serverId;
666 private String _videoContainer;
667 private Properties _videoProperties;
668
669 }
670
671 private static class LiferayVideoThumbnailProcessCallable
672 implements ProcessCallable<String> {
673
674 public LiferayVideoThumbnailProcessCallable(
675 String serverId, String liferayHome,
676 Map<String, String> customLogSettings, String inputURL,
677 File outputFile, String extension, int height, int width,
678 int percentage) {
679
680 _serverId = serverId;
681 _liferayHome = liferayHome;
682 _customLogSettings = customLogSettings;
683 _inputURL = inputURL;
684 _outputFile = outputFile;
685 _extension = extension;
686 _height = height;
687 _width = width;
688 _percentage = percentage;
689 }
690
691 @Override
692 public String call() throws ProcessException {
693 Class<?> clazz = getClass();
694
695 ClassLoader classLoader = clazz.getClassLoader();
696
697 Properties systemProperties = System.getProperties();
698 SystemEnv.setProperties(systemProperties);
699
700 Log4JUtil.initLog4J(
701 _serverId, _liferayHome, classLoader, new Log4jLogFactoryImpl(),
702 _customLogSettings);
703
704 try {
705 LiferayConverter liferayConverter =
706 new LiferayVideoThumbnailConverter(
707 _inputURL, _outputFile, _extension, _height, _width,
708 _percentage);
709
710 liferayConverter.convert();
711 }
712 catch (Exception e) {
713 throw new ProcessException(e);
714 }
715
716 return StringPool.BLANK;
717 }
718
719 private static final long serialVersionUID = 1L;
720
721 private Map<String, String> _customLogSettings;
722 private String _extension;
723 private int _height;
724 private String _inputURL;
725 private String _liferayHome;
726 private File _outputFile;
727 private int _percentage;
728 private String _serverId;
729 private int _width;
730
731 }
732
733 }