com.day.cq.dam.video.FFMpegThumbnailProcess Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
package com.day.cq.dam.video;
import static com.day.cq.dam.api.DamConstants.THUMBNAIL_MIMETYPE;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.jcr.RepositoryException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
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.Reference;
import org.apache.felix.scr.annotations.Service;
import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.cq.dam.api.renditions.RenditionMaker;
import com.day.cq.dam.api.renditions.RenditionTemplate;
import com.day.cq.dam.api.thumbnail.ThumbnailConfig;
import com.day.cq.dam.commons.thumbnail.ThumbnailConfigImpl;
import com.day.cq.dam.handler.ffmpeg.FFMpegWrapper;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.metadata.MetaDataMap;
import com.day.image.Layer;
/**
* Workflow process that calls FFMPEG on the command line to create thumbnails of the image. You can specify the
* dimension of the thumbnails to be created
*
* For example, using the following workflow step arguments:
*
* count:3,index:1,start:10,[140x100],[48x48]
*
*
* 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 Thumbnail Process", description = "Workflow process that creates thumbnails from video files")
@Service
@Properties({ @Property(name = "process.label", value = "Create Video Thumbnails", propertyPrivate=true) })
public class FFMpegThumbnailProcess extends AbstractFFMpegProcess{
@Reference
private RenditionMaker renditionMaker;
/**
* The available arguments to this process implementation.
*/
public enum Arguments {
PROCESS_ARGS("PROCESS_ARGS"), START("start"), COUNT("count"), INDEX("index"), CONFIGS("CONFIGS");
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);
int start = 0, count = 1, index = 0;
// parse out the start value
int i = 0;
while (i < args.length) {
final String arg = args[i];
if (arg.startsWith("start:") || arg.startsWith("count:") || arg.startsWith("index:")) {
final String[] split = StringUtils.split(arg, ":");
if (split.length == 2) {
if (arg.startsWith("start:")) {
start = NumberUtils.toInt(split[1], start);
} else if (arg.startsWith("count:")) {
count = NumberUtils.toInt(split[1], count);
if (count <= 0) {
count = 1;
}
} else if (arg.startsWith("index:")) {
index = NumberUtils.toInt(split[1], index);
}
}
// and remove the arg from the list of arguments
// since incompatible with subsequent thumbnail config
args = (String[]) ArrayUtils.remove(args, i);
} else {
i++;
}
}
FFMpegWrapper wrapper=null;
File tmpWorkingDir = null;
File tmpRenditionFile = null;
OutputStream out = null;
InputStream in = null;
try {
//creating temp working directory for ffmpeg
tmpWorkingDir = createTempDir(getWorkingDir());
wrapper = new FFMpegWrapper(tmpFile, tmpWorkingDir);
wrapper.setExecutableLocator(locator);
BufferedImage[] thumbnails = wrapper.getThumbnails(count, start);
if (thumbnails != null && thumbnails.length > 0) {
if (index >= thumbnails.length) {
index = thumbnails.length - 1;
}
final BufferedImage thumbnail = thumbnails[index];
if (thumbnail != null) {
final BufferedImage rgbaThumbnail =
new BufferedImage(thumbnail.getWidth(), thumbnail.getHeight(), BufferedImage.TYPE_INT_ARGB);
rgbaThumbnail.getGraphics().drawImage(thumbnail, 0, 0, null);
// creating a temporary rendition through which normal renditions can be generated for an image.
tmpRenditionFile = File.createTempFile("thumbnail", ".tmp");
Layer layer = new Layer(rgbaThumbnail);
out = FileUtils.openOutputStream(tmpRenditionFile);
layer.write(THUMBNAIL_MIMETYPE, 0.8, out);
IOUtils.closeQuietly(out);
in = FileUtils.openInputStream(tmpRenditionFile);
final Rendition rendition = asset.addRendition("tempRendition", in, THUMBNAIL_MIMETYPE);
ThumbnailConfig[] thumbConfigs = getThumbnailConfigs(args);
// for each thumbnail config, create a rendition template
RenditionTemplate[] templates = createRenditionTemplates(rendition, thumbConfigs, renditionMaker);
// create thumbnail renditions
renditionMaker.generateRenditions(asset, templates);
//removing the temporary rendition
asset.removeRendition(rendition.getName());
}
} else {
log.warn("Could not create thumbnails for video asset {}, maybe ffmpeg is not installed.", asset.getPath());
}
} finally {
try {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
// cleaning up ffmpeg's temp working directory
if (tmpWorkingDir != null) {
FileUtils.deleteDirectory(tmpWorkingDir);
}
// cleaning up temp file
if(tmpRenditionFile != null) {
FileUtils.deleteQuietly(tmpRenditionFile);
}
} catch (IOException e) {
log.warn(
"Could not delete ffmpeg's temporary working directory: {}",
tmpWorkingDir.getPath());
}
}
}
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();
String start = metaData.get(Arguments.START.name(), String.class);
if(start != null){
StringBuilder builder = new StringBuilder();
builder.append(Arguments.START.getArgumentPrefix()).append(start);
arguments.add(builder.toString());
}
String count = metaData.get(Arguments.COUNT.name(), String.class);
if(count != null){
StringBuilder builder = new StringBuilder();
builder.append(Arguments.COUNT.getArgumentPrefix()).append(count);
arguments.add(builder.toString());
}
String index = metaData.get(Arguments.INDEX.name(), String.class);
if(index != null){
StringBuilder builder = new StringBuilder();
builder.append(Arguments.INDEX.getArgumentPrefix()).append(index);
arguments.add(builder.toString());
}
String[] configs = metaData.get(Arguments.CONFIGS.name(), String[].class);
if (configs != null) {
for(String config : configs){
arguments.add(config);
}
}
return arguments.toArray(new String[arguments.size()]);
}
}
private ThumbnailConfig[] getThumbnailConfigs(final String[] args) {
final Set set = new HashSet();
for (String arg : args) {
ThumbnailConfig config = null;
// remove any whitespace
arg = arg.trim();
// remove any square brackets
arg = StringUtils.replaceEach(arg, new String[]{"[", "]"}, new String[]{"", ""});
final String fragments[] = arg.split(":");
// ensure sufficient arguments are present (at least width:height)
if (fragments.length >= 2) {
try {
final Integer width = Integer.valueOf(fragments[0]);
final Integer height = Integer.valueOf(fragments[1]);
boolean doCenter = false;
if (fragments.length > 2) {
doCenter = Boolean.valueOf(fragments[2]);
}
config = new ThumbnailConfigImpl(width, height, doCenter);
} catch (NumberFormatException e) {
log.warn("parseConfig: cannot parse, invalid width/height specified in config [{}]: ", arg, e);
}
} else {
log.warn("parseConfig: cannot parse, insufficient arguments in config [{}].", arg);
}
if(config != null) {
set.add(config);
}
}
return set.toArray(new ThumbnailConfig[]{});
}
private RenditionTemplate[] createRenditionTemplates(Rendition rendition, ThumbnailConfig[] thumbnails, RenditionMaker renditionMaker) {
RenditionTemplate[] templates = new RenditionTemplate[thumbnails.length];
for (int i = 0; i < thumbnails.length; i++) {
ThumbnailConfig thumb = thumbnails[i];
templates[i] = renditionMaker.createThumbnailTemplate(rendition, thumb.getWidth(), thumb.getHeight(), thumb.doCenter());
}
return templates;
}
}