Search in sources :

Example 6 with Dimension

use of org.opencastproject.composer.layout.Dimension in project opencast by opencast.

the class ConcatWorkflowOperationHandler method concat.

private WorkflowOperationResult concat(MediaPackage src, WorkflowOperationInstance operation) throws EncoderException, IOException, NotFoundException, MediaPackageException, WorkflowOperationException {
    MediaPackage mediaPackage = (MediaPackage) src.clone();
    Map<Integer, Tuple<TrackSelector, Boolean>> trackSelectors = getTrackSelectors(operation);
    String outputResolution = StringUtils.trimToNull(operation.getConfiguration(OUTPUT_RESOLUTION));
    String outputFrameRate = StringUtils.trimToNull(operation.getConfiguration(OUTPUT_FRAMERATE));
    String encodingProfile = StringUtils.trimToNull(operation.getConfiguration(ENCODING_PROFILE));
    // Skip the worklow if no source-flavors or tags has been configured
    if (trackSelectors.isEmpty()) {
        logger.warn("No source-tags or source-flavors has been set.");
        return createResult(mediaPackage, Action.SKIP);
    }
    String targetTagsOption = StringUtils.trimToNull(operation.getConfiguration(TARGET_TAGS));
    String targetFlavorOption = StringUtils.trimToNull(operation.getConfiguration(TARGET_FLAVOR));
    // Target tags
    List<String> targetTags = asList(targetTagsOption);
    // Target flavor
    if (targetFlavorOption == null)
        throw new WorkflowOperationException("Target flavor must be set!");
    // Find the encoding profile
    if (encodingProfile == null)
        throw new WorkflowOperationException("Encoding profile must be set!");
    EncodingProfile profile = composerService.getProfile(encodingProfile);
    if (profile == null)
        throw new WorkflowOperationException("Encoding profile '" + encodingProfile + "' was not found");
    // Output resolution
    if (outputResolution == null)
        throw new WorkflowOperationException("Output resolution must be set!");
    Dimension outputDimension = null;
    if (outputResolution.startsWith(OUTPUT_PART_PREFIX)) {
        if (!trackSelectors.keySet().contains(Integer.parseInt(outputResolution.substring(OUTPUT_PART_PREFIX.length()))))
            throw new WorkflowOperationException("Output resolution part not set!");
    } else {
        try {
            String[] outputResolutionArray = StringUtils.split(outputResolution, "x");
            if (outputResolutionArray.length != 2) {
                throw new WorkflowOperationException("Invalid format of output resolution!");
            }
            outputDimension = Dimension.dimension(Integer.parseInt(outputResolutionArray[0]), Integer.parseInt(outputResolutionArray[1]));
        } catch (WorkflowOperationException e) {
            throw e;
        } catch (Exception e) {
            throw new WorkflowOperationException("Unable to parse output resolution!", e);
        }
    }
    float fps = -1.0f;
    if (StringUtils.isNotEmpty(outputFrameRate)) {
        if (StringUtils.startsWith(outputFrameRate, OUTPUT_PART_PREFIX)) {
            if (!NumberUtils.isNumber(outputFrameRate.substring(OUTPUT_PART_PREFIX.length())) || !trackSelectors.keySet().contains(Integer.parseInt(outputFrameRate.substring(OUTPUT_PART_PREFIX.length())))) {
                throw new WorkflowOperationException("Output frame rate part not set or invalid!");
            }
        } else if (NumberUtils.isNumber(outputFrameRate)) {
            fps = NumberUtils.toFloat(outputFrameRate);
        } else {
            throw new WorkflowOperationException("Unable to parse output frame rate!");
        }
    }
    MediaPackageElementFlavor targetFlavor = null;
    try {
        targetFlavor = MediaPackageElementFlavor.parseFlavor(targetFlavorOption);
        if ("*".equals(targetFlavor.getType()) || "*".equals(targetFlavor.getSubtype()))
            throw new WorkflowOperationException("Target flavor must have a type and a subtype, '*' are not allowed!");
    } catch (IllegalArgumentException e) {
        throw new WorkflowOperationException("Target flavor '" + targetFlavorOption + "' is malformed");
    }
    List<Track> tracks = new ArrayList<Track>();
    for (Entry<Integer, Tuple<TrackSelector, Boolean>> trackSelector : trackSelectors.entrySet()) {
        Collection<Track> tracksForSelector = trackSelector.getValue().getA().select(mediaPackage, false);
        String currentFlavor = StringUtils.join(trackSelector.getValue().getA().getFlavors());
        String currentTag = StringUtils.join(trackSelector.getValue().getA().getTags());
        if (tracksForSelector.size() > 1) {
            logger.warn("More than one track has been found with flavor '{}' and/or tag '{}' for concat operation, skipping concatenation!", currentFlavor, currentTag);
            return createResult(mediaPackage, Action.SKIP);
        } else if (tracksForSelector.size() == 0 && trackSelector.getValue().getB()) {
            logger.warn("No track has been found with flavor '{}' and/or tag '{}' for concat operation, skipping concatenation!", currentFlavor, currentTag);
            return createResult(mediaPackage, Action.SKIP);
        } else if (tracksForSelector.size() == 0 && !trackSelector.getValue().getB()) {
            logger.info("No track has been found with flavor '{}' and/or tag '{}' for concat operation, skipping track!", currentFlavor, currentTag);
            continue;
        }
        for (Track t : tracksForSelector) {
            tracks.add(t);
            VideoStream[] videoStreams = TrackSupport.byType(t.getStreams(), VideoStream.class);
            if (videoStreams.length == 0) {
                logger.info("No video stream available in the track with flavor {}! {}", currentFlavor, t);
                return createResult(mediaPackage, Action.SKIP);
            }
            if (StringUtils.startsWith(outputResolution, OUTPUT_PART_PREFIX) && NumberUtils.isNumber(outputResolution.substring(OUTPUT_PART_PREFIX.length())) && trackSelector.getKey() == Integer.parseInt(outputResolution.substring(OUTPUT_PART_PREFIX.length()))) {
                outputDimension = new Dimension(videoStreams[0].getFrameWidth(), videoStreams[0].getFrameHeight());
                if (!trackSelector.getValue().getB()) {
                    logger.warn("Output resolution track {} must be mandatory, skipping concatenation!", outputResolution);
                    return createResult(mediaPackage, Action.SKIP);
                }
            }
            if (fps <= 0 && StringUtils.startsWith(outputFrameRate, OUTPUT_PART_PREFIX) && NumberUtils.isNumber(outputFrameRate.substring(OUTPUT_PART_PREFIX.length())) && trackSelector.getKey() == Integer.parseInt(outputFrameRate.substring(OUTPUT_PART_PREFIX.length()))) {
                fps = videoStreams[0].getFrameRate();
            }
        }
    }
    if (tracks.size() == 0) {
        logger.warn("No tracks found for concating operation, skipping concatenation!");
        return createResult(mediaPackage, Action.SKIP);
    } else if (tracks.size() == 1) {
        Track track = (Track) tracks.get(0).clone();
        track.setIdentifier(null);
        addNewTrack(mediaPackage, track, targetTags, targetFlavor);
        logger.info("At least two tracks are needed for the concating operation, skipping concatenation!");
        return createResult(mediaPackage, Action.SKIP);
    }
    Job concatJob;
    if (fps > 0) {
        concatJob = composerService.concat(profile.getIdentifier(), outputDimension, fps, tracks.toArray(new Track[tracks.size()]));
    } else {
        concatJob = composerService.concat(profile.getIdentifier(), outputDimension, tracks.toArray(new Track[tracks.size()]));
    }
    // Wait for the jobs to return
    if (!waitForStatus(concatJob).isSuccess())
        throw new WorkflowOperationException("The concat job did not complete successfully");
    if (concatJob.getPayload().length() > 0) {
        Track concatTrack = (Track) MediaPackageElementParser.getFromXml(concatJob.getPayload());
        concatTrack.setURI(workspace.moveTo(concatTrack.getURI(), mediaPackage.getIdentifier().toString(), concatTrack.getIdentifier(), "concat." + FilenameUtils.getExtension(concatTrack.getURI().toString())));
        addNewTrack(mediaPackage, concatTrack, targetTags, targetFlavor);
        WorkflowOperationResult result = createResult(mediaPackage, Action.CONTINUE, concatJob.getQueueTime());
        logger.debug("Concat operation completed");
        return result;
    } else {
        logger.info("concat operation unsuccessful, no payload returned: {}", concatJob);
        return createResult(mediaPackage, Action.SKIP);
    }
}
Also used : ArrayList(java.util.ArrayList) VideoStream(org.opencastproject.mediapackage.VideoStream) EncodingProfile(org.opencastproject.composer.api.EncodingProfile) Dimension(org.opencastproject.composer.layout.Dimension) MediaPackageElementFlavor(org.opencastproject.mediapackage.MediaPackageElementFlavor) WorkflowOperationException(org.opencastproject.workflow.api.WorkflowOperationException) NotFoundException(org.opencastproject.util.NotFoundException) IOException(java.io.IOException) MediaPackageException(org.opencastproject.mediapackage.MediaPackageException) EncoderException(org.opencastproject.composer.api.EncoderException) WorkflowOperationResult(org.opencastproject.workflow.api.WorkflowOperationResult) MediaPackage(org.opencastproject.mediapackage.MediaPackage) WorkflowOperationException(org.opencastproject.workflow.api.WorkflowOperationException) Job(org.opencastproject.job.api.Job) Tuple(org.opencastproject.util.data.Tuple) Track(org.opencastproject.mediapackage.Track)

