use of org.opencastproject.mediapackage.MediaPackage in project opencast by opencast.
the class EncodeWorkflowOperationHandler method encode.
/**
* Encode tracks from MediaPackage using profiles stored in properties and updates current MediaPackage.
*
* @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 encode(MediaPackage src, WorkflowOperationInstance operation) throws EncoderException, IOException, NotFoundException, MediaPackageException, WorkflowOperationException {
MediaPackage mediaPackage = (MediaPackage) src.clone();
// Check which tags have been configured
String sourceTagsOption = StringUtils.trimToNull(operation.getConfiguration("source-tags"));
String targetTagsOption = StringUtils.trimToNull(operation.getConfiguration("target-tags"));
String sourceFlavorOption = StringUtils.trimToNull(operation.getConfiguration("source-flavor"));
String sourceFlavorsOption = StringUtils.trimToNull(operation.getConfiguration("source-flavors"));
String targetFlavorOption = StringUtils.trimToNull(operation.getConfiguration("target-flavor"));
AbstractMediaPackageElementSelector<Track> elementSelector = new TrackSelector();
// Make sure either one of tags or flavors are provided
if (StringUtils.isBlank(sourceTagsOption) && StringUtils.isBlank(sourceFlavorOption) && StringUtils.isBlank(sourceFlavorsOption)) {
logger.info("No source tags or flavors have been specified, not matching anything");
return createResult(mediaPackage, Action.CONTINUE);
}
// Select the source flavors
for (String flavor : asList(sourceFlavorsOption)) {
try {
elementSelector.addFlavor(MediaPackageElementFlavor.parseFlavor(flavor));
} catch (IllegalArgumentException e) {
throw new WorkflowOperationException("Source flavor '" + flavor + "' is malformed");
}
}
// Support legacy "source-flavor" option
if (StringUtils.isNotBlank(sourceFlavorOption)) {
String flavor = StringUtils.trim(sourceFlavorOption);
try {
elementSelector.addFlavor(MediaPackageElementFlavor.parseFlavor(flavor));
} catch (IllegalArgumentException e) {
throw new WorkflowOperationException("Source flavor '" + flavor + "' is malformed");
}
}
// Select the source tags
for (String tag : asList(sourceTagsOption)) {
elementSelector.addTag(tag);
}
// Find the encoding profile
String profilesOption = StringUtils.trimToNull(operation.getConfiguration("encoding-profiles"));
List<EncodingProfile> profiles = new ArrayList<EncodingProfile>();
for (String profileName : asList(profilesOption)) {
EncodingProfile profile = composerService.getProfile(profileName);
if (profile == null)
throw new WorkflowOperationException("Encoding profile '" + profileName + "' was not found");
profiles.add(profile);
}
// Support legacy "encoding-profile" option
String profileOption = StringUtils.trimToNull(operation.getConfiguration("encoding-profile"));
if (StringUtils.isNotBlank(profileOption)) {
String profileId = StringUtils.trim(profileOption);
EncodingProfile profile = composerService.getProfile(profileId);
if (profile == null)
throw new WorkflowOperationException("Encoding profile '" + profileId + "' was not found");
profiles.add(profile);
}
// Make sure there is at least one profile
if (profiles.isEmpty())
throw new WorkflowOperationException("No encoding profile was specified");
// Target tags
List<String> targetTags = asList(targetTagsOption);
// Target flavor
MediaPackageElementFlavor targetFlavor = null;
if (StringUtils.isNotBlank(targetFlavorOption)) {
try {
targetFlavor = MediaPackageElementFlavor.parseFlavor(targetFlavorOption);
} catch (IllegalArgumentException e) {
throw new WorkflowOperationException("Target flavor '" + targetFlavorOption + "' is malformed");
}
}
// Look for elements matching the tag
Collection<Track> elements = elementSelector.select(mediaPackage, false);
// Encode all tracks found
long totalTimeInQueue = 0;
Map<Job, JobInformation> encodingJobs = new HashMap<Job, JobInformation>();
for (Track track : elements) {
// Encode the track with all profiles
for (EncodingProfile profile : profiles) {
// Check if the track supports the output type of the profile
MediaType outputType = profile.getOutputType();
if (outputType.equals(MediaType.Audio) && !track.hasAudio()) {
logger.info("Skipping encoding of '{}', since it lacks an audio stream", track);
continue;
} else if (outputType.equals(MediaType.Visual) && !track.hasVideo()) {
logger.info("Skipping encoding of '{}', since it lacks a video stream", track);
continue;
}
logger.info("Encoding track {} using encoding profile '{}'", track, profile);
// Start encoding and wait for the result
encodingJobs.put(composerService.parallelEncode(track, profile.getIdentifier()), new JobInformation(track, profile));
}
}
if (encodingJobs.isEmpty()) {
logger.info("No matching tracks found");
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");
}
// Process the result
for (Map.Entry<Job, JobInformation> entry : encodingJobs.entrySet()) {
Job job = entry.getKey();
Track track = entry.getValue().getTrack();
// 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
if (job.getPayload().length() > 0) {
List<Track> composedTracks = (List<Track>) MediaPackageElementParser.getArrayFromXml(job.getPayload());
// Adjust the target tags
for (Track encodedTrack : composedTracks) {
for (String tag : targetTags) {
logger.trace("Tagging composed track {} with '{}'", encodedTrack.toString(), tag);
encodedTrack.addTag(tag);
}
}
// Adjust the target flavor. Make sure to account for partial updates
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();
for (Track encodedTrack : composedTracks) {
encodedTrack.setFlavor(new MediaPackageElementFlavor(flavorType, flavorSubtype));
logger.debug("Composed track {} has flavor '{}'", encodedTrack.toString(), encodedTrack.getFlavor());
}
}
// store new tracks to mediaPackage
for (Track encodedTrack : composedTracks) {
mediaPackage.addDerived(encodedTrack, track);
String fileName = getFileNameFromElements(track, encodedTrack);
encodedTrack.setURI(workspace.moveTo(encodedTrack.getURI(), mediaPackage.getIdentifier().toString(), encodedTrack.getIdentifier(), fileName));
}
}
}
WorkflowOperationResult result = createResult(mediaPackage, Action.CONTINUE, totalTimeInQueue);
logger.debug("Parallel encode operation completed");
return result;
}
use of org.opencastproject.mediapackage.MediaPackage in project opencast by opencast.
the class PartialImportWorkflowOperationHandler method concat.
private WorkflowOperationResult concat(MediaPackage src, WorkflowOperationInstance operation, List<MediaPackageElement> elementsToClean) throws EncoderException, IOException, NotFoundException, MediaPackageException, WorkflowOperationException, ServiceRegistryException {
final MediaPackage mediaPackage = (MediaPackage) src.clone();
final Long operationId = operation.getId();
//
// read config options
final Opt<String> presenterFlavor = getOptConfig(operation, SOURCE_PRESENTER_FLAVOR);
final Opt<String> presentationFlavor = getOptConfig(operation, SOURCE_PRESENTATION_FLAVOR);
final String smilFlavor = getConfig(operation, SOURCE_SMIL_FLAVOR);
final String concatEncodingProfile = getConfig(operation, CONCAT_ENCODING_PROFILE);
final Opt<String> concatOutputFramerate = getOptConfig(operation, CONCAT_OUTPUT_FRAMERATE);
final String trimEncodingProfile = getConfig(operation, TRIM_ENCODING_PROFILE);
final MediaPackageElementFlavor targetPresenterFlavor = parseTargetFlavor(getConfig(operation, TARGET_PRESENTER_FLAVOR), "presenter");
final MediaPackageElementFlavor targetPresentationFlavor = parseTargetFlavor(getConfig(operation, TARGET_PRESENTATION_FLAVOR), "presentation");
final Opt<EncodingProfile> forceProfile = getForceEncodingProfile(operation);
final boolean forceEncoding = BooleanUtils.toBoolean(getOptConfig(operation, FORCE_ENCODING).getOr("false"));
final boolean forceDivisible = BooleanUtils.toBoolean(getOptConfig(operation, ENFORCE_DIVISIBLE_BY_TWO).getOr("false"));
final List<String> requiredExtensions = getRequiredExtensions(operation);
// Skip the worklow if no presenter and presentation flavor has been configured
if (presenterFlavor.isNone() && presentationFlavor.isNone()) {
logger.warn("No presenter and presentation flavor has been set.");
return createResult(mediaPackage, Action.SKIP);
}
final EncodingProfile concatProfile = composerService.getProfile(concatEncodingProfile);
if (concatProfile == null) {
throw new WorkflowOperationException("Concat encoding profile '" + concatEncodingProfile + "' was not found");
}
float outputFramerate = -1.0f;
if (concatOutputFramerate.isSome()) {
if (NumberUtils.isNumber(concatOutputFramerate.get())) {
logger.info("Using concat output framerate");
outputFramerate = NumberUtils.toFloat(concatOutputFramerate.get());
} else {
throw new WorkflowOperationException("Unable to parse concat output frame rate!");
}
}
final EncodingProfile trimProfile = composerService.getProfile(trimEncodingProfile);
if (trimProfile == null) {
throw new WorkflowOperationException("Trim encoding profile '" + trimEncodingProfile + "' was not found");
}
//
// get tracks
final TrackSelector presenterTrackSelector = mkTrackSelector(presenterFlavor);
final TrackSelector presentationTrackSelector = mkTrackSelector(presentationFlavor);
final List<Track> originalTracks = new ArrayList<Track>();
final List<Track> presenterTracks = new ArrayList<Track>();
final List<Track> presentationTracks = new ArrayList<Track>();
// Collecting presenter tracks
for (Track t : presenterTrackSelector.select(mediaPackage, false)) {
logger.info("Found partial presenter track {}", t);
originalTracks.add(t);
presenterTracks.add(t);
}
// Collecting presentation tracks
for (Track t : presentationTrackSelector.select(mediaPackage, false)) {
logger.info("Found partial presentation track {}", t);
originalTracks.add(t);
presentationTracks.add(t);
}
// flavor_type -> job
final Map<String, Job> jobs = new HashMap<String, Job>();
// get SMIL catalog
final SMILDocument smilDocument = getSmilDocumentFromMediaPackage(mediaPackage, smilFlavor);
final SMILParElement parallel = (SMILParElement) smilDocument.getBody().getChildNodes().item(0);
final NodeList sequences = parallel.getTimeChildren();
final float trackDurationInSeconds = parallel.getDur();
final long trackDurationInMs = Math.round(trackDurationInSeconds * 1000f);
for (int i = 0; i < sequences.getLength(); i++) {
final SMILElement item = (SMILElement) sequences.item(i);
for (final String mediaType : new String[] { NODE_TYPE_AUDIO, NODE_TYPE_VIDEO }) {
final List<Track> tracks = new ArrayList<Track>();
final VCell<String> sourceType = VCell.cell(EMPTY_VALUE);
final long position = processChildren(0, tracks, item.getChildNodes(), originalTracks, sourceType, mediaType, elementsToClean, operationId);
if (tracks.isEmpty()) {
logger.debug("The tracks list was empty.");
continue;
}
final Track lastTrack = tracks.get(tracks.size() - 1);
if (position < trackDurationInMs) {
final double extendingTime = (trackDurationInMs - position) / 1000d;
if (extendingTime > 0) {
if (!lastTrack.hasVideo()) {
logger.info("Extending {} audio track end by {} seconds with silent audio", sourceType.get(), extendingTime);
tracks.add(getSilentAudio(extendingTime, elementsToClean, operationId));
} else {
logger.info("Extending {} track end with last image frame by {} seconds", sourceType.get(), extendingTime);
Attachment tempLastImageFrame = extractLastImageFrame(lastTrack, elementsToClean);
tracks.add(createVideoFromImage(tempLastImageFrame, extendingTime, elementsToClean));
}
}
}
if (tracks.size() < 2) {
logger.debug("There were less than 2 tracks, copying track...");
if (sourceType.get().startsWith(PRESENTER_KEY)) {
createCopyOfTrack(mediaPackage, tracks.get(0), targetPresenterFlavor);
} else if (sourceType.get().startsWith(PRESENTATION_KEY)) {
createCopyOfTrack(mediaPackage, tracks.get(0), targetPresentationFlavor);
} else {
logger.warn("Can't handle unkown source type '{}' for unprocessed track", sourceType.get());
}
continue;
}
for (final Track t : tracks) {
if (!t.hasVideo() && !t.hasAudio()) {
logger.error("No audio or video stream available in the track with flavor {}! {}", t.getFlavor(), t);
throw new WorkflowOperationException("No audio or video stream available in the track " + t.toString());
}
}
if (sourceType.get().startsWith(PRESENTER_KEY)) {
logger.info("Concatenating {} track", PRESENTER_KEY);
jobs.put(sourceType.get(), startConcatJob(concatProfile, tracks, outputFramerate, forceDivisible));
} else if (sourceType.get().startsWith(PRESENTATION_KEY)) {
logger.info("Concatenating {} track", PRESENTATION_KEY);
jobs.put(sourceType.get(), startConcatJob(concatProfile, tracks, outputFramerate, forceDivisible));
} else {
logger.warn("Can't handle unknown source type '{}'!", sourceType.get());
}
}
}
// Wait for the jobs to return
if (jobs.size() > 0) {
if (!JobUtil.waitForJobs(serviceRegistry, jobs.values()).isSuccess()) {
throw new WorkflowOperationException("One of the concat jobs did not complete successfully");
}
} else {
logger.info("No concatenating needed for presenter and presentation tracks, took partial source elements");
}
// All the jobs have passed, let's update the media package
long queueTime = 0L;
MediaPackageElementFlavor adjustedTargetPresenterFlavor = targetPresenterFlavor;
MediaPackageElementFlavor adjustedTargetPresentationFlavor = targetPresentationFlavor;
for (final Entry<String, Job> job : jobs.entrySet()) {
final Opt<Job> concatJob = JobUtil.update(serviceRegistry, job.getValue());
if (concatJob.isSome()) {
final String concatPayload = concatJob.get().getPayload();
if (concatPayload != null) {
final Track concatTrack;
try {
concatTrack = (Track) MediaPackageElementParser.getFromXml(concatPayload);
} catch (MediaPackageException e) {
throw new WorkflowOperationException(e);
}
final String fileName;
// Adjust the target flavor.
if (job.getKey().startsWith(PRESENTER_KEY)) {
if (!concatTrack.hasVideo()) {
fileName = PRESENTER_KEY.concat(FLAVOR_AUDIO_SUFFIX);
adjustedTargetPresenterFlavor = deriveAudioFlavor(targetPresenterFlavor);
} else {
fileName = PRESENTER_KEY;
adjustedTargetPresenterFlavor = targetPresenterFlavor;
}
concatTrack.setFlavor(adjustedTargetPresenterFlavor);
} else if (job.getKey().startsWith(PRESENTATION_KEY)) {
if (!concatTrack.hasVideo()) {
fileName = PRESENTATION_KEY.concat(FLAVOR_AUDIO_SUFFIX);
adjustedTargetPresentationFlavor = deriveAudioFlavor(targetPresentationFlavor);
} else {
fileName = PRESENTATION_KEY;
adjustedTargetPresentationFlavor = targetPresentationFlavor;
}
concatTrack.setFlavor(adjustedTargetPresentationFlavor);
} else {
fileName = UNKNOWN_KEY;
}
concatTrack.setURI(workspace.moveTo(concatTrack.getURI(), mediaPackage.getIdentifier().toString(), concatTrack.getIdentifier(), fileName + "." + FilenameUtils.getExtension(concatTrack.getURI().toString())));
logger.info("Concatenated track {} got flavor '{}'", concatTrack, concatTrack.getFlavor());
mediaPackage.add(concatTrack);
queueTime += concatJob.get().getQueueTime();
} else {
// If there is no payload, then the item has not been distributed.
logger.warn("Concat job {} does not contain a payload", concatJob);
}
} else {
logger.warn("Concat job {} could not be updated since it cannot be found", job.getValue());
}
}
// Trim presenter and presentation source track if longer than the duration from the SMIL catalog
queueTime += checkForTrimming(mediaPackage, trimProfile, targetPresentationFlavor, trackDurationInSeconds, elementsToClean);
queueTime += checkForTrimming(mediaPackage, trimProfile, deriveAudioFlavor(targetPresentationFlavor), trackDurationInSeconds, elementsToClean);
queueTime += checkForTrimming(mediaPackage, trimProfile, targetPresenterFlavor, trackDurationInSeconds, elementsToClean);
queueTime += checkForTrimming(mediaPackage, trimProfile, deriveAudioFlavor(targetPresenterFlavor), trackDurationInSeconds, elementsToClean);
adjustAudioTrackTargetFlavor(mediaPackage, targetPresenterFlavor);
adjustAudioTrackTargetFlavor(mediaPackage, targetPresentationFlavor);
queueTime += checkForMuxing(mediaPackage, targetPresenterFlavor, targetPresentationFlavor, false, elementsToClean);
queueTime += checkForEncodeToStandard(mediaPackage, forceEncoding, forceProfile, requiredExtensions, targetPresenterFlavor, targetPresentationFlavor, elementsToClean);
final WorkflowOperationResult result = createResult(mediaPackage, Action.CONTINUE, queueTime);
logger.debug("Partial import operation completed");
return result;
}
use of org.opencastproject.mediapackage.MediaPackage in project opencast by opencast.
the class ComposeWorkflowOperationHandlerTest method testComposeEncodedTrack.
@Test
public void testComposeEncodedTrack() throws Exception {
// set up mock profile
profile = EasyMock.createNiceMock(EncodingProfile.class);
EasyMock.expect(profile.getIdentifier()).andReturn(PROFILE_ID);
EasyMock.expect(profile.getApplicableMediaType()).andReturn(MediaType.Stream);
EasyMock.expect(profile.getOutputType()).andReturn(MediaType.AudioVisual);
EasyMock.expect(profile.getMimeType()).andReturn(MimeTypes.MPEG4.toString()).times(2);
profileList = new EncodingProfile[] { profile };
EasyMock.replay(profile);
// set up mock composer service
composerService = EasyMock.createNiceMock(ComposerService.class);
EasyMock.expect(composerService.getProfile(PROFILE_ID)).andReturn(profile);
EasyMock.expect(composerService.encode((Track) EasyMock.anyObject(), (String) EasyMock.anyObject())).andReturn(job);
EasyMock.replay(composerService);
operationHandler.setComposerService(composerService);
// operation configuration
String targetTags = "engage,rss";
Map<String, String> configurations = new HashMap<String, String>();
configurations.put("source-flavors", "presentation/source");
configurations.put("target-tags", targetTags);
configurations.put("target-flavor", "presenter/delivery");
configurations.put("encoding-profiles", "flash.http");
// run the operation handler
WorkflowOperationResult result = getWorkflowOperationResult(mp, configurations);
// check track metadata
MediaPackage mpNew = result.getMediaPackage();
Track trackEncoded = mpNew.getTrack(ENCODED_TRACK_ID);
Assert.assertEquals("presenter/delivery", trackEncoded.getFlavor().toString());
Assert.assertArrayEquals(targetTags.split("\\W"), trackEncoded.getTags());
Assert.assertEquals(SOURCE_TRACK_ID, trackEncoded.getReference().getIdentifier());
}
use of org.opencastproject.mediapackage.MediaPackage in project opencast by opencast.
the class CompositeWorkflowOperationHandlerTest method testSingleLayout.
@Test
public void testSingleLayout() throws Exception {
setMockups();
// operation configuration
String targetTags = "engage,compound";
Map<String, String> configurations = new HashMap<String, String>();
configurations.put("source-flavor-upper", "presenter/source");
configurations.put("source-flavor-lower", "presentation/source");
configurations.put("target-tags", targetTags);
configurations.put("target-flavor", "composite/work");
configurations.put("encoding-profile", "composite");
configurations.put("layout", TEST_LAYOUT);
configurations.put("layout-single", TEST_SINGLE_LAYOUT);
configurations.put("output-resolution", "1900x1080");
configurations.put("output-background", "black");
// run the operation handler
WorkflowOperationResult result = getWorkflowOperationResult(mp, configurations);
// check track metadata
MediaPackage mpNew = result.getMediaPackage();
Assert.assertEquals(Action.CONTINUE, result.getAction());
Track trackEncoded = mpNew.getTrack(COMPOUND_TRACK_ID);
Assert.assertEquals("composite/work", trackEncoded.getFlavor().toString());
Assert.assertTrue(Arrays.asList(targetTags.split("\\W")).containsAll(Arrays.asList(trackEncoded.getTags())));
}
use of org.opencastproject.mediapackage.MediaPackage in project opencast by opencast.
the class ImageToVideoWorkflowOperationHandlerTest method testOneImageToVideo.
@Test
public void testOneImageToVideo() throws Exception {
setMockups();
// operation configuration
String targetTags = "engage,rss";
Map<String, String> configurations = new HashMap<String, String>();
configurations.put("source-flavor", "image/intro");
configurations.put("source-tag", "intro");
configurations.put("target-tags", targetTags);
configurations.put("target-flavor", "video/intro");
configurations.put("profile", "image-movie");
configurations.put("duration", "5");
// run the operation handler
WorkflowOperationResult result = getWorkflowOperationResult(mp, configurations);
// check track metadata
MediaPackage mpNew = result.getMediaPackage();
Assert.assertEquals(Action.CONTINUE, result.getAction());
Assert.assertNotNull(mpNew.getTrack(CONVERTED_TRACK_ID));
}
Aggregations