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 Google Speech Transcription Workflow Operation Handler",
59          "workflow.operation=google-speech-start-transcription"
60      }
61  )
62  public class GoogleSpeechStartTranscriptionOperationHandler extends AbstractWorkflowOperationHandler {
63  
64    /**
65     * The logging facility
66     */
67    private static final Logger logger = LoggerFactory.getLogger(GoogleSpeechStartTranscriptionOperationHandler.class);
68  
69    /**
70     * Workflow configuration option keys
71     */
72    static final String LANGUAGE_CODE = "language-code";
73    static final String SKIP_IF_FLAVOR_EXISTS = "skip-if-flavor-exists";
74  
75    /**
76     * The transcription service
77     */
78    private TranscriptionService service = null;
79  
80    /**
81     * The language code
82     */
83    private String language = null;
84  
85    @Override
86    @Activate
87    protected void activate(ComponentContext cc) {
88      super.activate(cc);
89    }
90  
91    /**
92     * {@inheritDoc}
93     *
94     * @see
95     * org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance,
96     * JobContext)
97     */
98    @Override
99    public WorkflowOperationResult start(final WorkflowInstance workflowInstance, JobContext context)
100           throws WorkflowOperationException {
101     MediaPackage mediaPackage = workflowInstance.getMediaPackage();
102     WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
103 
104     String skipOption = StringUtils.trimToNull(operation.getConfiguration(SKIP_IF_FLAVOR_EXISTS));
105     if (skipOption != null) {
106       MediaPackageElement[] mpes = mediaPackage.getElementsByFlavor(MediaPackageElementFlavor.parseFlavor(skipOption));
107       if (mpes != null && mpes.length > 0) {
108         logger.info(
109                 "Start transcription operation will be skipped because flavor {} already exists in the media package",
110                 skipOption);
111         return createResult(Action.SKIP);
112       }
113     }
114 
115     logger.debug("Start transcription for mediapackage {} started", mediaPackage);
116 
117     // Get language code if configured
118     String langCode = operation.getConfiguration(LANGUAGE_CODE);
119 
120     // Check which tags have been configured
121     ConfiguredTagsAndFlavors tagsAndFlavors = getTagsAndFlavors(
122         workflowInstance, Configuration.many, Configuration.many, Configuration.none, Configuration.none);
123     List<String> sourceTagOption = tagsAndFlavors.getSrcTags();
124     List<MediaPackageElementFlavor> sourceFlavorOption = tagsAndFlavors.getSrcFlavors();
125 
126     AbstractMediaPackageElementSelector<Track> elementSelector = new TrackSelector();
127 
128     // Make sure either one of tags or flavors are provided
129     if (sourceTagOption.isEmpty() && sourceFlavorOption.isEmpty()) {
130       throw new WorkflowOperationException("No source tag or flavor have been specified!");
131     }
132 
133     if (!sourceFlavorOption.isEmpty()) {
134       MediaPackageElementFlavor flavor = sourceFlavorOption.get(0);
135       elementSelector.addFlavor(flavor);
136     }
137     if (StringUtils.isNotBlank(langCode)) {
138       language = StringUtils.trim(langCode);
139     }
140     if (!sourceTagOption.isEmpty()) {
141       elementSelector.addTag(sourceTagOption.get(0));
142     }
143 
144     Collection<Track> elements = elementSelector.select(mediaPackage, false);
145     Job job = null;
146     for (Track track : elements) {
147       if (track.hasVideo()) {
148         logger.info("Skipping track {} since it contains a video stream", track);
149         continue;
150       }
151       if (!track.hasAudio()) {
152         logger.info("Track {} from media package {} doesn't contain audio stream. Skip subtitle generation.",
153             track.getFlavor(), mediaPackage.getIdentifier());
154         continue;
155       }
156       try {
157         job = service.startTranscription(mediaPackage.getIdentifier().toString(), track, language);
158         // Only one job per media package
159         break;
160       } catch (TranscriptionServiceException e) {
161         throw new WorkflowOperationException(e);
162       }
163     }
164 
165     if (job == null) {
166       logger.info("No matching tracks found");
167       return createResult(mediaPackage, Action.CONTINUE);
168     }
169 
170     // Wait for the jobs to return
171     if (!waitForStatus(job).isSuccess()) {
172       throw new WorkflowOperationException("Transcription job did not complete successfully");
173     }
174     // Return OK means that the Google speech job was created, but not finished yet
175 
176     logger.debug("External transcription job for mediapackage {} was created", mediaPackage);
177 
178     // Results are empty, we should get a callback when transcription is done
179     return createResult(Action.CONTINUE);
180   }
181 
182   @Reference(target = "(provider=google.speech)")
183   public void setTranscriptionService(TranscriptionService service) {
184     this.service = service;
185   }
186 
187   @Reference
188   @Override
189   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
190     super.setServiceRegistry(serviceRegistry);
191   }
192 
193 }