Example 7 with Dimension

use of org.opencastproject.composer.layout.Dimension in project opencast by opencast.

the class ComposerServiceTest method testConcat.

/**
 * Test method for {@link ComposerServiceImpl#concat(String, Dimension, Track...)}
 */
@Test
public void testConcat() throws Exception {
    Dimension outputDimension = new Dimension(500, 500);
    Job concat = composerService.concat("concat.work", outputDimension, sourceVideoTrack, sourceVideoTrack);
    Track concatTrack = (Track) MediaPackageElementParser.getFromXml(concat.getPayload());
    Assert.assertNotNull(concatTrack);
    inspectedTrack.setIdentifier(concatTrack.getIdentifier());
    inspectedTrack.setMimeType(MimeType.mimeType("video", "mp4"));
    Assert.assertEquals(inspectedTrack, concatTrack);
}
Also used : Dimension(org.opencastproject.composer.layout.Dimension) Job(org.opencastproject.job.api.Job) Track(org.opencastproject.mediapackage.Track) Test(org.junit.Test)

Example 8 with Dimension

use of org.opencastproject.composer.layout.Dimension in project opencast by opencast.

the class ComposerRestServiceTest method testConcat.

@Test
public void testConcat() throws Exception {
    Dimension dimension = new Dimension(640, 480);
    Track videoTrack = (Track) MediaPackageElementParser.getFromXml(generateVideoTrack());
    String sourceTracks = MediaPackageElementParser.getArrayAsXml(Collections.list(videoTrack, videoTrack));
    Response response = restService.concat(sourceTracks, profileId, Serializer.json(dimension).toJson(), "25");
    Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
    Assert.assertNotNull("Concat rest endpoint should send a job in response", response.getEntity());
}
Also used : Response(javax.ws.rs.core.Response) Dimension(org.opencastproject.composer.layout.Dimension) Track(org.opencastproject.mediapackage.Track) Test(org.junit.Test)

