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

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

package com.day.cq.dam.video;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.RepositoryException;

import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.dam.api.DamConstants;
import com.day.cq.dam.api.Rendition;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
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 org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

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;

import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.caconfig.resource.ConfigurationResourceResolver;

/**
 * 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: *

* *

 *    [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 Transcode Process", description = "Workflow process that transcodes video files into different formats") @Service @Properties({ @Property(name = "process.label", value = "Transcode Video", propertyPrivate = true) }) public class FFMpegTranscodeProcess extends AbstractFFMpegProcess { private static final String MIX_DAM_METADATA = "dam:Metadata"; @Reference protected ConfigurationResourceResolver configResolver; /** * The available arguments to this process implementation. */ public enum Arguments { PROCESS_ARGS(""), CONFIGS("tn"), VIDEO_PROFILES("profile"); private String argumentName; Arguments(String argumentName) { this.argumentName = argumentName; } public String getArgumentName() { return this.argumentName; } public String getArgumentPrefix() { return this.argumentName + ":"; } } private static final String[] propertiesLong = { VideoConstants.PN_AUDIO_CHANNELS, VideoConstants.PN_AUDIO_SAMPLING_RATE, VideoConstants.PN_VIDEO_BITRATE, VideoConstants.PN_AUDIO_BITRATE, VideoConstants.PN_VIDEO_BITRATE_TOLERANCE, VideoConstants.PN_VIDEO_HEIGHT, VideoConstants.PN_VIDEO_WIDTH, }; protected void processVideo(final MetaDataMap metaData, final Asset asset, final File tmpFile, final WorkflowSession wfSession) throws IOException, RepositoryException { final long start = System.currentTimeMillis(); log.info("processing asset [{}]...", asset.getPath()); ResourceResolver resolver = getResourceResolver(wfSession.getSession()); // create videos from profiles String[] videoProfiles = getVideoProfiles(metaData); for (String videoProfile : videoProfiles) { VideoProfile profile = VideoProfile.get(resolver, configResolver, videoProfile); if (profile != null) { log.info("processVideo: creating video using profile [{}]", videoProfile); // creating temp working directory for ffmpeg File tmpWorkingDir = createTempDir(getWorkingDir()); FFMpegWrapper ffmpegWrapper = FFMpegWrapper.fromProfile( tmpFile, profile, tmpWorkingDir); ffmpegWrapper.setExecutableLocator(locator); FileInputStream fis = null; try { final String renditionName = getRenditionName(ffmpegWrapper); final File video = ffmpegWrapper.transcode(); fis = new FileInputStream(video); Rendition rendition = asset.addRendition(renditionName, fis, ffmpegWrapper.getOutputMimetype()); addEncodingMetadata(profile, rendition); video.delete(); } catch (IOException e) { log.error(e.getMessage(), e); log.error( "processVideo: failed creating video from profile [{}]: {}", videoProfile, e.getMessage()); } finally { IOUtils.closeQuietly(fis); 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()); } } } } log.info("finished processing asset [{}] in [{}ms].", asset.getPath(), System.currentTimeMillis() - start); } private void addEncodingMetadata(VideoProfile profile, Rendition rendition) throws RepositoryException, PersistenceException { Resource contentRes = rendition.getChild(JcrConstants.JCR_CONTENT); if (contentRes != null) { Node contentNode = contentRes.adaptTo(Node.class); // apply mixin, this mixin allows a metadata node of type // nt:unstructured under nt:resource contentNode.addMixin(MIX_DAM_METADATA); Node metadataNode = null; if(!contentNode.hasNode(DamConstants.METADATA_FOLDER)) metadataNode = contentNode.addNode(DamConstants.METADATA_FOLDER,JcrConstants.NT_UNSTRUCTURED); else metadataNode = contentNode.getNode(DamConstants.METADATA_FOLDER); // add profile properties to the rendition ValueMap profileVM = profile.getProperties(); Set> entries = profileVM.entrySet(); for (Map.Entry entry : entries) { String key = entry.getKey(); // ignore jcr/sling/cq properties if (!ignoreProperty(key)) { if(ArrayUtils.indexOf(propertiesLong,key)>=0){ try{ Long value = new Long(Long.parseLong((String) entry.getValue())); metadataNode.setProperty(key,value); } catch(Exception e){ metadataNode.setProperty(key,(String)entry.getValue()); } } else{ metadataNode.setProperty(key,(String)entry.getValue()); } } } } } private boolean ignoreProperty(String key) { return key.startsWith("jcr:") || key.startsWith("sling:") || key.startsWith("cq:"); } private String getRenditionName(FFMpegWrapper ffmpegWrapper) { String outputFormat = ffmpegWrapper.getOutputExtension(); String renditionSelector = ffmpegWrapper.getRenditionSelector(); if (StringUtils.isEmpty(renditionSelector)) { // fallback to profile name, for legacy reasons renditionSelector = ffmpegWrapper.getProfileName(); } StringBuilder builder = new StringBuilder(); builder.append(VideoConstants.RENDITION_PREFIX).append(renditionSelector); if (ffmpegWrapper.getOutputSize() != null) { builder.append(".").append(ffmpegWrapper.getOutputSize().width) .append(".").append(ffmpegWrapper.getOutputSize().height); } builder.append(".").append(outputFormat); return builder.toString(); } /** * Reads the thumbnail configurations from the given meta data. * * @param metaData * @return String[] of thumbnail configurations. */ public String[] getThumbnailConfigs(MetaDataMap metaData) { if (isLegacy(metaData)) { List configs = getValuesFromArgs( Arguments.CONFIGS.getArgumentName(), getLegacyArguments(metaData)); return configs.toArray(new String[configs.size()]); } else { String[] configs = metaData.get(Arguments.CONFIGS.name(), String[].class); return configs != null ? configs : new String[0]; } } public String[] getVideoProfiles(MetaDataMap metaData) { if (isLegacy(metaData)) { List profiles = getValuesFromArgs( Arguments.VIDEO_PROFILES.getArgumentName(), getLegacyArguments(metaData)); return profiles.toArray(new String[profiles.size()]); } else { String[] profiles = metaData.get(Arguments.VIDEO_PROFILES.name(), String[].class); return profiles != null ? profiles : new String[0]; } } private boolean isLegacy(MetaDataMap metaDataMap) { return metaDataMap.get(Arguments.PROCESS_ARGS.name(), String.class) != null; } private String[] getLegacyArguments(MetaDataMap metaData) { String processArgs = metaData.get(Arguments.PROCESS_ARGS.name(), String.class); if (processArgs != null && !processArgs.equals("")) { return processArgs.split(","); } else { return new String[0]; } } 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(","); } // the 'new' way else { String[] configs = metaData.get(Arguments.CONFIGS.name(), String[].class); if (configs != null) { return configs; } else { return new String[0]; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy