use of org.opencastproject.workflow.api.WorkflowOperationException in project opencast by opencast.
the class NormalizeAudioWorkflowOperationHandler method normalize.
private WorkflowOperationResult normalize(MediaPackage src, WorkflowOperationInstance operation) throws SoxException, IOException, NotFoundException, MediaPackageException, WorkflowOperationException, EncoderException {
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"));
String targetDecibelString = StringUtils.trimToNull(operation.getConfiguration("target-decibel"));
if (targetDecibelString == null)
throw new IllegalArgumentException("target-decibel must be specified");
boolean forceTranscode = BooleanUtils.toBoolean(operation.getConfiguration("force-transcode"));
Float targetDecibel;
try {
targetDecibel = new Float(targetDecibelString);
} catch (NumberFormatException e1) {
throw new WorkflowOperationException("Unable to parse target-decibel " + targetDecibelString);
}
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);
}
// 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;
List<URI> cleanupURIs = new ArrayList<URI>();
Map<Job, Track> normalizeJobs = new HashMap<Job, Track>();
try {
for (Track track : elements) {
TrackImpl audioTrack = (TrackImpl) track;
// Skip video only mismatches
if (!track.hasAudio()) {
logger.info("Skipping audio normalization of '{}', since it contains no audio stream", track);
continue;
} else if (track.hasVideo() || forceTranscode) {
audioTrack = (TrackImpl) extractAudioTrack(track);
audioTrack.setAudio(((TrackImpl) track).getAudio());
cleanupURIs.add(audioTrack.getURI());
}
// Analyze audio track
if (audioTrack.getAudio().size() < 1 || audioTrack.getAudio().get(0).getRmsLevDb() == null) {
logger.info("Audio track {} has no RMS Lev dB metadata, analyze it first", audioTrack);
Job analyzeJob = soxService.analyze(audioTrack);
if (!waitForStatus(analyzeJob).isSuccess())
throw new WorkflowOperationException("Unable to analyze the audio track " + audioTrack);
audioTrack = (TrackImpl) MediaPackageElementParser.getFromXml(analyzeJob.getPayload());
cleanupURIs.add(audioTrack.getURI());
}
normalizeJobs.put(soxService.normalize(audioTrack, targetDecibel), track);
}
if (normalizeJobs.isEmpty()) {
logger.info("No matching tracks found");
return createResult(mediaPackage, Action.CONTINUE);
}
// Wait for the jobs to return
if (!waitForStatus(normalizeJobs.keySet().toArray(new Job[normalizeJobs.size()])).isSuccess())
throw new WorkflowOperationException("One of the normalize jobs did not complete successfully");
// Process the result
for (Map.Entry<Job, Track> entry : normalizeJobs.entrySet()) {
Job job = entry.getKey();
TrackImpl origTrack = (TrackImpl) entry.getValue();
// add this receipt's queue time to the total
totalTimeInQueue += job.getQueueTime();
if (job.getPayload().length() > 0) {
TrackImpl normalizedAudioTrack = (TrackImpl) MediaPackageElementParser.getFromXml(job.getPayload());
TrackImpl resultTrack = normalizedAudioTrack;
if (origTrack.hasVideo() || forceTranscode) {
cleanupURIs.add(normalizedAudioTrack.getURI());
logger.info("Mux normalized audio track {} to video track {}", normalizedAudioTrack, origTrack);
Job muxAudioVideo = composerService.mux(origTrack, normalizedAudioTrack, SOX_AREPLACE_PROFILE);
if (!waitForStatus(muxAudioVideo).isSuccess())
throw new WorkflowOperationException("Muxing normalized audio track " + normalizedAudioTrack + " to video container " + origTrack + " failed");
resultTrack = (TrackImpl) MediaPackageElementParser.getFromXml(muxAudioVideo.getPayload());
// Set metadata on track
extendAudioStream(resultTrack, normalizedAudioTrack);
}
adjustFlavorAndTags(targetTags, targetFlavor, origTrack, resultTrack);
mediaPackage.addDerived(resultTrack, origTrack);
String fileName = getFileNameFromElements(origTrack, resultTrack);
resultTrack.setURI(workspace.moveTo(resultTrack.getURI(), mediaPackage.getIdentifier().toString(), resultTrack.getIdentifier(), fileName));
} else {
logger.warn("Normalize audio job {} for track {} has no result!", job, origTrack);
}
}
} finally {
// Clean up temporary audio and video files from workspace
for (URI uri : cleanupURIs) {
workspace.delete(uri);
}
}
WorkflowOperationResult result = createResult(mediaPackage, Action.CONTINUE, totalTimeInQueue);
logger.debug("Normalize audio operation completed");
return result;
}
use of org.opencastproject.workflow.api.WorkflowOperationException in project opencast by opencast.
the class AnimateWorkflowOperationHandler method start.
/**
* {@inheritDoc}
*
* @see
* org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance,
* org.opencastproject.job.api.JobContext)
*/
@Override
public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException {
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
logger.info("Start animate workflow operation for media package {}", mediaPackage);
WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
List<String> arguments;
// Check required options
final File animationFile = new File(StringUtils.trimToEmpty(operation.getConfiguration(ANIMATION_FILE_PROPERTY)));
if (!animationFile.isFile()) {
throw new WorkflowOperationException(String.format("Animation file `%s` does not exist", animationFile));
}
URI animation = animationFile.toURI();
final MediaPackageElementFlavor targetFlavor;
try {
targetFlavor = MediaPackageElementFlavor.parseFlavor(StringUtils.trimToNull(operation.getConfiguration(TARGET_FLAVOR_PROPERTY)));
} catch (IllegalArgumentException e) {
throw new WorkflowOperationException("Invalid target flavor", e);
}
// Get optional options
String targetTagsProperty = StringUtils.trimToNull(operation.getConfiguration(TARGET_TAGS_PROPERTY));
// Check if we have custom command line options
String cmd = operation.getConfiguration(COMMANDLINE_ARGUMENTS_PROPERTY);
if (StringUtils.isNotEmpty(cmd)) {
arguments = Arrays.asList(StringUtils.split(cmd));
} else {
// set default encoding
arguments = new ArrayList<>();
arguments.add("-t");
arguments.add("ffmpeg");
arguments.add("--video-codec");
arguments.add("libx264-lossless");
arguments.add("--video-bitrate");
arguments.add("10000");
addArgumentIfExists(operation, arguments, WIDTH_PROPERTY, "-w");
addArgumentIfExists(operation, arguments, HEIGHT_PROPERTY, "-h");
addArgumentIfExists(operation, arguments, FPS_PROPERTY, "--fps");
}
final Map<String, String> metadata = getMetadata(mediaPackage);
Job job;
try {
job = animateService.animate(animation, metadata, arguments);
} catch (AnimateServiceException e) {
throw new WorkflowOperationException(String.format("Rendering animation from '%s' in media package '%s' failed", animation, mediaPackage), e);
}
if (!waitForStatus(job).isSuccess()) {
throw new WorkflowOperationException(String.format("Animate job for media package '%s' failed", mediaPackage));
}
// put animated clip into media package
try {
URI output = new URI(job.getPayload());
String id = UUID.randomUUID().toString();
InputStream in = workspace.read(output);
URI uri = workspace.put(mediaPackage.getIdentifier().toString(), id, FilenameUtils.getName(output.getPath()), in);
TrackImpl track = new TrackImpl();
track.setIdentifier(id);
track.setFlavor(targetFlavor);
track.setURI(uri);
Job inspection = mediaInspectionService.enrich(track, true);
if (!waitForStatus(inspection).isSuccess()) {
throw new AnimateServiceException(String.format("Animating %s failed", animation));
}
track = (TrackImpl) MediaPackageElementParser.getFromXml(inspection.getPayload());
// add track to media package
for (String tag : asList(targetTagsProperty)) {
track.addTag(tag);
}
mediaPackage.add(track);
workspace.delete(output);
} catch (Exception e) {
throw new WorkflowOperationException("Error handling animation service output", e);
}
try {
workspace.cleanup(mediaPackage.getIdentifier());
} catch (IOException e) {
throw new WorkflowOperationException(e);
}
logger.info("Animate workflow operation for media package {} completed", mediaPackage);
return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE);
}
use of org.opencastproject.workflow.api.WorkflowOperationException in project opencast by opencast.
the class ThemeWorkflowOperationHandler method start.
/**
* {@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 {
logger.debug("Running theme workflow operation on workflow {}", workflowInstance.getId());
final MediaPackageElementFlavor bumperFlavor = getOptConfig(workflowInstance, BUMPER_FLAVOR).map(toMediaPackageElementFlavor).getOr(new MediaPackageElementFlavor("branding", "bumper"));
final MediaPackageElementFlavor trailerFlavor = getOptConfig(workflowInstance, TRAILER_FLAVOR).map(toMediaPackageElementFlavor).getOr(new MediaPackageElementFlavor("branding", "trailer"));
final MediaPackageElementFlavor titleSlideFlavor = getOptConfig(workflowInstance, TITLE_SLIDE_FLAVOR).map(toMediaPackageElementFlavor).getOr(new MediaPackageElementFlavor("branding", "title-slide"));
final MediaPackageElementFlavor licenseSlideFlavor = getOptConfig(workflowInstance, LICENSE_SLIDE_FLAVOR).map(toMediaPackageElementFlavor).getOr(new MediaPackageElementFlavor("branding", "license-slide"));
final MediaPackageElementFlavor watermarkFlavor = getOptConfig(workflowInstance, WATERMARK_FLAVOR).map(toMediaPackageElementFlavor).getOr(new MediaPackageElementFlavor("branding", "watermark"));
final List<String> bumperTags = asList(workflowInstance.getConfiguration(BUMPER_TAGS));
final List<String> trailerTags = asList(workflowInstance.getConfiguration(TRAILER_TAGS));
final List<String> titleSlideTags = asList(workflowInstance.getConfiguration(TITLE_SLIDE_TAGS));
final List<String> licenseSlideTags = asList(workflowInstance.getConfiguration(LICENSE_SLIDE_TAGS));
final List<String> watermarkTags = asList(workflowInstance.getConfiguration(WATERMARK_TAGS));
Opt<String> layoutStringOpt = getOptConfig(workflowInstance, WATERMARK_LAYOUT);
Opt<String> watermarkLayoutVariable = getOptConfig(workflowInstance, WATERMARK_LAYOUT_VARIABLE);
List<String> layoutList = new ArrayList<>(Stream.$(layoutStringOpt).bind(Strings.split(";")).toList());
try {
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
String series = mediaPackage.getSeries();
if (series == null) {
logger.info("Skipping theme workflow operation, no series assigned to mediapackage {}", mediaPackage.getIdentifier());
return createResult(Action.SKIP);
}
Long themeId;
try {
themeId = Long.parseLong(seriesService.getSeriesProperty(series, THEME_PROPERTY_NAME));
} catch (NotFoundException e) {
logger.info("Skipping theme workflow operation, no theme assigned to series {} on mediapackage {}.", series, mediaPackage.getIdentifier());
return createResult(Action.SKIP);
} catch (UnauthorizedException e) {
logger.warn("Skipping theme workflow operation, user not authorized to perform operation: {}", ExceptionUtils.getStackTrace(e));
return createResult(Action.SKIP);
}
Theme theme;
try {
theme = themesServiceDatabase.getTheme(themeId);
} catch (NotFoundException e) {
logger.warn("Skipping theme workflow operation, no theme with id {} found.", themeId);
return createResult(Action.SKIP);
}
logger.info("Applying theme {} to mediapackage {}", themeId, mediaPackage.getIdentifier());
/* Make theme settings available to workflow instance */
workflowInstance.setConfiguration(THEME_ACTIVE, Boolean.toString(theme.isBumperActive() || theme.isTrailerActive() || theme.isTitleSlideActive() || theme.isWatermarkActive()));
workflowInstance.setConfiguration(THEME_BUMPER_ACTIVE, Boolean.toString(theme.isBumperActive()));
workflowInstance.setConfiguration(THEME_TRAILER_ACTIVE, Boolean.toString(theme.isTrailerActive()));
workflowInstance.setConfiguration(THEME_TITLE_SLIDE_ACTIVE, Boolean.toString(theme.isTitleSlideActive()));
workflowInstance.setConfiguration(THEME_TITLE_SLIDE_UPLOADED, Boolean.toString(StringUtils.isNotBlank(theme.getTitleSlideBackground())));
workflowInstance.setConfiguration(THEME_WATERMARK_ACTIVE, Boolean.toString(theme.isWatermarkActive()));
if (theme.isBumperActive() && StringUtils.isNotBlank(theme.getBumperFile())) {
try (InputStream bumper = staticFileService.getFile(theme.getBumperFile())) {
addElement(mediaPackage, bumperFlavor, bumperTags, bumper, staticFileService.getFileName(theme.getBumperFile()), Type.Track);
} catch (NotFoundException e) {
logger.warn("Bumper file {} not found in static file service, skip applying it", theme.getBumperFile());
}
}
if (theme.isTrailerActive() && StringUtils.isNotBlank(theme.getTrailerFile())) {
try (InputStream trailer = staticFileService.getFile(theme.getTrailerFile())) {
addElement(mediaPackage, trailerFlavor, trailerTags, trailer, staticFileService.getFileName(theme.getTrailerFile()), Type.Track);
} catch (NotFoundException e) {
logger.warn("Trailer file {} not found in static file service, skip applying it", theme.getTrailerFile());
}
}
if (theme.isTitleSlideActive()) {
if (StringUtils.isNotBlank(theme.getTitleSlideBackground())) {
try (InputStream titleSlideBackground = staticFileService.getFile(theme.getTitleSlideBackground())) {
addElement(mediaPackage, titleSlideFlavor, titleSlideTags, titleSlideBackground, staticFileService.getFileName(theme.getTitleSlideBackground()), Type.Attachment);
} catch (NotFoundException e) {
logger.warn("Title slide file {} not found in static file service, skip applying it", theme.getTitleSlideBackground());
}
}
// TODO add the title slide metadata to the workflow properties to be used by the cover-image WOH
// String titleSlideMetadata = theme.getTitleSlideMetadata();
}
if (theme.isLicenseSlideActive()) {
if (StringUtils.isNotBlank(theme.getLicenseSlideBackground())) {
try (InputStream licenseSlideBackground = staticFileService.getFile(theme.getLicenseSlideBackground())) {
addElement(mediaPackage, licenseSlideFlavor, licenseSlideTags, licenseSlideBackground, staticFileService.getFileName(theme.getLicenseSlideBackground()), Type.Attachment);
} catch (NotFoundException e) {
logger.warn("License slide file {} not found in static file service, skip applying it", theme.getLicenseSlideBackground());
}
} else {
// TODO define what to do here (maybe extract image as background)
}
// TODO add the license slide description to the workflow properties to be used by the cover-image WOH
// String licenseSlideDescription = theme.getLicenseSlideDescription();
}
if (theme.isWatermarkActive() && StringUtils.isNotBlank(theme.getWatermarkFile())) {
try (InputStream watermark = staticFileService.getFile(theme.getWatermarkFile())) {
addElement(mediaPackage, watermarkFlavor, watermarkTags, watermark, staticFileService.getFileName(theme.getWatermarkFile()), Type.Attachment);
} catch (NotFoundException e) {
logger.warn("Watermark file {} not found in static file service, skip applying it", theme.getWatermarkFile());
}
if (layoutStringOpt.isNone() || watermarkLayoutVariable.isNone())
throw new WorkflowOperationException(format("Configuration key '%s' or '%s' is either missing or empty", WATERMARK_LAYOUT, WATERMARK_LAYOUT_VARIABLE));
AbsolutePositionLayoutSpec watermarkLayout = parseLayout(theme.getWatermarkPosition());
layoutList.set(layoutList.size() - 1, Serializer.json(watermarkLayout).toJson());
layoutStringOpt = Opt.some(Stream.$(layoutList).mkString(";"));
}
if (watermarkLayoutVariable.isSome() && layoutStringOpt.isSome())
workflowInstance.setConfiguration(watermarkLayoutVariable.get(), layoutStringOpt.get());
return createResult(mediaPackage, Action.CONTINUE);
} catch (SeriesException | ThemesServiceDatabaseException | IllegalStateException | IllegalArgumentException | IOException e) {
throw new WorkflowOperationException(e);
}
}
use of org.opencastproject.workflow.api.WorkflowOperationException in project opencast by opencast.
the class TextAnalysisWorkflowOperationHandler method start.
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance,
* JobContext)
*/
@Override
public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException {
logger.debug("Running segments preview workflow operation on {}", workflowInstance);
// Check if there is an mpeg-7 catalog containing video segments
MediaPackage src = (MediaPackage) workflowInstance.getMediaPackage().clone();
Catalog[] segmentCatalogs = src.getCatalogs(MediaPackageElements.SEGMENTS);
if (segmentCatalogs.length == 0) {
logger.info("Media package {} does not contain segment information", src);
return createResult(Action.CONTINUE);
}
try {
return extractVideoText(src, workflowInstance.getCurrentOperation());
} catch (Exception e) {
throw new WorkflowOperationException(e);
}
}
use of org.opencastproject.workflow.api.WorkflowOperationException in project opencast by opencast.
the class AttachTranscriptionOperationHandler method start.
/**
* {@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 {
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
WorkflowOperationInstance operation = workflowInstance.getCurrentOperation();
logger.debug("Attach transcription for mediapackage {} started", mediaPackage);
// Get job id.
String jobId = StringUtils.trimToNull(operation.getConfiguration(TRANSCRIPTION_JOB_ID));
if (jobId == null)
throw new WorkflowOperationException(TRANSCRIPTION_JOB_ID + " missing");
// Check which tags/flavors have been configured
String targetTagOption = StringUtils.trimToNull(operation.getConfiguration(TARGET_TAG));
String targetFlavorOption = StringUtils.trimToNull(operation.getConfiguration(TARGET_FLAVOR));
String captionFormatOption = StringUtils.trimToNull(operation.getConfiguration(TARGET_CAPTION_FORMAT));
// Target flavor is mandatory if target-caption-format was NOT informed and no conversion is done
if (targetFlavorOption == null && captionFormatOption == null)
throw new WorkflowOperationException(TARGET_FLAVOR + " missing");
// Target flavor is optional if target-caption-format was informed because the default flavor
// will be "captions/<format>". If informed, will override the default.
MediaPackageElementFlavor flavor = null;
if (targetFlavorOption != null)
flavor = MediaPackageElementFlavor.parseFlavor(targetFlavorOption);
try {
// Get transcription file from the service
MediaPackageElement original = service.getGeneratedTranscription(mediaPackage.getIdentifier().compact(), jobId);
MediaPackageElement transcription = original;
// If caption format passed, convert to desired format
if (captionFormatOption != null) {
Job job = captionService.convert(transcription, "ibm-watson", captionFormatOption, service.getLanguage());
if (!waitForStatus(job).isSuccess()) {
throw new WorkflowOperationException("Transcription format conversion job did not complete successfully");
}
transcription = MediaPackageElementParser.getFromXml(job.getPayload());
}
// Set the target flavor if informed
if (flavor != null)
transcription.setFlavor(flavor);
// Add tags
if (targetTagOption != null) {
for (String tag : asList(targetTagOption)) {
if (StringUtils.trimToNull(tag) != null)
transcription.addTag(tag);
}
}
// Add to media package
mediaPackage.add(transcription);
String uri = transcription.getURI().toString();
String ext = uri.substring(uri.lastIndexOf("."));
transcription.setURI(workspace.moveTo(transcription.getURI(), mediaPackage.getIdentifier().toString(), transcription.getIdentifier(), "captions." + ext));
} catch (Exception e) {
throw new WorkflowOperationException(e);
}
return createResult(mediaPackage, Action.CONTINUE);
}
Aggregations