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