001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Thomas Roger
016 *     Florent Guillaume
017 */
018package org.nuxeo.ecm.platform.video.service;
019
020import java.io.Serializable;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.TimeUnit;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031import org.nuxeo.ecm.core.api.DocumentLocation;
032import org.nuxeo.ecm.core.api.DocumentModel;
033import org.nuxeo.ecm.core.api.NuxeoException;
034import org.nuxeo.ecm.core.api.blobholder.BlobHolder;
035import org.nuxeo.ecm.core.api.blobholder.SimpleBlobHolder;
036import org.nuxeo.ecm.core.convert.api.ConversionService;
037import org.nuxeo.ecm.core.work.api.Work;
038import org.nuxeo.ecm.core.work.api.Work.State;
039import org.nuxeo.ecm.core.work.api.WorkManager;
040import org.nuxeo.ecm.core.work.api.WorkManager.Scheduling;
041import org.nuxeo.ecm.platform.video.TranscodedVideo;
042import org.nuxeo.ecm.platform.video.Video;
043import org.nuxeo.ecm.platform.video.VideoConversionStatus;
044import org.nuxeo.ecm.platform.video.VideoHelper;
045import org.nuxeo.ecm.platform.video.VideoInfo;
046import org.nuxeo.runtime.api.Framework;
047import org.nuxeo.runtime.model.ComponentContext;
048import org.nuxeo.runtime.model.ComponentInstance;
049import org.nuxeo.runtime.model.DefaultComponent;
050
051import static org.nuxeo.ecm.platform.video.service.Configuration.DEFAULT_CONFIGURATION;
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.getLocalService(WorkManager.class);
089        if (workManager != null && workManager.isStarted()) {
090            try {
091                workManager.shutdownQueue(
092                        workManager.getCategoryQueueId(VideoConversionWork.CATEGORY_VIDEO_CONVERSION), 10,
093                        TimeUnit.SECONDS);
094            } catch (InterruptedException e) {
095                // restore interrupted status
096                Thread.currentThread().interrupt();
097                throw new RuntimeException(e);
098            }
099        }
100        videoConversions = null;
101        automaticVideoConversions = null;
102    }
103
104    @Override
105    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
106        switch (extensionPoint) {
107            case VIDEO_CONVERSIONS_EP:
108                videoConversions.addContribution((VideoConversion) contribution);
109                break;
110            case DEFAULT_VIDEO_CONVERSIONS_EP:
111                automaticVideoConversions.addContribution((AutomaticVideoConversion) contribution);
112                break;
113            case CONFIGURATION_EP:
114                configuration = (Configuration) contribution;
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        }
132    }
133
134    @Override
135    public Collection<VideoConversion> getAvailableVideoConversions() {
136        return videoConversions.registry.values();
137    }
138
139    @Override
140    public void launchConversion(DocumentModel doc, String conversionName) {
141        WorkManager workManager = Framework.getLocalService(WorkManager.class);
142        if (workManager == null) {
143            throw new RuntimeException("No WorkManager available");
144        }
145        VideoConversionWork work = new VideoConversionWork(doc.getRepositoryName(), doc.getId(), conversionName);
146        workManager.schedule(work, Scheduling.IF_NOT_RUNNING_OR_SCHEDULED);
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    @Deprecated
160    public TranscodedVideo convert(VideoConversionId id, Video originalVideo, String conversionName) {
161        return convert(originalVideo, conversionName);
162    }
163
164    @Override
165    public TranscodedVideo convert(Video originalVideo, String conversionName) {
166        if (!videoConversions.registry.containsKey(conversionName)) {
167            throw new NuxeoException(String.format("'%s' is not a registered video conversion.", conversionName));
168        }
169        BlobHolder blobHolder = new SimpleBlobHolder(originalVideo.getBlob());
170        VideoConversion conversion = videoConversions.registry.get(conversionName);
171        Map<String, Serializable> parameters = new HashMap<>();
172        parameters.put("height", conversion.getHeight());
173        parameters.put("videoInfo", originalVideo.getVideoInfo());
174        ConversionService conversionService = Framework.getLocalService(ConversionService.class);
175        BlobHolder result = conversionService.convert(conversion.getConverter(), blobHolder, parameters);
176        VideoInfo videoInfo = VideoHelper.getVideoInfo(result.getBlob());
177        return TranscodedVideo.fromBlobAndInfo(conversionName, result.getBlob(), videoInfo);
178    }
179
180    @Override
181    @Deprecated
182    public VideoConversionStatus getProgressStatus(VideoConversionId id) {
183        DocumentLocation loc = id.getDocumentLocation();
184        return getProgressStatus(loc.getServerName(), loc.getIdRef().value, id.getConversionName());
185    }
186
187    @Override
188    public VideoConversionStatus getProgressStatus(String repositoryName, String docId, String conversionName) {
189        WorkManager workManager = Framework.getLocalService(WorkManager.class);
190        Work work = new VideoConversionWork(repositoryName, docId, conversionName);
191        State state = workManager.getWorkState(work.getId());
192        if (state == null || state == State.COMPLETED) {
193            return null;
194        } else if (state == State.SCHEDULED) {
195            String queueId = workManager.getCategoryQueueId(VideoConversionWork.CATEGORY_VIDEO_CONVERSION);
196            int queueSize = workManager.getQueueSize(queueId, State.SCHEDULED);
197            return new VideoConversionStatus(VideoConversionStatus.STATUS_CONVERSION_QUEUED, 0, queueSize);
198        } else { // RUNNING
199            return new VideoConversionStatus(VideoConversionStatus.STATUS_CONVERSION_PENDING, 0, 0);
200        }
201    }
202
203    @Override
204    public VideoConversion getVideoConversion(String conversionName) {
205        return videoConversions.registry.get(conversionName);
206    }
207
208    @Override
209    public Configuration getConfiguration() {
210        return configuration;
211    }
212}