use of org.opencastproject.composer.api.LaidOutElement in project opencast by opencast.
the class ComposerRestService method composite.
/**
* Compose two videos into one with an optional watermark.
*
* @param compositeSizeJson
* The composite track dimension as JSON
* @param lowerTrackXml
* The lower track of the composition as XML
* @param lowerLayoutJson
* The lower layout as JSON
* @param upperTrackXml
* The upper track of the composition as XML
* @param upperLayoutJson
* The upper layout as JSON
* @param watermarkAttachmentXml
* The watermark image attachment of the composition as XML
* @param watermarkLayoutJson
* The watermark layout as JSON
* @param profileId
* The encoding profile to use
* @param background
* The background color
* @return A {@link Response} with the resulting track in the response body
* @throws Exception
*/
@POST
@Path("composite")
@Produces(MediaType.TEXT_XML)
@RestQuery(name = "composite", description = "Starts a video compositing process, based on the specified resolution, encoding profile ID, the source elements and their layouts", restParameters = { @RestParameter(description = "The resolution size of the resulting video as JSON", isRequired = true, name = "compositeSize", type = Type.STRING), @RestParameter(description = "The lower source track containing the lower video", isRequired = true, name = "lowerTrack", type = Type.TEXT), @RestParameter(description = "The lower layout containing the JSON definition of the layout", isRequired = true, name = "lowerLayout", type = Type.TEXT), @RestParameter(description = "The upper source track containing the upper video", isRequired = false, name = "upperTrack", type = Type.TEXT), @RestParameter(description = "The upper layout containing the JSON definition of the layout", isRequired = false, name = "upperLayout", type = Type.TEXT), @RestParameter(description = "The watermark source attachment containing watermark image", isRequired = false, name = "watermarkTrack", type = Type.TEXT), @RestParameter(description = "The watermark layout containing the JSON definition of the layout", isRequired = false, name = "watermarkLayout", type = Type.TEXT), @RestParameter(description = "The background color", isRequired = false, name = "background", type = Type.TEXT, defaultValue = "black"), @RestParameter(description = "The encoding profile to use", isRequired = true, name = "profileId", type = Type.STRING) }, reponses = { @RestResponse(description = "Results in an xml document containing the compound video track", responseCode = HttpServletResponse.SC_OK), @RestResponse(description = "If required parameters aren't set or if the source elements aren't from the right type", responseCode = HttpServletResponse.SC_BAD_REQUEST) }, returnDescription = "")
public Response composite(@FormParam("compositeSize") String compositeSizeJson, @FormParam("lowerTrack") String lowerTrackXml, @FormParam("lowerLayout") String lowerLayoutJson, @FormParam("upperTrack") String upperTrackXml, @FormParam("upperLayout") String upperLayoutJson, @FormParam("watermarkAttachment") String watermarkAttachmentXml, @FormParam("watermarkLayout") String watermarkLayoutJson, @FormParam("profileId") String profileId, @FormParam("background") @DefaultValue("black") String background) throws Exception {
// Ensure that the POST parameters are present
if (StringUtils.isBlank(compositeSizeJson) || StringUtils.isBlank(lowerTrackXml) || StringUtils.isBlank(lowerLayoutJson) || StringUtils.isBlank(profileId))
return Response.status(Response.Status.BAD_REQUEST).entity("One of the required parameters must not be null").build();
// Deserialize the source elements
MediaPackageElement lowerTrack = MediaPackageElementParser.getFromXml(lowerTrackXml);
Layout lowerLayout = Serializer.layout(JsonObj.jsonObj(lowerLayoutJson));
if (!Track.TYPE.equals(lowerTrack.getElementType()))
return Response.status(Response.Status.BAD_REQUEST).entity("lowerTrack element must be of type track").build();
LaidOutElement<Track> lowerLaidOutElement = new LaidOutElement<Track>((Track) lowerTrack, lowerLayout);
Option<LaidOutElement<Track>> upperLaidOutElement = Option.<LaidOutElement<Track>>none();
if (StringUtils.isNotBlank(upperTrackXml)) {
MediaPackageElement upperTrack = MediaPackageElementParser.getFromXml(upperTrackXml);
Layout upperLayout = Serializer.layout(JsonObj.jsonObj(upperLayoutJson));
if (!Track.TYPE.equals(upperTrack.getElementType())) {
return Response.status(Response.Status.BAD_REQUEST).entity("upperTrack element must be of type track").build();
}
upperLaidOutElement = Option.option(new LaidOutElement<Track>((Track) upperTrack, upperLayout));
}
Option<LaidOutElement<Attachment>> watermarkLaidOutElement = Option.<LaidOutElement<Attachment>>none();
if (StringUtils.isNotBlank(watermarkAttachmentXml)) {
Layout watermarkLayout = Serializer.layout(JsonObj.jsonObj(watermarkLayoutJson));
MediaPackageElement watermarkAttachment = MediaPackageElementParser.getFromXml(watermarkAttachmentXml);
if (!Attachment.TYPE.equals(watermarkAttachment.getElementType()))
return Response.status(Response.Status.BAD_REQUEST).entity("watermarkTrack element must be of type track").build();
watermarkLaidOutElement = Option.some(new LaidOutElement<Attachment>((Attachment) watermarkAttachment, watermarkLayout));
}
Dimension compositeTrackSize = Serializer.dimension(JsonObj.jsonObj(compositeSizeJson));
try {
// Asynchronously composite the specified source elements
Job job = composerService.composite(compositeTrackSize, upperLaidOutElement, lowerLaidOutElement, watermarkLaidOutElement, profileId, background);
return Response.ok().entity(new JaxbJob(job)).build();
} catch (EncoderException e) {
logger.warn("Unable to composite video: " + e.getMessage());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
}
use of org.opencastproject.composer.api.LaidOutElement in project opencast by opencast.
the class ComposerServiceTest method testComposite.
/**
* Test method for
* {@link ComposerServiceImpl#composite(Dimension, Option, LaidOutElement, Option, String, String)}
*/
@Test
public void testComposite() throws Exception {
if (!ffmpegInstalled)
return;
Dimension outputDimension = new Dimension(500, 500);
List<HorizontalCoverageLayoutSpec> layouts = new ArrayList<HorizontalCoverageLayoutSpec>();
layouts.add(Serializer.horizontalCoverageLayoutSpec(JsonObj.jsonObj("{\"horizontalCoverage\":1.0,\"anchorOffset\":{\"referring\":{\"left\":1.0,\"top\":1.0},\"offset\":{\"y\":-20,\"x\":-20},\"reference\":{\"left\":1.0,\"top\":1.0}}}")));
layouts.add(Serializer.horizontalCoverageLayoutSpec(JsonObj.jsonObj("{\"horizontalCoverage\":0.2,\"anchorOffset\":{\"referring\":{\"left\":0.0,\"top\":0.0},\"offset\":{\"y\":-20,\"x\":-20},\"reference\":{\"left\":0.0,\"top\":0.0}}}")));
layouts.add(Serializer.horizontalCoverageLayoutSpec(JsonObj.jsonObj("{\"horizontalCoverage\":1.0,\"anchorOffset\":{\"referring\":{\"left\":1.0,\"top\":0.0},\"offset\":{\"y\":20,\"x\":20},\"reference\":{\"left\":1.0,\"top\":0.0}}}")));
List<Tuple<Dimension, HorizontalCoverageLayoutSpec>> shapes = new ArrayList<>();
shapes.add(0, Tuple.tuple(new Dimension(300, 300), layouts.get(0)));
shapes.add(1, Tuple.tuple(new Dimension(200, 200), layouts.get(1)));
MultiShapeLayout multiShapeLayout = LayoutManager.multiShapeLayout(outputDimension, shapes);
Option<LaidOutElement<Attachment>> watermarkOption = Option.<LaidOutElement<Attachment>>none();
LaidOutElement<Track> lowerLaidOutElement = new LaidOutElement<Track>(sourceVideoTrack, multiShapeLayout.getShapes().get(0));
LaidOutElement<Track> upperLaidOutElement = new LaidOutElement<Track>(sourceVideoTrack, multiShapeLayout.getShapes().get(1));
Job composite = composerService.composite(outputDimension, Option.option(lowerLaidOutElement), upperLaidOutElement, watermarkOption, "composite.work", "black");
Track compositeTrack = (Track) MediaPackageElementParser.getFromXml(composite.getPayload());
Assert.assertNotNull(compositeTrack);
inspectedTrack.setIdentifier(compositeTrack.getIdentifier());
inspectedTrack.setMimeType(MimeType.mimeType("video", "mp4"));
Assert.assertEquals(inspectedTrack, compositeTrack);
}
use of org.opencastproject.composer.api.LaidOutElement in project opencast by opencast.
the class CompositeWorkflowOperationHandler method createWatermarkLaidOutElement.
private Option<LaidOutElement<Attachment>> createWatermarkLaidOutElement(CompositeSettings compositeSettings, Dimension outputDimension, Option<Attachment> watermarkAttachment) throws WorkflowOperationException {
Option<LaidOutElement<Attachment>> watermarkOption = Option.<LaidOutElement<Attachment>>none();
if (watermarkAttachment.isSome() && compositeSettings.getWatermarkLayout().isSome()) {
BufferedImage image;
try {
File watermarkFile = workspace.get(watermarkAttachment.get().getURI());
image = ImageIO.read(watermarkFile);
} catch (Exception e) {
logger.warn("Unable to read the watermark image attachment {}: {}", watermarkAttachment.get().getURI(), e);
throw new WorkflowOperationException("Unable to read the watermark image attachment", e);
}
Dimension imageDimension = Dimension.dimension(image.getWidth(), image.getHeight());
List<Tuple<Dimension, AbsolutePositionLayoutSpec>> watermarkShapes = new ArrayList<Tuple<Dimension, AbsolutePositionLayoutSpec>>();
watermarkShapes.add(0, Tuple.tuple(imageDimension, compositeSettings.getWatermarkLayout().get()));
MultiShapeLayout watermarkLayout = LayoutManager.absoluteMultiShapeLayout(outputDimension, watermarkShapes);
watermarkOption = Option.some(new LaidOutElement<Attachment>(watermarkAttachment.get(), watermarkLayout.getShapes().get(0)));
}
return watermarkOption;
}
use of org.opencastproject.composer.api.LaidOutElement in project opencast by opencast.
the class CompositeWorkflowOperationHandler method handleMultipleTracks.
private WorkflowOperationResult handleMultipleTracks(MediaPackage mediaPackage, WorkflowOperationInstance operation, CompositeSettings compositeSettings, Option<Attachment> watermarkAttachment) throws EncoderException, IOException, NotFoundException, MediaPackageException, WorkflowOperationException {
if (compositeSettings.getMultiSourceLayouts() == null || compositeSettings.getMultiSourceLayouts().size() == 0) {
throw new WorkflowOperationException("Multi video layout must be set! Please verify that you have a " + LAYOUT_MULTIPLE + " or " + LAYOUT + " property in your composite operation in your workflow definition to be able to handle multiple videos");
}
try {
Track upperTrack = compositeSettings.getUpperTrack();
Track lowerTrack = compositeSettings.getLowerTrack();
List<HorizontalCoverageLayoutSpec> layouts = compositeSettings.getMultiSourceLayouts();
VideoStream[] upperVideoStreams = TrackSupport.byType(upperTrack.getStreams(), VideoStream.class);
if (upperVideoStreams.length == 0) {
logger.warn("No video stream available in the upper track! {}", upperTrack);
return createResult(mediaPackage, Action.SKIP);
}
VideoStream[] lowerVideoStreams = TrackSupport.byType(lowerTrack.getStreams(), VideoStream.class);
if (lowerVideoStreams.length == 0) {
logger.warn("No video stream available in the lower track! {}", lowerTrack);
return createResult(mediaPackage, Action.SKIP);
}
// Read the video dimensions from the mediapackage stream information
Dimension upperDimensions = Dimension.dimension(upperVideoStreams[0].getFrameWidth(), upperVideoStreams[0].getFrameHeight());
Dimension lowerDimensions = Dimension.dimension(lowerVideoStreams[0].getFrameWidth(), lowerVideoStreams[0].getFrameHeight());
// Determine dimension of output
Dimension outputDimension = null;
String outputResolutionSource = compositeSettings.getOutputResolutionSource();
if (outputResolutionSource.equals(CompositeSettings.OUTPUT_RESOLUTION_FIXED)) {
outputDimension = compositeSettings.getOutputDimension();
} else if (outputResolutionSource.equals(CompositeSettings.OUTPUT_RESOLUTION_LOWER)) {
outputDimension = lowerDimensions;
} else if (outputResolutionSource.equals(CompositeSettings.OUTPUT_RESOLUTION_UPPER)) {
outputDimension = upperDimensions;
}
// Create the video layout definitions
List<Tuple<Dimension, HorizontalCoverageLayoutSpec>> shapes = new ArrayList<Tuple<Dimension, HorizontalCoverageLayoutSpec>>();
shapes.add(0, Tuple.tuple(lowerDimensions, layouts.get(0)));
shapes.add(1, Tuple.tuple(upperDimensions, layouts.get(1)));
// Calculate the layout
MultiShapeLayout multiShapeLayout = LayoutManager.multiShapeLayout(outputDimension, shapes);
// Create the laid out element for the videos
LaidOutElement<Track> lowerLaidOutElement = new LaidOutElement<Track>(lowerTrack, multiShapeLayout.getShapes().get(0));
LaidOutElement<Track> upperLaidOutElement = new LaidOutElement<Track>(upperTrack, multiShapeLayout.getShapes().get(1));
// Create the optionally laid out element for the watermark
Option<LaidOutElement<Attachment>> watermarkOption = createWatermarkLaidOutElement(compositeSettings, outputDimension, watermarkAttachment);
Job compositeJob = composerService.composite(outputDimension, Option.option(upperLaidOutElement), lowerLaidOutElement, watermarkOption, compositeSettings.getProfile().getIdentifier(), compositeSettings.getOutputBackground());
// Wait for the jobs to return
if (!waitForStatus(compositeJob).isSuccess())
throw new WorkflowOperationException("The composite job did not complete successfully");
if (compositeJob.getPayload().length() > 0) {
Track compoundTrack = (Track) MediaPackageElementParser.getFromXml(compositeJob.getPayload());
compoundTrack.setURI(workspace.moveTo(compoundTrack.getURI(), mediaPackage.getIdentifier().toString(), compoundTrack.getIdentifier(), "composite." + FilenameUtils.getExtension(compoundTrack.getURI().toString())));
// Adjust the target tags
for (String tag : compositeSettings.getTargetTags()) {
logger.trace("Tagging compound track with '{}'", tag);
compoundTrack.addTag(tag);
}
// Adjust the target flavor.
compoundTrack.setFlavor(compositeSettings.getTargetFlavor());
logger.debug("Compound track has flavor '{}'", compoundTrack.getFlavor());
// store new tracks to mediaPackage
mediaPackage.add(compoundTrack);
WorkflowOperationResult result = createResult(mediaPackage, Action.CONTINUE, compositeJob.getQueueTime());
logger.debug("Composite operation completed");
return result;
} else {
logger.info("Composite operation unsuccessful, no payload returned: {}", compositeJob);
return createResult(mediaPackage, Action.SKIP);
}
} finally {
if (compositeSettings.getSourceUrlWatermark() != null)
workspace.deleteFromCollection(COLLECTION, compositeSettings.getWatermarkIdentifier() + "." + FilenameUtils.getExtension(compositeSettings.getSourceUrlWatermark()));
}
}
use of org.opencastproject.composer.api.LaidOutElement in project opencast by opencast.
the class ComposerServiceImpl method process.
/**
* {@inheritDoc}
*
* @see org.opencastproject.job.api.AbstractJobProducer#process(org.opencastproject.job.api.Job)
*/
@Override
protected String process(Job job) throws ServiceRegistryException {
String operation = job.getOperation();
List<String> arguments = job.getArguments();
try {
Operation op = Operation.valueOf(operation);
Track firstTrack;
Track secondTrack;
String encodingProfile = arguments.get(0);
final String serialized;
switch(op) {
case Encode:
firstTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(1));
serialized = encode(job, Collections.map(tuple("video", firstTrack)), encodingProfile).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
case ParallelEncode:
firstTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(1));
serialized = MediaPackageElementParser.getArrayAsXml(parallelEncode(job, firstTrack, encodingProfile));
break;
case Image:
firstTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(1));
List<Attachment> resultingElements;
if (Boolean.parseBoolean(arguments.get(2))) {
double[] times = new double[arguments.size() - 3];
for (int i = 3; i < arguments.size(); i++) {
times[i - 3] = Double.parseDouble(arguments.get(i));
}
resultingElements = image(job, firstTrack, encodingProfile, times);
} else {
Map<String, String> properties = parseProperties(arguments.get(3));
resultingElements = image(job, firstTrack, encodingProfile, properties);
}
serialized = MediaPackageElementParser.getArrayAsXml(resultingElements);
break;
case ImageConversion:
Attachment sourceImage = (Attachment) MediaPackageElementParser.getFromXml(arguments.get(1));
serialized = convertImage(job, sourceImage, encodingProfile).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
case Mux:
firstTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(1));
secondTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(2));
serialized = mux(job, firstTrack, secondTrack, encodingProfile).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
case Trim:
firstTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(1));
long start = Long.parseLong(arguments.get(2));
long duration = Long.parseLong(arguments.get(3));
serialized = trim(job, firstTrack, encodingProfile, start, duration).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
case Composite:
Attachment watermarkAttachment;
firstTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(LOWER_TRACK_INDEX));
Layout lowerLayout = Serializer.layout(JsonObj.jsonObj(arguments.get(LOWER_TRACK_LAYOUT_INDEX)));
LaidOutElement<Track> lowerLaidOutElement = new LaidOutElement<>(firstTrack, lowerLayout);
Option<LaidOutElement<Track>> upperLaidOutElement = Option.none();
if (NOT_AVAILABLE.equals(arguments.get(UPPER_TRACK_INDEX)) && NOT_AVAILABLE.equals(arguments.get(UPPER_TRACK_LAYOUT_INDEX))) {
logger.trace("This composite action does not use a second track.");
} else {
secondTrack = (Track) MediaPackageElementParser.getFromXml(arguments.get(UPPER_TRACK_INDEX));
Layout upperLayout = Serializer.layout(JsonObj.jsonObj(arguments.get(UPPER_TRACK_LAYOUT_INDEX)));
upperLaidOutElement = Option.option(new LaidOutElement<Track>(secondTrack, upperLayout));
}
Dimension compositeTrackSize = Serializer.dimension(JsonObj.jsonObj(arguments.get(COMPOSITE_TRACK_SIZE_INDEX)));
String backgroundColor = arguments.get(BACKGROUND_COLOR_INDEX);
Option<LaidOutElement<Attachment>> watermarkOption = Option.none();
if (arguments.size() == 9) {
watermarkAttachment = (Attachment) MediaPackageElementParser.getFromXml(arguments.get(WATERMARK_INDEX));
Layout watermarkLayout = Serializer.layout(JsonObj.jsonObj(arguments.get(WATERMARK_LAYOUT_INDEX)));
watermarkOption = Option.some(new LaidOutElement<>(watermarkAttachment, watermarkLayout));
}
serialized = composite(job, compositeTrackSize, lowerLaidOutElement, upperLaidOutElement, watermarkOption, encodingProfile, backgroundColor).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
case Concat:
String dimensionString = arguments.get(1);
String frameRateString = arguments.get(2);
Dimension outputDimension = null;
if (StringUtils.isNotBlank(dimensionString))
outputDimension = Serializer.dimension(JsonObj.jsonObj(dimensionString));
float outputFrameRate = NumberUtils.toFloat(frameRateString, -1.0f);
List<Track> tracks = new ArrayList<>();
for (int i = 3; i < arguments.size(); i++) {
tracks.add(i - 3, (Track) MediaPackageElementParser.getFromXml(arguments.get(i)));
}
serialized = concat(job, tracks, encodingProfile, outputDimension, outputFrameRate).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
case ImageToVideo:
Attachment image = (Attachment) MediaPackageElementParser.getFromXml(arguments.get(1));
double time = Double.parseDouble(arguments.get(2));
serialized = imageToVideo(job, image, encodingProfile, time).map(MediaPackageElementParser.getAsXml()).getOrElse("");
break;
default:
throw new IllegalStateException("Don't know how to handle operation '" + operation + "'");
}
return serialized;
} catch (IllegalArgumentException e) {
throw new ServiceRegistryException(String.format("Cannot handle operations of type '%s'", operation), e);
} catch (IndexOutOfBoundsException e) {
throw new ServiceRegistryException(String.format("Invalid arguments for operation '%s'", operation), e);
} catch (Exception e) {
throw new ServiceRegistryException(String.format("Error handling operation '%s'", operation), e);
}
}
Aggregations