/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.workflow.handler.workflow;
import org.opencastproject.job.api.JobContext;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.Track;
import org.opencastproject.mediapackage.VideoStream;
import org.opencastproject.mediapackage.track.TrackImpl;
import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowOperationException;
import org.opencastproject.workflow.api.WorkflowOperationResult;
import org.opencastproject.workflow.api.WorkflowOperationResult.Action;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.Fraction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Workflow operation handler for analyzing tracks and set control variables.
*/
public class AnalyzeTracksWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
/** Configuration key for the "flavor" of the tracks to use as a source input */
static final String OPT_SOURCE_FLAVOR = "source-flavor";
/** Configuration key for video aspect ratio to check */
static final String OPT_VIDEO_ASPECT = "aspect-ratio";
/** Configuration key to define behavior if no track matches */
static final String OPT_FAIL_NO_TRACK = "fail-no-track";
/** The logging facility */
private static final Logger logger = LoggerFactory
.getLogger(AnalyzeTracksWorkflowOperationHandler.class);
@Override
public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context)
throws WorkflowOperationException {
logger.info("Running analyze-tracks workflow operation on workflow {}", workflowInstance.getId());
final MediaPackage mediaPackage = workflowInstance.getMediaPackage();
final String sourceFlavor = getConfig(workflowInstance, OPT_SOURCE_FLAVOR);
Map<String, String> properties = new HashMap<>();
final MediaPackageElementFlavor flavor = MediaPackageElementFlavor.parseFlavor(sourceFlavor);
final Track[] tracks = mediaPackage.getTracks(flavor);
if (tracks.length <= 0) {
if (BooleanUtils.toBoolean(getConfig(workflowInstance, OPT_FAIL_NO_TRACK, "false"))) {
throw new WorkflowOperationException("No matching tracks for flavor " + sourceFlavor);
}
logger.info("No tracks with specified flavors ({}) to analyse.", sourceFlavor);
return createResult(mediaPackage, properties, Action.CONTINUE, 0);
}
List<Fraction> aspectRatios = getAspectRatio(getConfig(workflowInstance, OPT_VIDEO_ASPECT, ""));
for (Track track : tracks) {
final String varName = toVariableName(track.getFlavor());
properties.put(varName + "_media", "true");
properties.put(varName + "_video", Boolean.toString(track.hasVideo()));
properties.put(varName + "_audio", Boolean.toString(track.hasAudio()));
// Check resolution
if (track.hasVideo()) {
for (VideoStream video: ((TrackImpl) track).getVideo()) {
// Set resolution variables
properties.put(varName + "_resolution_x", video.getFrameWidth().toString());
properties.put(varName + "_resolution_y", video.getFrameHeight().toString());
Fraction trackAspect = Fraction.getReducedFraction(video.getFrameWidth(), video.getFrameHeight());
properties.put(varName + "_aspect", trackAspect.toString());
// Check if we should fall back to nearest defined aspect ratio
if (!aspectRatios.isEmpty()) {
trackAspect = getNearestAspectRatio(trackAspect, aspectRatios);
properties.put(varName + "_aspect_snap", trackAspect.toString());
}
}
}
}
logger.info("Finished analyze-tracks workflow operation adding the properties: {}", properties);
return createResult(mediaPackage, properties, Action.CONTINUE, 0);
}
/**
* Get nearest aspect ratio from list
*
* @param videoAspect
* Aspect ratio of video to check
* @param aspects
* List of aspect ratios to snap to.
* @return Nearest aspect ratio
*/
Fraction getNearestAspectRatio(final Fraction videoAspect, final List<Fraction> aspects) {
Fraction nearestAspect = aspects.get(0);
for (Fraction aspect: aspects) {
if (videoAspect.subtract(nearestAspect).abs().compareTo(videoAspect.subtract(aspect).abs()) > 0) {
nearestAspect = aspect;
}
}
return nearestAspect;
}
/**
* Get aspect ratios to check from configuration string.
*
* @param aspectConfig
* Configuration string
* @return List of aspect rations to check
*/
List<Fraction> getAspectRatio(String aspectConfig) {
List<Fraction> aspectRatios = new ArrayList<>();
for (String aspect: aspectConfig.split(" *, *")) {
if (StringUtils.isNotBlank(aspect)) {
aspectRatios.add(Fraction.getFraction(aspect).reduce());
}
}
return aspectRatios;
}
/** Create a name for a workflow variable from a flavor */
private static String toVariableName(final MediaPackageElementFlavor flavor) {
return flavor.getType() + "_" + flavor.getSubtype();
}
}