001/*
002 * (C) Copyright 2006-2014 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.platform.video.service.Configuration.DEFAULT_CONFIGURATION;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031import java.util.concurrent.TimeUnit;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.api.DocumentModel;
036import org.nuxeo.ecm.core.api.NuxeoException;
037import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
038import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder;
039import org.nuxeo.ecm.core.convert.api.ConversionService;
040import org.nuxeo.ecm.core.work.api.Work;
041import org.nuxeo.ecm.core.work.api.Work.State;
042import org.nuxeo.ecm.core.work.api.WorkManager;
043import org.nuxeo.ecm.platform.video.TranscodedVideo;
044import org.nuxeo.ecm.platform.video.Video;
045import org.nuxeo.ecm.platform.video.VideoConversionStatus;
046import org.nuxeo.ecm.platform.video.VideoDocument;
047import org.nuxeo.ecm.platform.video.VideoHelper;
048import org.nuxeo.ecm.platform.video.VideoInfo;
049import org.nuxeo.runtime.api.Framework;
050import org.nuxeo.runtime.model.ComponentContext;
051import org.nuxeo.runtime.model.ComponentInstance;
052import org.nuxeo.runtime.model.DefaultComponent;
053
054/**
055 * Default implementation of {@link VideoService}.
056 *
057 * @since 5.5
058 */
059public class VideoServiceImpl extends DefaultComponent implements VideoService {
060
061    protected static final Log log = LogFactory.getLog(VideoServiceImpl.class);
062
063    public static final String VIDEO_CONVERSIONS_EP = "videoConversions";
064
065    public static final String DEFAULT_VIDEO_CONVERSIONS_EP = "automaticVideoConversions";
066
067    /**
068     * @since 7.4
069     */
070    public static final String CONFIGURATION_EP = "configuration";
071
072    protected VideoConversionContributionHandler videoConversions;
073
074    protected AutomaticVideoConversionContributionHandler automaticVideoConversions;
075
076    /**
077     * @since 7.4
078     */
079    protected Configuration configuration = DEFAULT_CONFIGURATION;
080
081    @Override
082    public void activate(ComponentContext context) {
083        videoConversions = new VideoConversionContributionHandler();
084        automaticVideoConversions = new AutomaticVideoConversionContributionHandler();
085    }
086
087    @Override
088    public void deactivate(ComponentContext context) {
089        WorkManager workManager = Framework.getService(WorkManager.class);
090        if (workManager != null && workManager.isStarted()) {
091            try {
092                workManager.shutdownQueue(workManager.getCategoryQueueId(VideoConversionWork.CATEGORY_VIDEO_CONVERSION),
093                        10, TimeUnit.SECONDS);
094            } catch (InterruptedException e) {
095                Thread.currentThread().interrupt();
096                throw new NuxeoException(e);
097            }
098        }
099        videoConversions = null;
100        automaticVideoConversions = null;
101    }
102
103    @Override
104    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
105        switch (extensionPoint) {
106        case VIDEO_CONVERSIONS_EP:
107            videoConversions.addContribution((VideoConversion) contribution);
108            break;
109        case DEFAULT_VIDEO_CONVERSIONS_EP:
110            automaticVideoConversions.addContribution((AutomaticVideoConversion) contribution);
111            break;
112        case CONFIGURATION_EP:
113            configuration = (Configuration) contribution;
114            break;
115        default:
116            break;
117        }
118    }
119
120    @Override
121    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
122        switch (extensionPoint) {
123        case VIDEO_CONVERSIONS_EP:
124            videoConversions.removeContribution((VideoConversion) contribution);
125            break;
126        case DEFAULT_VIDEO_CONVERSIONS_EP:
127            automaticVideoConversions.removeContribution((AutomaticVideoConversion) contribution);
128            break;
129        case CONFIGURATION_EP:
130            configuration = DEFAULT_CONFIGURATION;
131            break;
132        default:
133            break;
134        }
135    }
136
137    @Override
138    public Collection<VideoConversion> getAvailableVideoConversions() {
139        return videoConversions.registry.values();
140    }
141
142    @Override
143    public void launchConversion(DocumentModel doc, String conversionName) {
144        WorkManager workManager = Framework.getService(WorkManager.class);
145        VideoConversionWork work = new VideoConversionWork(doc.getRepositoryName(), doc.getId(), conversionName);
146        log.debug(String.format("Scheduling work: %s conversion of Video document %s.", conversionName, doc));
147        workManager.schedule(work, true);
148    }
149
150    @Override
151    public void launchAutomaticConversions(DocumentModel doc, boolean onlyMissing) {
152        List<AutomaticVideoConversion> conversions = new ArrayList<>(automaticVideoConversions.registry.values());
153        Collections.sort(conversions);
154        VideoDocument videoDocument = doc.getAdapter(VideoDocument.class);
155        for (AutomaticVideoConversion conversion : conversions) {
156            if (!onlyMissing || videoDocument.getTranscodedVideo(conversion.getName()) == null) {
157                launchConversion(doc, conversion.getName());
158            }
159        }
160    }
161
162    @Override
163    public TranscodedVideo convert(Video originalVideo, String conversionName) {
164        if (!videoConversions.registry.containsKey(conversionName)) {
165            throw new NuxeoException(String.format("'%s' is not a registered video conversion.", conversionName));
166        }
167        BlobHolder blobHolder = new SimpleBlobHolder(originalVideo.getBlob());
168        VideoConversion conversion = videoConversions.registry.get(conversionName);
169        Map<String, Serializable> parameters = new HashMap<>();
170        parameters.put("height", conversion.getHeight());
171        parameters.put("videoInfo", originalVideo.getVideoInfo());
172        ConversionService conversionService = Framework.getService(ConversionService.class);
173        BlobHolder result = conversionService.convert(conversion.getConverter(), blobHolder, parameters);
174        VideoInfo videoInfo = VideoHelper.getVideoInfo(result.getBlob());
175        return TranscodedVideo.fromBlobAndInfo(conversionName, result.getBlob(), videoInfo);
176    }
177
178    @Override
179    public VideoConversionStatus getProgressStatus(String repositoryName, String docId, String conversionName) {
180        WorkManager workManager = Framework.getService(WorkManager.class);
181        Work work = new VideoConversionWork(repositoryName, docId, conversionName);
182        State state = workManager.getWorkState(work.getId());
183        if (state == null) { // DONE
184            return null;
185        } else if (state == State.SCHEDULED) {
186            String queueId = workManager.getCategoryQueueId(VideoConversionWork.CATEGORY_VIDEO_CONVERSION);
187            long queueSize = workManager.getQueueSize(queueId, State.SCHEDULED);
188            return new VideoConversionStatus(VideoConversionStatus.STATUS_CONVERSION_QUEUED, 0L, queueSize);
189        } else { // RUNNING
190            return new VideoConversionStatus(VideoConversionStatus.STATUS_CONVERSION_PENDING, 0L, 0L);
191        }
192    }
193
194    @Override
195    public VideoConversion getVideoConversion(String conversionName) {
196        return videoConversions.registry.get(conversionName);
197    }
198
199    @Override
200    public Configuration getConfiguration() {
201        return configuration;
202    }
203}