ProcessSmilWorkflowOperationHandler.java
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.workflow.handler.composer;
import org.opencastproject.composer.api.ComposerService;
import org.opencastproject.composer.api.EncoderException;
import org.opencastproject.composer.api.EncodingProfile;
import org.opencastproject.composer.api.EncodingProfile.MediaType;
import org.opencastproject.job.api.Job;
import org.opencastproject.job.api.JobContext;
import org.opencastproject.mediapackage.AdaptivePlaylist;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.MediaPackageElementParser;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.Track;
import org.opencastproject.mediapackage.selector.AbstractMediaPackageElementSelector;
import org.opencastproject.mediapackage.selector.TrackSelector;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.smil.api.SmilException;
import org.opencastproject.smil.api.SmilResponse;
import org.opencastproject.smil.api.SmilService;
import org.opencastproject.smil.entity.api.Smil;
import org.opencastproject.smil.entity.media.param.api.SmilMediaParam;
import org.opencastproject.smil.entity.media.param.api.SmilMediaParamGroup;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowOperationException;
import org.opencastproject.workflow.api.WorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowOperationInstance;
import org.opencastproject.workflow.api.WorkflowOperationResult;
import org.opencastproject.workflow.api.WorkflowOperationResult.Action;
import org.opencastproject.workspace.api.Workspace;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* The workflow definition for handling "compose" operations
*/
@Component(
immediate = true,
service = WorkflowOperationHandler.class,
property = {
"service.description=Process Smil Workflow Operation Handler",
"workflow.operation=process-smil"
}
)
public class ProcessSmilWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
static final String SEPARATOR = ";";
/** The logging facility */
private static final Logger logger = LoggerFactory.getLogger(ProcessSmilWorkflowOperationHandler.class);
/** The composer service */
private ComposerService composerService = null;
/** The smil service to parse the smil */
private SmilService smilService;
/** The local workspace */
private Workspace workspace = null;
private Predicate<EncodingProfile> isManifestEP = p -> p.getOutputType() == EncodingProfile.MediaType.Manifest;
/**
* A convenience structure to hold info for each paramgroup in the Smil which will produce one trim/concat/encode job
*/
private class TrackSection {
private final String paramGroupId;
private List<Track> sourceTracks;
private List<String> smilTracks;
private final String flavor;
private String mediaType = ""; // Has both Audio and Video
TrackSection(String id, String flavor) {
this.flavor = flavor;
this.paramGroupId = id;
}
public List<Track> getSourceTracks() {
return sourceTracks;
}
/**
* Set source Tracks for this group, if audio or video is missing in any of the source files, then do not try to
* edit with the missing media type, because it will fail
*
* @param sourceTracks
*/
public void setSourceTracks(List<Track> sourceTracks) {
boolean hasVideo = true;
boolean hasAudio = true;
this.sourceTracks = sourceTracks;
for (Track track : sourceTracks) {
if (!track.hasVideo())
hasVideo = false;
if (!track.hasAudio())
hasAudio = false;
}
if (!hasVideo) {
mediaType = ComposerService.AUDIO_ONLY;
}
if (!hasAudio) {
mediaType = ComposerService.VIDEO_ONLY;
}
}
public String getFlavor() {
return flavor;
}
@Override
public String toString() {
return paramGroupId + " " + flavor + " " + sourceTracks.toString();
}
public void setSmilTrackList(List<String> smilSourceTracks) {
smilTracks = smilSourceTracks;
}
public List<String> getSmilTrackList() {
return smilTracks;
}
};
// To return both params from a function that checks all the jobs
private class ResultTally {
private final MediaPackage mediaPackage;
private final long totalTimeInQueue;
ResultTally(MediaPackage mediaPackage, long totalTimeInQueue) {
super();
this.mediaPackage = mediaPackage;
this.totalTimeInQueue = totalTimeInQueue;
}
public MediaPackage getMediaPackage() {
return mediaPackage;
}
public long getTotalTimeInQueue() {
return totalTimeInQueue;
}
}
@Activate
public void activate(ComponentContext cc) {
super.activate(cc);
}
/**
* Callback for the OSGi declarative services configuration.
*
* @param composerService
* the local composer service
*/
@Reference
protected void setComposerService(ComposerService composerService) {
this.composerService = composerService;
}
/**
* Callback for the OSGi declarative services configuration.
*
* @param smilService
*/
@Reference
protected void setSmilService(SmilService smilService) {
this.smilService = smilService;
}
/**
* Callback for declarative services configuration that will introduce us to the local workspace service.
* Implementation assumes that the reference is configured as being static.
*
* @param workspace
* an instance of the workspace
*/
@Reference
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
@Reference
@Override
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
super.setServiceRegistry(serviceRegistry);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance,
* JobContext)
*/
@Override
public WorkflowOperationResult start(final WorkflowInstance workflowInstance, JobContext context)
throws WorkflowOperationException {
try {
return processSmil(workflowInstance.getMediaPackage(), workflowInstance.getCurrentOperation());
} catch (Exception e) {
e.printStackTrace();
throw new WorkflowOperationException(e);
}
}
private String[] getConfigAsArray(WorkflowOperationInstance operation, String name) {
String sourceOption = StringUtils.trimToNull(operation.getConfiguration(name));
String[] options = (sourceOption != null) ? sourceOption.split(SEPARATOR) : null;
return (options);
}
private String[] collapseConfig(WorkflowOperationInstance operation, String name) {
String targetOption = StringUtils.trimToNull(operation.getConfiguration(name));
return (targetOption != null) ? new String[] { targetOption.replaceAll(SEPARATOR, ",") } : null;
}
/**
* Encode tracks from Smil using profiles stored in properties and updates current MediaPackage. This procedure parses
* the workflow definitions and decides how many encoding jobs are needed
*
* @param src
* The source media package
* @param operation
* the current workflow operation
* @return the operation result containing the updated media package
* @throws EncoderException
* if encoding fails
* @throws WorkflowOperationException
* if errors occur during processing
* @throws IOException
* if the workspace operations fail
* @throws NotFoundException
* if the workspace doesn't contain the requested file
*/
private WorkflowOperationResult processSmil(MediaPackage src, WorkflowOperationInstance operation)
throws EncoderException, IOException, NotFoundException, MediaPackageException, WorkflowOperationException {
MediaPackage mediaPackage = (MediaPackage) src.clone();
// Check which tags have been configured
String smilFlavorOption = StringUtils.trimToEmpty(operation.getConfiguration("smil-flavor"));
String[] srcFlavors = getConfigAsArray(operation, "source-flavors");
String[] targetFlavors = getConfigAsArray(operation, "target-flavors");
String[] targetTags = getConfigAsArray(operation, "target-tags");
String[] profilesSections = getConfigAsArray(operation, "encoding-profiles");
String tagWithProfileConfig = StringUtils.trimToNull(operation.getConfiguration("tag-with-profile"));
boolean tagWithProfile = tagWithProfileConfig != null && Boolean.parseBoolean(tagWithProfileConfig);
// Make sure there is a smil src
if (StringUtils.isBlank(smilFlavorOption)) {
logger.info("No smil flavor has been specified, no src to process"); // Must have Smil input
return createResult(mediaPackage, Action.CONTINUE);
}
if (srcFlavors == null) {
logger.info("No source flavors have been specified, not matching anything");
return createResult(mediaPackage, Action.CONTINUE); // Should be OK
}
// Make sure at least one encoding profile is provided
if (profilesSections == null) {
throw new WorkflowOperationException("No encoding profile was specified");
}
/*
* Must have smil file, and encoding profile(s) If source-flavors is used, then target-flavors must be used If
* separators ";" are used in source-flavors, then there must be the equivalent number of matching target-flavors
* and encoding profiles used, or one for all of them.
*/
if (srcFlavors.length > 1) { // Different processing for each flavor
if (targetFlavors != null && srcFlavors.length != targetFlavors.length && targetFlavors.length != 1) {
String mesg = "Number of target flavor sections " + targetFlavors + " must either match that of src flavor "
+ srcFlavors + " or equal 1 ";
throw new WorkflowOperationException(mesg);
}
if (srcFlavors.length != profilesSections.length) {
if (profilesSections.length != 1) {
String mesg = "Number of encoding profile sections " + profilesSections
+ " must either match that of src flavor " + srcFlavors + " or equal 1 ";
throw new WorkflowOperationException(mesg);
} else { // we need to duplicate profileSections for each src selector
String[] array = new String[srcFlavors.length];
Arrays.fill(array, 0, srcFlavors.length, profilesSections[0]);
profilesSections = array;
}
}
if (targetTags != null && srcFlavors.length != targetTags.length && targetTags.length != 1) {
String mesg = "Number of target Tags sections " + targetTags + " must either match that of src flavor "
+ srcFlavors + " or equal 1 ";
throw new WorkflowOperationException(mesg);
}
} else { // Only one srcFlavor - collapse all sections into one
targetFlavors = collapseConfig(operation, "target-flavors");
targetTags = collapseConfig(operation, "target-tags");
profilesSections = collapseConfig(operation, "encoding-profiles");
if (profilesSections.length != 1)
throw new WorkflowOperationException(
"No matching src flavors " + srcFlavors + " for encoding profiles sections " + profilesSections);
logger.debug("Single input flavor: output= " + Arrays.toString(targetFlavors) + " tag: "
+ Arrays.toString(targetTags) + " profile:" + Arrays.toString(profilesSections));
}
Map<Job, JobInformation> encodingJobs = new HashMap<Job, JobInformation>();
for (int i = 0; i < profilesSections.length; i++) {
// Each section is one multiconcatTrim job - set up the jobs
processSection(encodingJobs, mediaPackage, (srcFlavors.length > 1) ? srcFlavors[i] : srcFlavors[0],
(targetFlavors != null) ? ((targetFlavors.length > 1) ? targetFlavors[i] : targetFlavors[0]) : null,
(targetTags != null) ? ((targetTags.length > 1) ? targetTags[i] : targetTags[0]) : null,
(profilesSections.length > 0) ? profilesSections[i] : profilesSections[0], smilFlavorOption,
tagWithProfile);
}
if (encodingJobs.isEmpty()) {
logger.info("Failed to process any tracks");
return createResult(mediaPackage, Action.CONTINUE);
}
// Wait for the jobs to return
if (!waitForStatus(encodingJobs.keySet().toArray(new Job[encodingJobs.size()])).isSuccess()) {
throw new WorkflowOperationException("One of the encoding jobs did not complete successfully");
}
ResultTally allResults = parseResults(encodingJobs, mediaPackage);
WorkflowOperationResult result = createResult(allResults.getMediaPackage(), Action.CONTINUE,
allResults.getTotalTimeInQueue());
logger.debug("ProcessSmil operation completed");
return result;
}
/**
* Process one group encode section with one source Flavor declaration(may be wildcard) , sharing one set of shared
* optional target tags/flavors and one set of encoding profiles
*
* @param encodingJobs
* @param mediaPackage
* @param srcFlavors
* - used to select which param group/tracks to process
* @param targetFlavors
* - the resultant track will be tagged with these flavors
* @param targetTags
* - the resultant track will be tagged
* @param media
* - if video or audio only
* @param encodingProfiles
* - profiles to use, if ant of them does not fit the source tracks, they will be omitted
* @param smilFlavor
* - the smil flavor for the input smil
* @param tagWithProfile - tag target with profile name
* @throws WorkflowOperationException
* if flavors/tags/etc are malformed or missing
* @throws EncoderException
* if encoding command cannot be constructed
* @throws MediaPackageException
* @throws IllegalArgumentException
* @throws NotFoundException
* @throws IOException
*/
private void processSection(Map<Job, JobInformation> encodingJobs, MediaPackage mediaPackage,
String srcFlavors, String targetFlavors, String targetTags,
String encodingProfiles, String smilFlavor, boolean tagWithProfile) throws WorkflowOperationException,
EncoderException, MediaPackageException, IllegalArgumentException, NotFoundException, IOException {
// Select the source flavors
AbstractMediaPackageElementSelector<Track> elementSelector = new TrackSelector();
for (String flavor : asList(srcFlavors)) {
try {
elementSelector.addFlavor(MediaPackageElementFlavor.parseFlavor(flavor));
} catch (IllegalArgumentException e) {
throw new WorkflowOperationException("Source flavor '" + flavor + "' is malformed");
}
}
Smil smil = getSmil(mediaPackage, smilFlavor);
// Check that the matching source tracks exist in the SMIL
List<TrackSection> smilgroups;
try {
smilgroups = selectTracksFromMP(mediaPackage, smil, srcFlavors);
} catch (URISyntaxException e1) {
logger.info("Smil contains bad URI", e1);
throw new WorkflowOperationException("Smil contains bad URI - cannot process", e1);
}
if (smilgroups.size() == 0 || smilgroups.get(0).sourceTracks.size() == 0) {
logger.info("Smil does not contain any tracks of {} source flavor", srcFlavors);
return;
}
// Check Target flavor
MediaPackageElementFlavor targetFlavor = null;
if (StringUtils.isNotBlank(targetFlavors)) {
try {
targetFlavor = MediaPackageElementFlavor.parseFlavor(targetFlavors);
} catch (IllegalArgumentException e) {
throw new WorkflowOperationException("Target flavor '" + targetFlavors + "' is malformed");
}
}
Set<EncodingProfile> profiles = new HashSet<EncodingProfile>();
Set<String> profileNames = new HashSet<String>();
// Find all the encoding profiles
// Check that the profiles support the media source types
for (TrackSection ts : smilgroups)
for (Track track : ts.getSourceTracks()) {
// Check that the profile is supported
for (String profileName : asList(encodingProfiles)) {
EncodingProfile profile = composerService.getProfile(profileName);
if (profile == null)
throw new WorkflowOperationException("Encoding profile '" + profileName + "' was not found");
MediaType outputType = profile.getOutputType();
// Check if the track supports the output type of the profile MediaType outputType = profile.getOutputType();
// Omit if needed
if (outputType.equals(MediaType.Audio) && !track.hasAudio()) {
logger.info("Skipping encoding of '{}' with " + profileName + ", since the track lacks an audio stream",
track);
continue;
} else if (outputType.equals(MediaType.Visual) && !track.hasVideo()) {
logger.info("Skipping encoding of '{}' " + profileName + ", since the track lacks a video stream", track);
continue;
} else if (outputType.equals(MediaType.AudioVisual) && !track.hasAudio() && !track.hasVideo()) {
logger.info("Skipping encoding of '{}' (audiovisual)" + profileName
+ ", since it lacks a audio or video stream", track);
continue;
}
profiles.add(profile); // Include this profiles for encoding
profileNames.add(profileName);
}
}
// Make sure there is at least one profile
if (profiles.isEmpty())
throw new WorkflowOperationException("No encoding profile was specified");
List<String> tags = (targetTags != null) ? asList(targetTags) : null;
// Encode all tracks found in each param group
// Start encoding and wait for the result - usually one for presenter, one for presentation
for (TrackSection trackGroup : smilgroups) {
encodingJobs.put(
composerService.processSmil(smil, trackGroup.paramGroupId, trackGroup.mediaType,
new ArrayList<String>(profileNames)),
new JobInformation(trackGroup.paramGroupId, trackGroup.sourceTracks,
new ArrayList<EncodingProfile>(profiles), tags, targetFlavor, tagWithProfile));
logger.info("Edit and encode {} target flavors: {} tags: {} profile {}", trackGroup, targetFlavor, tags,
profileNames);
}
}
/**
* Find the matching encoding profile for this track and tag by name
*
* @param track
* @param profiles
* - profiles used to encode a track to multiple formats
* @return
*/
private void tagByProfile(Track track, List<EncodingProfile> profiles) {
String rawfileName = track.getURI().getRawPath();
for (EncodingProfile ep : profiles) {
// #5687: Add any character at the beginning of the suffix so that it is properly
// converted in toSafeName (because the regex used there may treat the first
// character differently; the default regex currently does).
String suffixToSanitize = "X" + ep.getSuffix();
// !! workspace.putInCollection renames the file - need to do the same with suffix
String suffix = workspace.toSafeName(suffixToSanitize).substring(1);
if (suffix.length() > 0 && rawfileName.endsWith(suffix)) {
track.addTag(ep.getIdentifier());
return;
}
}
}
/**
* parse all the encoding jobs to collect all the composed tracks, if any of them fail, just fail the whole thing and
* try to clean up
*
* @param encodingJobs
* - queued jobs to do the encodings, this is parsed for payload
* @param mediaPackage
* - to hold the target tracks
* @return a structure with time in queue plus a mediaPackage with all the new tracks added if all the encoding jobs
* passed, if any of them fail, just fail the whole thing and try to clean up
* @throws IllegalArgumentException
* @throws NotFoundException
* @throws IOException
* @throws MediaPackageException
* @throws WorkflowOperationException
*/
@SuppressWarnings("unchecked")
private ResultTally parseResults(Map<Job, JobInformation> encodingJobs, MediaPackage mediaPackage)
throws IllegalArgumentException, NotFoundException, IOException, MediaPackageException, WorkflowOperationException {
// Process the result
long totalTimeInQueue = 0;
for (Map.Entry<Job, JobInformation> entry : encodingJobs.entrySet()) {
Job job = entry.getKey();
List<Track> tracks = entry.getValue().getTracks();
Track track = tracks.get(0); // Can only reference one track, pick one
// add this receipt's queue time to the total
totalTimeInQueue += job.getQueueTime();
// it is allowed for compose jobs to return an empty payload. See the EncodeEngine interface
List<Track> composedTracks = null;
if (job.getPayload().length() > 0) {
composedTracks = (List<Track>) MediaPackageElementParser.getArrayFromXml(job.getPayload());
boolean isHLS = entry.getValue().getProfiles().stream().anyMatch(isManifestEP);
if (isHLS) { // check that manifests and segments counts are correct
decipherHLSPlaylistResults(track, entry.getValue(), mediaPackage, composedTracks);
}
// Adjust the target tags
for (Track composedTrack : composedTracks) {
if (entry.getValue().getTags() != null) {
for (String tag : entry.getValue().getTags()) {
composedTrack.addTag(tag);
}
}
// Adjust the target flavor. Make sure to account for partial updates
MediaPackageElementFlavor targetFlavor = entry.getValue().getFlavor();
if (targetFlavor != null) {
String flavorType = targetFlavor.getType();
String flavorSubtype = targetFlavor.getSubtype();
if ("*".equals(flavorType))
flavorType = track.getFlavor().getType();
if ("*".equals(flavorSubtype))
flavorSubtype = track.getFlavor().getSubtype();
composedTrack.setFlavor(new MediaPackageElementFlavor(flavorType, flavorSubtype));
logger.debug("Composed track has flavor '{}'", composedTrack.getFlavor());
}
List<EncodingProfile> eps = entry.getValue().getProfiles();
String fileName = composedTrack.getURI().getRawPath();
// Tag each output with encoding profile name, if configured
if (entry.getValue().getTagProfile()) {
tagByProfile(composedTrack, eps);
}
if (!isHLS || composedTrack.isMaster()) {
fileName = getFileNameFromElements(track, composedTrack);
} else // preserve name from profile - should we do this?
fileName = FilenameUtils.getName(composedTrack.getURI().getPath());
composedTrack.setURI(workspace.moveTo(composedTrack.getURI(), mediaPackage.getIdentifier().toString(),
composedTrack.getIdentifier(), fileName));
synchronized (mediaPackage) {
mediaPackage.addDerived(composedTrack, track);
}
}
}
}
return new ResultTally(mediaPackage, totalTimeInQueue);
}
private List<Track> getManifest(Collection<Track> tracks) {
return tracks.stream().filter(AdaptivePlaylist.isHLSTrackPred).collect(Collectors.toList());
}
// HLS-VOD
private void decipherHLSPlaylistResults(Track track, JobInformation jobInfo, MediaPackage mediaPackage,
List<Track> composedTracks)
throws WorkflowOperationException, IllegalArgumentException, NotFoundException, IOException {
int nprofiles = jobInfo.getProfiles().size();
List<Track> manifests = getManifest(composedTracks);
if (manifests.size() != nprofiles) {
throw new WorkflowOperationException("Number of output playlists does not match number of encoding profiles");
}
if (composedTracks.size() != manifests.size() * 2 - 1) {
throw new WorkflowOperationException("Number of output media does not match number of encoding profiles");
}
}
/**
* @param trackFlavor
* @param sourceFlavor
* @return true if trackFlavor matches sourceFlavor
*/
private boolean trackMatchesFlavor(MediaPackageElementFlavor trackFlavor, MediaPackageElementFlavor sourceFlavor) {
return ((trackFlavor.getType().equals(sourceFlavor.getType()) && trackFlavor.getSubtype() // exact match
.equals(sourceFlavor.getSubtype()))
|| ("*".equals(sourceFlavor.getType()) && trackFlavor.getSubtype().equals(sourceFlavor.getSubtype())) // same
// subflavor
|| (trackFlavor.getType().equals(sourceFlavor.getType()) && "*".equals(sourceFlavor.getSubtype()))); // same
// flavor
}
/**
* @param mediaPackage
* - mp obj contains tracks
* @param smil
* - smil obj contains description of clips
* @param srcFlavors
* - source flavor string (may contain wild cards)
* @return a structure of smil groups, each with a single flavor and mp tracks for that flavor only
* @throws WorkflowOperationException
* @throws URISyntaxException
*/
private List<TrackSection> selectTracksFromMP(MediaPackage mediaPackage, Smil smil, String srcFlavors)
throws WorkflowOperationException, URISyntaxException {
List<TrackSection> sourceTrackList = new ArrayList<TrackSection>();
Collection<TrackSection> smilFlavors = parseSmil(smil);
Iterator<TrackSection> it = smilFlavors.iterator();
while (it.hasNext()) {
TrackSection ts = it.next();
for (String f : StringUtils.split(srcFlavors, ",")) { // Look for all source Flavors
String sourceFlavorStr = StringUtils.trimToNull(f);
if (sourceFlavorStr == null)
continue;
MediaPackageElementFlavor sourceFlavor = MediaPackageElementFlavor.parseFlavor(sourceFlavorStr);
MediaPackageElementFlavor trackFlavor = MediaPackageElementFlavor.parseFlavor(ts.getFlavor());
if (trackMatchesFlavor(trackFlavor, sourceFlavor)) {
sourceTrackList.add(ts); // This smil group matches src Flavor, add to list
Track[] elements = null;
List<Track> sourceTracks = new ArrayList<Track>();
elements = mediaPackage.getTracks(sourceFlavor);
for (String t : ts.getSmilTrackList()) { // Look thru all the tracks referenced by the smil
URI turi = new URI(t);
for (Track e : elements)
if (e.getURI().equals(turi)) { // find it in the mp
sourceTracks.add(e); // add the track from mp containing inspection info
}
}
if (sourceTracks.isEmpty()) {
logger.info("ProcessSmil - No tracks in mediapackage matching the URI in the smil- cannot process");
throw new WorkflowOperationException("Smil has no matching tracks in the mediapackage");
}
ts.setSourceTracks(sourceTracks); // Will also if srcTracks are Video/Audio Only
}
}
}
return sourceTrackList;
}
/**
* Get smil from media package
*
* @param mp
* @param smilFlavorOption
* @return smil
* @throws WorkflowOperationException
*/
private Smil getSmil(MediaPackage mp, String smilFlavorOption) throws WorkflowOperationException {
MediaPackageElementFlavor smilFlavor = MediaPackageElementFlavor.parseFlavor(smilFlavorOption);
Catalog[] catalogs = mp.getCatalogs(smilFlavor);
if (catalogs.length == 0) {
throw new WorkflowOperationException("MediaPackage does not contain a SMIL document.");
}
Smil smil = null;
try {
File smilFile = workspace.get(catalogs[0].getURI());
// break up chained method for junit smil service mockup
SmilResponse response = smilService.fromXml(FileUtils.readFileToString(smilFile, "UTF-8"));
smil = response.getSmil();
return smil;
} catch (NotFoundException ex) {
throw new WorkflowOperationException("MediaPackage does not contain a smil catalog.");
} catch (IOException ex) {
throw new WorkflowOperationException("Failed to read smil catalog.", ex);
} catch (SmilException ex) {
throw new WorkflowOperationException(ex);
}
}
/**
* Sort paramGroup by flavor, each one will be a separate job
*
* @param smil
* @return TrackSection
*/
private Collection<TrackSection> parseSmil(Smil smil) {
// get all source tracks
List<TrackSection> trackGroups = new ArrayList<TrackSection>();
// Find the track flavors, and find track groups that matches the flavors
for (SmilMediaParamGroup paramGroup : smil.getHead().getParamGroups()) { // For each group look at elements
TrackSection ts = null;
List<String> src = new ArrayList<String>();
for (SmilMediaParam param : paramGroup.getParams()) {
if (SmilMediaParam.PARAM_NAME_TRACK_FLAVOR.matches(param.getName())) { // Is a flavor
ts = new TrackSection(paramGroup.getId(), param.getValue());
trackGroups.add(ts);
}
if (SmilMediaParam.PARAM_NAME_TRACK_SRC.matches(param.getName())) { // Is a track
src.add(param.getValue());
}
}
if (ts != null)
ts.setSmilTrackList(src);
}
return trackGroups;
}
/**
* This class is used to store context information for the jobs.
*/
private static final class JobInformation {
private final List<EncodingProfile> profiles;
private final List<Track> tracks;
private String grp = null;
private MediaPackageElementFlavor flavor = null;
private List<String> tags = null;
private boolean tagProfile;
JobInformation(String paramgroup, List<Track> tracks, List<EncodingProfile> profiles, List<String> tags,
MediaPackageElementFlavor flavor, boolean tagWithProfile) {
this.tracks = tracks;
this.grp = paramgroup;
this.profiles = profiles;
this.tags = tags;
this.flavor = flavor;
this.tagProfile = tagWithProfile;
}
public List<Track> getTracks() {
return tracks;
}
public MediaPackageElementFlavor getFlavor() {
return flavor;
}
public List<String> getTags() {
return tags;
}
public boolean getTagProfile() {
return this.tagProfile;
}
@SuppressWarnings("unused")
public String getGroups() {
return grp;
}
@SuppressWarnings("unused")
public List<EncodingProfile> getProfiles() {
return profiles;
}
}
}