001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Thomas Roger
016 *     Florent Guillaume
017 */
018package org.nuxeo.ecm.platform.video.service;
019
020import static org.nuxeo.ecm.core.api.CoreSession.ALLOW_VERSION_WRITE;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.IdRef;
031import org.nuxeo.ecm.core.event.Event;
032import org.nuxeo.ecm.core.event.EventService;
033import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
034import org.nuxeo.ecm.core.work.AbstractWork;
035import org.nuxeo.ecm.core.work.api.WorkManager;
036import org.nuxeo.ecm.platform.video.TranscodedVideo;
037import org.nuxeo.ecm.platform.video.Video;
038import org.nuxeo.ecm.platform.video.VideoDocument;
039import org.nuxeo.runtime.api.Framework;
040
041/**
042 * Work running a defined video conversion.
043 *
044 * @since 5.6
045 */
046public class VideoConversionWork extends AbstractWork {
047
048    private static final long serialVersionUID = 1L;
049
050    private static final Log log = LogFactory.getLog(VideoConversionWork.class);
051
052    public static final String CATEGORY_VIDEO_CONVERSION = "videoConversion";
053
054    public static final String VIDEO_CONVERSIONS_DONE_EVENT = "videoConversionsDone";
055
056    protected final String conversionName;
057
058    protected static String computeIdPrefix(String repositoryName, String docId) {
059        return repositoryName + ':' + docId + ":videoconv:";
060    }
061
062    public VideoConversionWork(String repositoryName, String docId, String conversionName) {
063        super(computeIdPrefix(repositoryName, docId) + conversionName);
064        setDocument(repositoryName, docId);
065        this.conversionName = conversionName;
066    }
067
068    @Override
069    public String getCategory() {
070        return CATEGORY_VIDEO_CONVERSION;
071    }
072
073    @Override
074    public String getTitle() {
075        return "Video Conversion " + conversionName;
076    }
077
078    @Override
079    public void work() {
080        setStatus("Extracting");
081        setProgress(Progress.PROGRESS_INDETERMINATE);
082
083        Video originalVideo = null;
084        try {
085            initSession();
086            originalVideo = getVideoToConvert();
087            commitOrRollbackTransaction();
088        } finally {
089            cleanUp(true, null);
090        }
091
092        if (originalVideo == null) {
093            setStatus("Nothing to process");
094            return;
095        }
096
097        // Perform the actual conversion
098        VideoService service = Framework.getLocalService(VideoService.class);
099        setStatus("Transcoding");
100        TranscodedVideo transcodedVideo = service.convert(originalVideo, conversionName);
101
102        // Saving it to the document
103        startTransaction();
104        setStatus("Saving");
105        initSession();
106        DocumentModel doc = session.getDocument(new IdRef(docId));
107        saveNewTranscodedVideo(doc, transcodedVideo);
108        fireVideoConversionsDoneEvent(doc);
109        setStatus("Done");
110    }
111
112    protected Video getVideoToConvert() {
113        DocumentModel doc = session.getDocument(new IdRef(docId));
114        VideoDocument videoDocument = doc.getAdapter(VideoDocument.class);
115        Video video = videoDocument.getVideo();
116        if (video == null) {
117            log.warn("No original video to transcode for: " + doc);
118        }
119        return video;
120    }
121
122    protected void saveNewTranscodedVideo(DocumentModel doc, TranscodedVideo transcodedVideo) {
123        @SuppressWarnings("unchecked")
124        List<Map<String, Serializable>> transcodedVideos = (List<Map<String, Serializable>>) doc.getPropertyValue("vid:transcodedVideos");
125        if (transcodedVideos == null) {
126            transcodedVideos = new ArrayList<>();
127        }
128        transcodedVideos.add(transcodedVideo.toMap());
129        doc.setPropertyValue("vid:transcodedVideos", (Serializable) transcodedVideos);
130        if (doc.isVersion()) {
131            doc.putContextData(ALLOW_VERSION_WRITE, Boolean.TRUE);
132        }
133        session.saveDocument(doc);
134    }
135
136    /**
137     * Fire a {@code VIDEO_CONVERSIONS_DONE_EVENT} event when no other VideoConversionWork is scheduled for this
138     * document.
139     *
140     * @since 5.8
141     */
142    protected void fireVideoConversionsDoneEvent(DocumentModel doc) {
143        WorkManager workManager = Framework.getLocalService(WorkManager.class);
144        List<String> workIds = workManager.listWorkIds(CATEGORY_VIDEO_CONVERSION, null);
145        String idPrefix = computeIdPrefix(repositoryName, docId);
146        int worksCount = 0;
147        for (String workId : workIds) {
148            if (workId.startsWith(idPrefix)) {
149                if (++worksCount > 1) {
150                    // another work scheduled
151                    return;
152                }
153            }
154        }
155
156        DocumentEventContext ctx = new DocumentEventContext(session, session.getPrincipal(), doc);
157        Event event = ctx.newEvent(VIDEO_CONVERSIONS_DONE_EVENT);
158        Framework.getLocalService(EventService.class).fireEvent(event);
159    }
160
161}