View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  package org.opencastproject.workflow.handler.crop;
22  
23  import org.opencastproject.crop.api.CropException;
24  import org.opencastproject.crop.api.CropService;
25  import org.opencastproject.job.api.Job;
26  import org.opencastproject.job.api.JobContext;
27  import org.opencastproject.mediapackage.MediaPackage;
28  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
29  import org.opencastproject.mediapackage.MediaPackageElementParser;
30  import org.opencastproject.mediapackage.MediaPackageException;
31  import org.opencastproject.mediapackage.Track;
32  import org.opencastproject.serviceregistry.api.ServiceRegistry;
33  import org.opencastproject.util.NotFoundException;
34  import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
35  import org.opencastproject.workflow.api.ConfiguredTagsAndFlavors;
36  import org.opencastproject.workflow.api.WorkflowInstance;
37  import org.opencastproject.workflow.api.WorkflowOperationException;
38  import org.opencastproject.workflow.api.WorkflowOperationHandler;
39  import org.opencastproject.workflow.api.WorkflowOperationInstance;
40  import org.opencastproject.workflow.api.WorkflowOperationResult;
41  import org.opencastproject.workspace.api.Workspace;
42  
43  import org.osgi.service.component.annotations.Component;
44  import org.osgi.service.component.annotations.Reference;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import java.io.IOException;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.HashMap;
52  import java.util.List;
53  import java.util.Map;
54  
55  /**
56   * The workflow definition will run recordings to crop them from 4:3 to 16:9.
57   */
58  @Component(
59      immediate = true,
60      service = WorkflowOperationHandler.class,
61      property = {
62          "service.description=Videosegmentation Workflow Operation Handler",
63          "workflow.operation=crop-video"
64      }
65  )
66  public class CropWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
67  
68    /** The logging facility */
69    private static final Logger logger = LoggerFactory.getLogger(CropWorkflowOperationHandler.class);
70  
71    /** Name of the configuration key that specifies the flavor of the track to analyze */
72    private static final String PROP_SOURCE_FLAVOR = "source-flavor";
73  
74    private static final String PROP_TARGET_FLAVOR = "target-flavor";
75  
76    /** Name of the configuration key that specifies the flavor of the track to analyze */
77    private static final String PROP_TARGET_TAGS = "target-tags";
78  
79    /** The composer service */
80    private CropService cropService = null;
81  
82    /** The local workspace */
83    private Workspace workspace = null;
84  
85    /**
86     * {@inheritDoc}
87     *
88     * @see org.opencastproject.workflow.api.WorkflowOperationHandler#start(
89     *        org.opencastproject.workflow.api.WorkflowInstance, JobContext)
90     */
91    @Override
92    public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext jobContext)
93            throws WorkflowOperationException {
94  
95      WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
96      MediaPackage mediaPackage = workflowInstance.getMediaPackage();
97  
98      logger.info("Start cropping workflow operation for mediapackage {}", mediaPackage.getIdentifier().toString());
99  
100     // Check which tags have been configured
101     ConfiguredTagsAndFlavors tagsAndFlavors = getTagsAndFlavors(workflowInstance,
102         Configuration.none, Configuration.one, Configuration.many, Configuration.many);
103     ConfiguredTagsAndFlavors.TargetTags targetTags = tagsAndFlavors.getTargetTags();
104     List<MediaPackageElementFlavor> targetFlavorOption = tagsAndFlavors.getTargetFlavors();
105 
106     MediaPackageElementFlavor targetFlavor = null;
107     if (!targetFlavorOption.isEmpty()) {
108       targetFlavor = targetFlavorOption.get(0);
109     }
110     MediaPackageElementFlavor trackFlavor = tagsAndFlavors.getSingleSrcFlavor();
111 
112     List<Track> candidates = new ArrayList<>();
113     candidates.addAll(Arrays.asList(mediaPackage.getTracks(trackFlavor)));
114     candidates.removeIf(t -> !t.hasVideo());
115 
116     if (candidates.size() == 0) {
117       logger.info("No matching tracks available for cropping in workflow {}", workflowInstance);
118       return createResult(WorkflowOperationResult.Action.CONTINUE);
119     } else if (candidates.size() > 1) {
120       logger.info("Found more than one track to crop");
121     }
122 
123     // start cropping all candidates in parallel
124     Map<Job, Track> jobs = new HashMap<Job, Track>();
125     for (Track candidate : candidates) {
126       try {
127         jobs.put(cropService.crop(candidate), candidate);
128       } catch (MediaPackageException | CropException e) {
129         throw new WorkflowOperationException("Failed starting crop job", e);
130       }
131     }
132 
133     // wait for all crop jobs to be finished
134     if (!waitForStatus(jobs.keySet().toArray(new Job[0])).isSuccess()) {
135       throw new WorkflowOperationException("Crop operation failed");
136     }
137 
138     long totalTimeInQueue = 0;
139     // add new tracks to media package
140 
141     for (Map.Entry<Job, Track> entry : jobs.entrySet()) {
142       Job job = entry.getKey();
143       Track track = entry.getValue();
144       // deserialize track
145       Track croppedTrack;
146       try {
147         croppedTrack = (Track) MediaPackageElementParser.getFromXml(job.getPayload());
148       } catch (MediaPackageException e) {
149         throw new WorkflowOperationException(String.format("Crop service yielded invalid track: %s", job.getPayload()));
150       }
151 
152       // update identifier
153       croppedTrack.generateIdentifier();
154 
155       // move into space for media package in ws/wfr
156       try {
157         String filename = "cropped_" + croppedTrack.getURI().toString();
158         croppedTrack.setURI(workspace
159                 .moveTo(croppedTrack.getURI(), mediaPackage.getIdentifier().toString(), croppedTrack.getIdentifier(),
160                         filename));
161       } catch (NotFoundException | IOException e) {
162         throw new WorkflowOperationException(
163                 String.format("Could not move %s to media package %s", croppedTrack.getURI(),
164                         mediaPackage.getIdentifier()));
165       }
166 
167       // Add target tags
168       applyTargetTagsToElement(targetTags, croppedTrack);
169       croppedTrack.setFlavor(targetFlavor);
170 
171       // add new track to mediapackage
172       mediaPackage.addDerived(croppedTrack, track);
173 
174       totalTimeInQueue += job.getQueueTime() == null ? 0 : job.getQueueTime();
175     }
176     logger.info("Video cropping completed");
177     return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE, totalTimeInQueue);
178   }
179 
180   /**
181    * Callback for declarative services configuration that will introduce us to the crop service.
182    * Implementation assumes that the reference is configured as being static.
183    *
184    * @param cropService
185    *          the crop service
186    */
187   @Reference
188   protected void setCropService(CropService cropService) {
189     this.cropService = cropService;
190   }
191 
192   /**
193    * Callback for declarative services configuration that will introduce us to the local workspace service.
194    * Implementation assumes that the reference is configured as being static.
195    *
196    * @param workspace
197    *          an instance of the workspace
198    */
199   @Reference
200   protected void setWorkspace(Workspace workspace) {
201     this.workspace = workspace;
202   }
203 
204   @Reference
205   @Override
206   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
207     super.setServiceRegistry(serviceRegistry);
208   }
209 
210 }