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