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