All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.cq.dam.video.FFMpegStoryBoardProcess Maven / Gradle / Ivy

package com.day.cq.dam.video;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.jcr.RepositoryException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.handler.ffmpeg.FFMpegWrapper;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.metadata.MetaDataMap;

/**
 * Workflow process that calls FFMPEG on the command line to create a storyboard
 * of a video. A storyboard consists of several key frames extracted from the
 * video. The key frames will be stored as subassets of the video asset. Also, a
 * merged film strip of the key frames will be stored as a storyboard rendition
 * of the video asset.
 * 

* Process arguments / configuration: *

* *

 *    frames:{Number}      The number of key frames to use for the strip. The frames will be evenly distributed
 *                         over the length of the movie. Default number of frames is 10.
 *                              E.g.: frames:10
 * 
 *    start:{Number}       The start position in seconds for the first key frame. Default is 0. Formats supported:
 *                              plain number (seconds)
 *                              E.g.: start:5
 * 
 *    maxWidth:{Number}    The maximum width in px of key frames. Default: 320 px. Aspect ratio is kept.
 * 
 *    maxHeight:{Number}   The maximum height in px of key frames. Default: 240 px. Aspect ratio is kept.
 * 
 *    upScale:{Boolean}    Whether to upscale key frames if the source video is smaller than maxWidth/maxHeight.
 * 
 *    [{time}],[{time}],..  Define number and time stamp of every key frame individually. Using this option
 *                          causes the "frames" and "start" options to be ignored. Time formats supported:
 *                              hh:mm:ss or plain number (seconds)
 *                              E.g.: [00:05:00],[00:10:00]
 *    Examples:
 * 
 *    - Create 10 frames, starting with the first at 12 seconds into the movie.
 *          frames:10,start:12
 * 
 *    - Create 10 frames, each 100 x 80 px, upscaled:
 *          frames:10,maxWidth:100,maxHeight:80,upScale:true
 * 
 *    - Create 5 frames, each at a defined position, each 100 x 80 px, upscaled:
 *          maxWidth:100,maxHeight:80,[00:05:00],[00:10:00],[00:15:00],[00:20:00],[00:25:00]
 * 
*

* Will create thumbnails of size 140x100 and 48x48 with a black * letterbox/pillarbox *

* This will only happen for assets having a video-based mime-type, others are * ignored. * */ @Component(label = "Day CQ DAM FFmpeg Storyboard Process", description = "Workflow process that creates storyboards from video files") @Service @Properties({ @Property(name = "process.label", value = "Create Video Storyboard", propertyPrivate=true) }) public class FFMpegStoryBoardProcess extends AbstractFFMpegProcess { private static final Logger log = LoggerFactory.getLogger(FFMpegStoryBoardProcess.class); /** * The available arguments to this process implementation. */ public enum Arguments { PROCESS_ARGS("PROCESS_ARGS"), FRAME_COUNT("frames"), START("start"), MAX_WIDTH("maxWidth"), MAX_HEIGHT( "maxHeight"), UPSCALE("upScale"), FRAMES(""); private String argumentName; Arguments(String argumentName) { this.argumentName = argumentName; } public String getArgumentName() { return this.argumentName; } public String getArgumentPrefix() { return this.argumentName + ":"; } } protected void processVideo(final MetaDataMap metaData, final Asset asset, final File tmpFile, final WorkflowSession wfSession) throws IOException, RepositoryException { String[] args = buildArguments(metaData); FFMpegWrapper wrapper = null; File tmpWorkingDir = null; try { //creating temp working directory for ffmpeg tmpWorkingDir = createTempDir(getWorkingDir()); wrapper = new FFMpegWrapper(tmpFile, tmpWorkingDir); wrapper.setExecutableLocator(locator); final StoryBoard board = new StoryBoard(wrapper, asset); for (final String arg : args) { final String value = getValue(arg); if (arg.startsWith("frames:")) { board.setFrames(NumberUtils.toInt(value, 10)); } else if (arg.startsWith("start:")) { board.setStart(NumberUtils.toInt(value, 0)); } else if (arg.startsWith("maxWidth:")) { board.setMaxWidth(NumberUtils.toInt(value, 0)); } else if (arg.startsWith("maxHeight:")) { board.setMaxHeight(NumberUtils.toInt(value, 0)); } else if (arg.startsWith("upScale:")) { if (null != value) { board.setUpscale(BooleanUtils.toBoolean(value)); } } else if (arg.startsWith("[") && arg.endsWith("]")) { String frameConfig = StringUtils.replaceEach(arg, new String[] { "[", "]" }, new String[] { "", "" }); if (StringUtils.isNotBlank(frameConfig)) { board.addFrame(frameConfig); } } } board.create(); log.info("created storyboard for video [{}]", asset.getPath()); } finally { try { // cleaning up ffmpeg's temp working directory if (tmpWorkingDir != null) { FileUtils.deleteDirectory(tmpWorkingDir); } } catch (IOException e) { log.warn( "Could not delete ffmpeg's temporary working directory: {}", tmpWorkingDir.getPath()); } } } private String getValue(final String arg) { final String[] strings = StringUtils.split(arg, ":"); return (strings.length == 2) ? strings[1] : null; } public String[] buildArguments(MetaDataMap metaData) { // the 'old' way, ensures backward compatibility String processArgs = metaData.get(Arguments.PROCESS_ARGS.name(), String.class); if (processArgs != null && !processArgs.equals("")) { return processArgs.split(","); } else { List arguments = new ArrayList(); Integer frameCount = metaData.get(Arguments.FRAME_COUNT.name(), Integer.class); if (frameCount != null) { StringBuilder builder = new StringBuilder(); builder.append(Arguments.FRAME_COUNT.getArgumentPrefix()).append(frameCount); arguments.add(builder.toString()); } Integer start = metaData.get(Arguments.START.name(), Integer.class); if (start != null) { StringBuilder builder = new StringBuilder(); builder.append(Arguments.START.getArgumentPrefix()).append(start); arguments.add(builder.toString()); } Integer maxWidth = metaData.get(Arguments.MAX_WIDTH.name(), Integer.class); if (maxWidth != null) { StringBuilder builder = new StringBuilder(); builder.append(Arguments.MAX_WIDTH.getArgumentPrefix()).append(maxWidth); arguments.add(builder.toString()); } Integer maxHeight = metaData.get(Arguments.MAX_HEIGHT.name(), Integer.class); if (maxHeight != null) { StringBuilder builder = new StringBuilder(); builder.append(Arguments.MAX_HEIGHT.getArgumentPrefix()).append(maxHeight); arguments.add(builder.toString()); } Boolean upScale = metaData.get(Arguments.UPSCALE.name(), Boolean.class); if (upScale != null) { StringBuilder builder = new StringBuilder(); builder.append(Arguments.UPSCALE.getArgumentPrefix()).append(upScale); arguments.add(builder.toString()); } String[] frames = metaData.get(Arguments.FRAMES.name(), String[].class); if (frames != null) { for(String frame :frames){ //frame setting must be in square brackets if(!frame.startsWith("[")){ frame = "[" +frame; } if(!frame.endsWith("]")){ frame = frame + "]"; } arguments.add(frame); } } return arguments.toArray(new String[arguments.size()]); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy