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