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