Example 9 with Dimension

use of org.opencastproject.composer.layout.Dimension 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);
    }
}
Also used : ArrayList(java.util.ArrayList) Attachment(org.opencastproject.mediapackage.Attachment) LaidOutElement(org.opencastproject.composer.api.LaidOutElement) Dimension(org.opencastproject.composer.layout.Dimension) ServiceRegistryException(org.opencastproject.serviceregistry.api.ServiceRegistryException) ServiceRegistryException(org.opencastproject.serviceregistry.api.ServiceRegistryException) MediaInspectionException(org.opencastproject.inspection.api.MediaInspectionException) MediaPackageException(org.opencastproject.mediapackage.MediaPackageException) NotFoundException(org.opencastproject.util.NotFoundException) IOException(java.io.IOException) EncoderException(org.opencastproject.composer.api.EncoderException) Layout(org.opencastproject.composer.layout.Layout) Track(org.opencastproject.mediapackage.Track)

Example 10 with Dimension

use of org.opencastproject.composer.layout.Dimension in project opencast by opencast.

the class ComposerRestService method concat.

/**
 * Concat multiple tracks having the same codec to a single track.
 *
 * @param sourceTracksXml
 *          an array of track to concat in order of the array as XML
 * @param profileId
 *          The encoding profile to use
 * @param outputDimension
 *          The output dimension as JSON
 * @return A {@link Response} with the resulting track in the response body
 * @throws Exception
 */
