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