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