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.transcription.workflowoperation;
22  
23  import org.opencastproject.job.api.Job;
24  import org.opencastproject.job.api.JobContext;
25  import org.opencastproject.mediapackage.MediaPackage;
26  import org.opencastproject.mediapackage.MediaPackageElement;
27  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
28  import org.opencastproject.mediapackage.Track;
29  import org.opencastproject.mediapackage.selector.AbstractMediaPackageElementSelector;
30  import org.opencastproject.mediapackage.selector.TrackSelector;
31  import org.opencastproject.serviceregistry.api.ServiceRegistry;
32  import org.opencastproject.transcription.api.TranscriptionService;
33  import org.opencastproject.transcription.api.TranscriptionServiceException;
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.workflow.api.WorkflowOperationResult.Action;
42  
43  import org.apache.commons.lang3.StringUtils;
44  import org.osgi.service.component.ComponentContext;
45  import org.osgi.service.component.annotations.Activate;
46  import org.osgi.service.component.annotations.Component;
47  import org.osgi.service.component.annotations.Reference;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  import java.util.Collection;
52  import java.util.List;
53  
54  @Component(
55      immediate = true,
56      service = WorkflowOperationHandler.class,
57      property = {
58          "service.description=Start Transcription Workflow Operation Handler (Amberscript)",
59          "workflow.operation=amberscript-start-transcription"
60      }
61  )
62  public class AmberscriptStartTranscriptionOperationHandler extends AbstractWorkflowOperationHandler {
63  
64    private static final Logger logger = LoggerFactory.getLogger(AmberscriptStartTranscriptionOperationHandler.class);
65  
66    /** Workflow configuration option keys */
67    static final String LANGUAGE = "language";
68    static final String JOBTYPE = "jobtype";
69    static final String SPEAKER = "speaker";
70    static final String TRANSCRIPTIONTYPE = "transcriptiontype";
71    static final String GLOSSARY = "glossary";
72    static final String TRANSCRIPTIONSTYLE = "transcriptionstyle";
73    static final String TARGETLANGUAGE = "targetlanguage";
74    static final String SKIP_IF_FLAVOR_EXISTS = "skip-if-flavor-exists";
75  
76    /** The transcription service */
77    private TranscriptionService service = null;
78  
79    @Override
80    @Activate
81    protected void activate(ComponentContext cc) {
82      super.activate(cc);
83    }
84  
85    @Override
86    public WorkflowOperationResult start(final WorkflowInstance workflowInstance, JobContext context)
87            throws WorkflowOperationException {
88      MediaPackage mediaPackage = workflowInstance.getMediaPackage();
89      WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
90  
91      String skipOption = StringUtils.trimToNull(operation.getConfiguration(SKIP_IF_FLAVOR_EXISTS));
92      if (skipOption != null) {
93        MediaPackageElement[] mpes = mediaPackage.getElementsByFlavor(MediaPackageElementFlavor.parseFlavor(skipOption));
94        if (mpes != null && mpes.length > 0) {
95          logger.info(
96              "Start transcription operation will be skipped because flavor '{}' already exists in the media package.",
97              skipOption);
98          return createResult(Action.SKIP);
99        }
100     }
101 
102     logger.debug("Start transcription for mediapackage '{}'.", mediaPackage);
103 
104     // Check which tags have been configured
105     ConfiguredTagsAndFlavors tagsAndFlavors = getTagsAndFlavors(
106         workflowInstance, Configuration.many, Configuration.many, Configuration.none, Configuration.none);
107     List<String> sourceTagOption = tagsAndFlavors.getSrcTags();
108     List<MediaPackageElementFlavor> sourceFlavorOption = tagsAndFlavors.getSrcFlavors();
109     String language = StringUtils.trimToEmpty(operation.getConfiguration(LANGUAGE));
110     String jobType = StringUtils.trimToEmpty(operation.getConfiguration(JOBTYPE));
111     String speaker = StringUtils.trimToEmpty(operation.getConfiguration(SPEAKER));
112     String transcriptionType = StringUtils.trimToEmpty(operation.getConfiguration(TRANSCRIPTIONTYPE));
113     // Note that specifying `""` is different from not specifying a glossary at all!
114     // The former will override the default with not using a glossary for this operation,
115     // while the latter will fall back to said default. Hence, no `trimToEmpty` here.
116     String glossary = StringUtils.trim(operation.getConfiguration(GLOSSARY));
117     String transcriptionStyle = StringUtils.trimToEmpty(operation.getConfiguration(TRANSCRIPTIONSTYLE));
118     // See glossary comment above
119     String targetLanguage = StringUtils.trim(operation.getConfiguration(TARGETLANGUAGE));
120 
121     AbstractMediaPackageElementSelector<Track> elementSelector = new TrackSelector();
122 
123     // Make sure either one of tags or flavors are provided
124     if (sourceTagOption.isEmpty() && sourceFlavorOption.isEmpty()) {
125       throw new WorkflowOperationException("No source tag or flavor have been specified!");
126     }
127 
128     if (!sourceFlavorOption.isEmpty()) {
129       elementSelector.addFlavor(sourceFlavorOption.get(0));
130     }
131     if (!sourceTagOption.isEmpty()) {
132       elementSelector.addTag(sourceTagOption.get(0));
133     }
134 
135     Collection<Track> elements = elementSelector.select(mediaPackage, false);
136     Job job = null;
137     for (Track track : elements) {
138       if (!track.hasAudio()) {
139         logger.info("Track {} from media package {} doesn't contain audio stream. Skip subtitle generation.",
140             track.getFlavor(), mediaPackage.getIdentifier());
141         continue;
142       }
143       try {
144         job = service.startTranscription(mediaPackage.getIdentifier().toString(), track, language, jobType, speaker,
145             transcriptionType, glossary, transcriptionStyle, targetLanguage);
146         // Only one job per media package
147         break;
148       } catch (TranscriptionServiceException e) {
149         throw new WorkflowOperationException(e);
150       }
151     }
152 
153     if (job == null) {
154       logger.info("No matching tracks found.");
155       return createResult(mediaPackage, Action.CONTINUE);
156     }
157 
158     // Wait for the jobs to return
159     if (!waitForStatus(job).isSuccess()) {
160       throw new WorkflowOperationException("Transcription job did not complete successfully.");
161     }
162     // Return OK means that the transcription job was created, but not finished yet
163 
164     logger.debug("External transcription job for mediapackage '{}' was created.", mediaPackage);
165 
166     // Results are empty, we should get a callback when transcription is done
167     return createResult(Action.CONTINUE);
168   }
169 
170   @Reference(target = "(provider=amberscript)")
171   public void setTranscriptionService(TranscriptionService service) {
172     this.service = service;
173   }
174 
175   @Reference
176   @Override
177   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
178     super.setServiceRegistry(serviceRegistry);
179   }
180 
181 }