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