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.core.work.api.WorkManager.Scheduling;
044import org.nuxeo.ecm.platform.video.TranscodedVideo;
045import org.nuxeo.ecm.platform.video.Video;
046import org.nuxeo.ecm.platform.video.VideoConversionStatus;
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.getLocalService(WorkManager.class);
090        if (workManager != null && workManager.isStarted()) {
091            try {
092                workManager.shutdownQueue(
093                        workManager.getCategoryQueueId(VideoConversionWork.CATEGORY_VIDEO_CONVERSION), 10,
094                        TimeUnit.SECONDS);
095            } catch (InterruptedException e) {
096                // restore interrupted status
097                Thread.currentThread().interrupt();
098                throw new RuntimeException(e);
099            }
100        }
101        videoConversions = null;
102        automaticVideoConversions = null;
103    }
104
105    @Override
106    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
107        switch (extensionPoint) {
108            case VIDEO_CONVERSIONS_EP:
109                videoConversions.addContribution((VideoConversion) contribution);
110                break;
111            case DEFAULT_VIDEO_CONVERSIONS_EP:
112                automaticVideoConversions.addContribution((AutomaticVideoConversion) contribution);
113                break;
114            case CONFIGURATION_EP:
115                configuration = (Configuration) contribution;
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        }
133    }
134
135    @Override
136    public Collection<VideoConversion> getAvailableVideoConversions() {
137        return videoConversions.registry.values();
138    }
139
140    @Override
141    public void launchConversion(DocumentModel doc, String conversionName) {
142        WorkManager workManager = Framework.getLocalService(WorkManager.class);
143        if (workManager == null) {
144            throw new RuntimeException("No WorkManager available");
145        }
146        VideoConversionWork work = new VideoConversionWork(doc.getRepositoryName(), doc.getId(), conversionName);
147        workManager.schedule(work, Scheduling.IF_NOT_RUNNING_OR_SCHEDULED);
148    }
149
150    @Override
151    public void launchAutomaticConversions(DocumentModel doc) {
152        List<AutomaticVideoConversion> conversions = new ArrayList<>(automaticVideoConversions.registry.values());
153        Collections.sort(conversions);
154        for (AutomaticVideoConversion conversion : conversions) {
155            launchConversion(doc, conversion.getName());
156        }
157    }
158
159    @Override
160    public TranscodedVideo convert(Video originalVideo, String conversionName) {
161        if (!videoConversions.registry.containsKey(conversionName)) {
162            throw new NuxeoException(String.format("'%s' is not a registered video conversion.", conversionName));
163        }
164        BlobHolder blobHolder = new SimpleBlobHolder(originalVideo.getBlob());
165        VideoConversion conversion = videoConversions.registry.get(conversionName);
166        Map<String, Serializable> parameters = new HashMap<>();
167        parameters.put("height", conversion.getHeight());
168        parameters.put("videoInfo", originalVideo.getVideoInfo());
169        ConversionService conversionService = Framework.getLocalService(ConversionService.class);
170        BlobHolder result = conversionService.convert(conversion.getConverter(), blobHolder, parameters);
171        VideoInfo videoInfo = VideoHelper.getVideoInfo(result.getBlob());
172        return TranscodedVideo.fromBlobAndInfo(conversionName, result.getBlob(), videoInfo);
173    }
174
175    @Override
176    public VideoConversionStatus getProgressStatus(String repositoryName, String docId, String conversionName) {
177        WorkManager workManager = Framework.getLocalService(WorkManager.class);
178        Work work = new VideoConversionWork(repositoryName, docId, conversionName);
179        State state = workManager.getWorkState(work.getId());
180        if (state == null) { // DONE
181            return null;
182        } else if (state == State.SCHEDULED) {
183            String queueId = workManager.getCategoryQueueId(VideoConversionWork.CATEGORY_VIDEO_CONVERSION);
184            long queueSize = workManager.getQueueSize(queueId, State.SCHEDULED);
185            return new VideoConversionStatus(VideoConversionStatus.STATUS_CONVERSION_QUEUED, 0L, queueSize);
186        } else { // RUNNING
187            return new VideoConversionStatus(VideoConversionStatus.STATUS_CONVERSION_PENDING, 0L, 0L);
188        }
189    }
190
191    @Override
192    public VideoConversion getVideoConversion(String conversionName) {
193        return videoConversions.registry.get(conversionName);
194    }
195
196    @Override
197    public Configuration getConfiguration() {
198        return configuration;
199    }
200}