@POST
@Path("concat")
@Produces(MediaType.TEXT_XML)
@RestQuery(name = "concat", description = "Starts a video concating process from multiple videos, based on the specified encoding profile ID and the source tracks", restParameters = { @RestParameter(description = "The source tracks to concat as XML", isRequired = true, name = "sourceTracks", type = Type.TEXT), @RestParameter(description = "The encoding profile to use", isRequired = true, name = "profileId", type = Type.STRING), @RestParameter(description = "The resolution dimension of the concat video as JSON", isRequired = false, name = "outputDimension", type = Type.STRING), @RestParameter(description = "The  frame rate of the concat video (should be positive, e.g. 25.0). Negative values and zero will deactivate frame rate operation.", isRequired = false, name = "outputFrameRate", type = Type.STRING) }, reponses = { @RestResponse(description = "Results in an xml document containing the video track", responseCode = HttpServletResponse.SC_OK), @RestResponse(description = "If required parameters aren't set or if sourceTracks aren't from the type Track or not at least two tracks are present", responseCode = HttpServletResponse.SC_BAD_REQUEST) }, returnDescription = "")
public Response concat(@FormParam("sourceTracks") String sourceTracksXml, @FormParam("profileId") String profileId, @FormParam("outputDimension") String outputDimension, @FormParam("outputFrameRate") String outputFrameRate) throws Exception {
    // Ensure that the POST parameters are present
    if (StringUtils.isBlank(sourceTracksXml) || StringUtils.isBlank(profileId))
        return Response.status(Response.Status.BAD_REQUEST).entity("sourceTracks and profileId must not be null").build();
    // Deserialize the source track
    List<? extends MediaPackageElement> tracks = MediaPackageElementParser.getArrayFromXml(sourceTracksXml);
    if (tracks.size() < 2)
        return Response.status(Response.Status.BAD_REQUEST).entity("At least two tracks must be set to concat").build();
    for (MediaPackageElement elem : tracks) {
        if (!Track.TYPE.equals(elem.getElementType()))
            return Response.status(Response.Status.BAD_REQUEST).entity("sourceTracks must be of type track").build();
    }
    float fps = NumberUtils.toFloat(outputFrameRate, -1.0f);
    try {
        // Asynchronously concat the specified tracks together
        Dimension dimension = null;
        if (StringUtils.isNotBlank(outputDimension)) {
            dimension = Serializer.dimension(JsonObj.jsonObj(outputDimension));
        }
        Job job = null;
        if (fps > 0) {
            job = composerService.concat(profileId, dimension, fps, tracks.toArray(new Track[tracks.size()]));
        } else {
            job = composerService.concat(profileId, dimension, tracks.toArray(new Track[tracks.size()]));
        }
        return Response.ok().entity(new JaxbJob(job)).build();
    } catch (EncoderException e) {
        logger.warn("Unable to concat videos: " + e.getMessage());
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
    }
}
Also used : EncoderException(org.opencastproject.composer.api.EncoderException) MediaPackageElement(org.opencastproject.mediapackage.MediaPackageElement) JaxbJob(org.opencastproject.job.api.JaxbJob) Dimension(org.opencastproject.composer.layout.Dimension) JaxbJob(org.opencastproject.job.api.JaxbJob) Job(org.opencastproject.job.api.Job) Path(javax.ws.rs.Path) POST(javax.ws.rs.POST) Produces(javax.ws.rs.Produces) RestQuery(org.opencastproject.util.doc.rest.RestQuery)

Aggregations

Dimension (org.opencastproject.composer.layout.Dimension)14 Track (org.opencastproject.mediapackage.Track)11 Job (org.opencastproject.job.api.Job)8 ArrayList (java.util.ArrayList)7 LaidOutElement (org.opencastproject.composer.api.LaidOutElement)6 EncoderException (org.opencastproject.composer.api.EncoderException)5 Tuple (org.opencastproject.util.data.Tuple)5 Test (org.junit.Test)4 MultiShapeLayout (org.opencastproject.composer.layout.MultiShapeLayout)4 VideoStream (org.opencastproject.mediapackage.VideoStream)4 WorkflowOperationException (org.opencastproject.workflow.api.WorkflowOperationException)4 IOException (java.io.IOException)3 HorizontalCoverageLayoutSpec (org.opencastproject.composer.layout.HorizontalCoverageLayoutSpec)3 MediaPackageException (org.opencastproject.mediapackage.MediaPackageException)3 NotFoundException (org.opencastproject.util.NotFoundException)3 WorkflowOperationResult (org.opencastproject.workflow.api.WorkflowOperationResult)3 POST (javax.ws.rs.POST)2 Path (javax.ws.rs.Path)2 Produces (javax.ws.rs.Produces)2 EncodingProfile (org.opencastproject.composer.api.EncodingProfile)2