use of org.opencastproject.composer.api.EncodingProfile in project opencast by opencast.
the class ComposerServiceTest method testImageToVideo.
/**
* Test method for
* {@link org.opencastproject.composer.impl.ComposerServiceImpl#imageToVideo(org.opencastproject.mediapackage.Attachment, String, Long)}
*/
@Test
public void testImageToVideo() throws Exception {
if (!ffmpegInstalled)
return;
assertTrue(sourceImage.isFile());
// Need different media files
Workspace workspace = EasyMock.createNiceMock(Workspace.class);
EasyMock.expect(workspace.get(EasyMock.anyObject())).andReturn(sourceImage).anyTimes();
EasyMock.expect(workspace.putInCollection(EasyMock.anyString(), EasyMock.anyString(), EasyMock.anyObject())).andReturn(sourceImage.toURI()).anyTimes();
composerService.setWorkspace(workspace);
EasyMock.replay(workspace);
EncodingProfile imageToVideoProfile = profileScanner.getProfile("image-movie.work");
Attachment attachment = AttachmentImpl.fromURI(sourceImage.toURI());
attachment.setIdentifier("test image");
Job imageToVideo = composerService.imageToVideo(attachment, imageToVideoProfile.getIdentifier(), 1L);
Track imageToVideoTrack = (Track) MediaPackageElementParser.getFromXml(imageToVideo.getPayload());
Assert.assertNotNull(imageToVideoTrack);
inspectedTrack.setIdentifier(imageToVideoTrack.getIdentifier());
inspectedTrack.setMimeType(MimeType.mimeType("video", "mp4"));
Assert.assertEquals(inspectedTrack, imageToVideoTrack);
}
use of org.opencastproject.composer.api.EncodingProfile in project opencast by opencast.
the class PrepareAVWorkflowOperationHandler method mux.
/**
* Merges audio and video track of the selected flavor and adds it to the media package. If there is nothing to mux, a
* new track with the target flavor is created (pointing to the original url).
*
* @param src
* The source media package
* @param operation
* the mux workflow operation
* @return the operation result containing the updated mediapackage
* @throws EncoderException
* if encoding fails
* @throws IOException
* if read/write operations from and to the workspace fail
* @throws NotFoundException
* if the workspace does not contain the requested element
*/
private WorkflowOperationResult mux(MediaPackage src, WorkflowOperationInstance operation) throws EncoderException, WorkflowOperationException, NotFoundException, MediaPackageException, IOException {
MediaPackage mediaPackage = (MediaPackage) src.clone();
// Read the configuration properties
String sourceFlavorName = StringUtils.trimToNull(operation.getConfiguration("source-flavor"));
String targetTrackTags = StringUtils.trimToNull(operation.getConfiguration("target-tags"));
String targetTrackFlavorName = StringUtils.trimToNull(operation.getConfiguration("target-flavor"));
String muxEncodingProfileName = StringUtils.trimToNull(operation.getConfiguration("mux-encoding-profile"));
String audioVideoEncodingProfileName = StringUtils.trimToNull(operation.getConfiguration("audio-video-encoding-profile"));
String videoOnlyEncodingProfileName = StringUtils.trimToNull(operation.getConfiguration("video-encoding-profile"));
String audioOnlyEncodingProfileName = StringUtils.trimToNull(operation.getConfiguration("audio-encoding-profile"));
String[] targetTags = StringUtils.split(targetTrackTags, ",");
List<String> removeTags = new ArrayList<String>();
List<String> addTags = new ArrayList<String>();
List<String> overrideTags = new ArrayList<String>();
if (targetTags != null) {
for (String tag : targetTags) {
if (tag.startsWith(MINUS)) {
removeTags.add(tag);
} else if (tag.startsWith(PLUS)) {
addTags.add(tag);
} else {
overrideTags.add(tag);
}
}
}
// Make sure the source flavor is properly set
if (sourceFlavorName == null)
throw new IllegalStateException("Source flavor must be specified");
MediaPackageElementFlavor sourceFlavor = MediaPackageElementFlavor.parseFlavor(sourceFlavorName);
// Make sure the target flavor is properly set
if (targetTrackFlavorName == null)
throw new IllegalStateException("Target flavor must be specified");
MediaPackageElementFlavor targetFlavor = MediaPackageElementFlavor.parseFlavor(targetTrackFlavorName);
// Reencode when there is no need for muxing?
boolean rewrite = true;
if (StringUtils.trimToNull(operation.getConfiguration(OPT_REWRITE)) != null) {
rewrite = Boolean.parseBoolean(operation.getConfiguration(OPT_REWRITE));
}
String audioMuxingSourceFlavors = StringUtils.trimToNull(operation.getConfiguration(OPT_AUDIO_MUXING_SOURCE_FLAVORS));
// Select those tracks that have matching flavors
Track[] tracks = mediaPackage.getTracks(sourceFlavor);
Track audioTrack = null;
Track videoTrack = null;
switch(tracks.length) {
case 0:
logger.info("No audio/video tracks with flavor '{}' found to prepare", sourceFlavor);
return createResult(mediaPackage, Action.CONTINUE);
case 1:
videoTrack = tracks[0];
if (!tracks[0].hasAudio() && tracks[0].hasVideo() && (audioMuxingSourceFlavors != null)) {
audioTrack = findAudioTrack(tracks[0], mediaPackage, audioMuxingSourceFlavors);
} else {
audioTrack = tracks[0];
}
break;
case 2:
for (Track track : tracks) {
if (track.hasAudio() && !track.hasVideo()) {
audioTrack = track;
} else if (!track.hasAudio() && track.hasVideo()) {
videoTrack = track;
} else {
throw new WorkflowOperationException("Multiple tracks with competing audio/video streams and flavor '" + sourceFlavor + "' found");
}
}
break;
default:
logger.error("More than two tracks with flavor {} found. No idea what we should be doing", sourceFlavor);
throw new WorkflowOperationException("More than two tracks with flavor '" + sourceFlavor + "' found");
}
Job job = null;
Track composedTrack = null;
// Make sure we have a matching combination
if (audioTrack == null && videoTrack != null) {
if (rewrite) {
logger.info("Encoding video only track {} to work version", videoTrack);
if (videoOnlyEncodingProfileName == null)
videoOnlyEncodingProfileName = PREPARE_VONLY_PROFILE;
// Find the encoding profile to make sure the given profile exists
EncodingProfile profile = composerService.getProfile(videoOnlyEncodingProfileName);
if (profile == null)
throw new IllegalStateException("Encoding profile '" + videoOnlyEncodingProfileName + "' was not found");
composedTrack = prepare(videoTrack, mediaPackage, videoOnlyEncodingProfileName);
} else {
composedTrack = (Track) videoTrack.clone();
composedTrack.setIdentifier(null);
mediaPackage.add(composedTrack);
}
} else if (videoTrack == null && audioTrack != null) {
if (rewrite) {
logger.info("Encoding audio only track {} to work version", audioTrack);
if (audioOnlyEncodingProfileName == null)
audioOnlyEncodingProfileName = PREPARE_AONLY_PROFILE;
// Find the encoding profile to make sure the given profile exists
EncodingProfile profile = composerService.getProfile(audioOnlyEncodingProfileName);
if (profile == null)
throw new IllegalStateException("Encoding profile '" + audioOnlyEncodingProfileName + "' was not found");
composedTrack = prepare(audioTrack, mediaPackage, audioOnlyEncodingProfileName);
} else {
composedTrack = (Track) audioTrack.clone();
composedTrack.setIdentifier(null);
mediaPackage.add(composedTrack);
}
} else if (audioTrack == videoTrack) {
if (rewrite) {
logger.info("Encoding audiovisual track {} to work version", videoTrack);
if (audioVideoEncodingProfileName == null)
audioVideoEncodingProfileName = PREPARE_AV_PROFILE;
// Find the encoding profile to make sure the given profile exists
EncodingProfile profile = composerService.getProfile(audioVideoEncodingProfileName);
if (profile == null)
throw new IllegalStateException("Encoding profile '" + audioVideoEncodingProfileName + "' was not found");
composedTrack = prepare(videoTrack, mediaPackage, audioVideoEncodingProfileName);
} else {
composedTrack = (Track) videoTrack.clone();
composedTrack.setIdentifier(null);
mediaPackage.add(composedTrack);
}
} else {
logger.info("Muxing audio and video only track {} to work version", videoTrack);
if (audioTrack.hasVideo()) {
logger.info("Stripping audio from track {}", audioTrack);
audioTrack = prepare(audioTrack, null, PREPARE_AONLY_PROFILE);
}
if (muxEncodingProfileName == null)
muxEncodingProfileName = MUX_AV_PROFILE;
// Find the encoding profile
EncodingProfile profile = composerService.getProfile(muxEncodingProfileName);
if (profile == null)
throw new IllegalStateException("Encoding profile '" + muxEncodingProfileName + "' was not found");
job = composerService.mux(videoTrack, audioTrack, profile.getIdentifier());
if (!waitForStatus(job).isSuccess()) {
throw new WorkflowOperationException("Muxing video track " + videoTrack + " and audio track " + audioTrack + " failed");
}
composedTrack = (Track) MediaPackageElementParser.getFromXml(job.getPayload());
mediaPackage.add(composedTrack);
String fileName = getFileNameFromElements(videoTrack, composedTrack);
composedTrack.setURI(workspace.moveTo(composedTrack.getURI(), mediaPackage.getIdentifier().toString(), composedTrack.getIdentifier(), fileName));
}
long timeInQueue = 0;
if (job != null) {
// add this receipt's queue time to the total
timeInQueue = job.getQueueTime();
}
// Update the track's flavor
composedTrack.setFlavor(targetFlavor);
logger.debug("Composed track has flavor '{}'", composedTrack.getFlavor());
// Add the target tags
if (overrideTags.size() > 0) {
composedTrack.clearTags();
for (String tag : overrideTags) {
logger.trace("Tagging composed track with '{}'", tag);
composedTrack.addTag(tag);
}
} else {
for (String tag : removeTags) {
logger.trace("Remove tagging '{}' from composed track", tag);
composedTrack.removeTag(tag.substring(MINUS.length()));
}
for (String tag : addTags) {
logger.trace("Add tagging '{}' to composed track", tag);
composedTrack.addTag(tag.substring(PLUS.length()));
}
}
return createResult(mediaPackage, Action.CONTINUE, timeInQueue);
}
use of org.opencastproject.composer.api.EncodingProfile in project opencast by opencast.
the class ComposeWorkflowOperationHandler 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"));
boolean tagsAndFlavorsOption = Boolean.parseBoolean(StringUtils.trimToNull(operation.getConfiguration("tags-and-flavors")));
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");
// Audio / Video only?
String audioOnlyConfig = StringUtils.trimToNull(operation.getConfiguration("audio-only"));
String videoOnlyConfig = StringUtils.trimToNull(operation.getConfiguration("video-only"));
boolean audioOnly = audioOnlyConfig != null && Boolean.parseBoolean(audioOnlyConfig);
boolean videoOnly = videoOnlyConfig != null && Boolean.parseBoolean(videoOnlyConfig);
// 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, tagsAndFlavorsOption);
String processOnlyOneConfig = StringUtils.trimToNull(operation.getConfiguration("process-first-match-only"));
boolean processOnlyOne = processOnlyOneConfig != null && Boolean.parseBoolean(processOnlyOneConfig);
// Encode all tracks found
long totalTimeInQueue = 0;
Map<Job, JobInformation> encodingJobs = new HashMap<Job, JobInformation>();
for (Track track : elements) {
// Skip audio/video only mismatches
if (audioOnly && track.hasVideo()) {
logger.info("Skipping encoding of '{}', since it contains a video stream", track);
continue;
} else if (videoOnly && track.hasAudio()) {
logger.info("Skipping encoding of '{}', since it containsa an audio stream", track);
continue;
}
// 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.encode(track, profile.getIdentifier()), new JobInformation(track, profile));
if (processOnlyOne)
break;
}
}
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) {
Track composedTrack = (Track) MediaPackageElementParser.getFromXml(job.getPayload());
// Adjust the target tags
for (String tag : targetTags) {
logger.trace("Tagging composed track with '{}'", tag);
composedTrack.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();
composedTrack.setFlavor(new MediaPackageElementFlavor(flavorType, flavorSubtype));
logger.debug("Composed track has flavor '{}'", composedTrack.getFlavor());
}
// store new tracks to mediaPackage
mediaPackage.addDerived(composedTrack, track);
String fileName = getFileNameFromElements(track, composedTrack);
composedTrack.setURI(workspace.moveTo(composedTrack.getURI(), mediaPackage.getIdentifier().toString(), composedTrack.getIdentifier(), fileName));
}
}
WorkflowOperationResult result = createResult(mediaPackage, Action.CONTINUE, totalTimeInQueue);
logger.debug("Compose operation completed");
return result;
}
use of org.opencastproject.composer.api.EncodingProfile in project opencast by opencast.
the class EncodingProfileTest method testGetExtension.
/**
* Test method for {@link org.opencastproject.composer.api.EncodingProfileImpl#getExtension(java.lang.String)}.
*/
@Test
public void testGetExtension() {
EncodingProfile profile = profiles.get(h264ProfileId);
assertNull(profile.getExtension("test"));
// Test profile with existing extension
profile = profiles.get(coverProfileId);
String commandline = "-i #{in.path} -y -r 1 -t 1 -f image2 -s 160x120 #{out.dir}/#{in.name}#{out.suffix}";
assertEquals(commandline, profile.getExtension("ffmpeg.command"));
}
use of org.opencastproject.composer.api.EncodingProfile in project opencast by opencast.
the class EncodingProfileTest method testGetIdentifier.
/**
* Test method for {@link org.opencastproject.composer.api.EncodingProfileImpl#getIdentifier()}.
*/
@Test
public void testGetIdentifier() {
EncodingProfile profile = profiles.get(h264ProfileId);
assertEquals(h264ProfileId, profile.getIdentifier());
}
Aggregations