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