001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
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.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    /**
064     * @author Juan Gonz??lez
065     * @author Sergio Gonz??lez
066     * @author Mika Koivisto
067     */
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    }