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