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}