001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
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    /**
065     * @author Juan González
066     * @author Sergio González
067     * @author Mika Koivisto
068     */
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    }