FFMpegTranscodeProcess.java 9.29 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.day.cq.dam.api.Asset
 *  com.day.cq.dam.api.Rendition
 *  com.day.cq.workflow.WorkflowSession
 *  com.day.cq.workflow.metadata.MetaDataMap
 *  javax.jcr.Node
 *  javax.jcr.Property
 *  javax.jcr.RepositoryException
 *  javax.jcr.Session
 *  org.apache.commons.io.FileUtils
 *  org.apache.commons.io.IOUtils
 *  org.apache.commons.lang.ArrayUtils
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.api.resource.PersistenceException
 *  org.apache.sling.api.resource.Resource
 *  org.apache.sling.api.resource.ResourceResolver
 *  org.apache.sling.api.resource.ValueMap
 *  org.slf4j.Logger
 */
package com.day.cq.dam.video;

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.Rendition;
import com.day.cq.dam.handler.ffmpeg.ExecutableLocator;
import com.day.cq.dam.handler.ffmpeg.FFMpegWrapper;
import com.day.cq.dam.video.AbstractFFMpegProcess;
import com.day.cq.dam.video.VideoProfile;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.metadata.MetaDataMap;
import java.awt.Dimension;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
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.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;

@Component(label="Day CQ DAM FFmpeg Transcode Process", description="Workflow process that transcodes video files into different formats")
@Service
@Properties(value={@Property(name="process.label", value={"Transcode Video"}, propertyPrivate=1)})
public class FFMpegTranscodeProcess
extends AbstractFFMpegProcess {
    private static final String MIX_DAM_METADATA = "dam:Metadata";
    private static final String[] propertiesLong = new String[]{"audioChannels", "audioSamplingRate", "videoBitrate", "audioBitrate", "videoBitrateTolerance", "height", "width"};

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    @Override
    protected void processVideo(MetaDataMap metaData, Asset asset, File tmpFile, WorkflowSession wfSession) throws IOException, RepositoryException {
        String[] videoProfiles;
        long start = System.currentTimeMillis();
        this.log.info("processing asset [{}]...", (Object)asset.getPath());
        ResourceResolver resolver = this.getResourceResolver(wfSession.getSession());
        for (String videoProfile : videoProfiles = this.getVideoProfiles(metaData)) {
            VideoProfile profile = VideoProfile.get(resolver, videoProfile);
            if (profile == null) continue;
            this.log.info("processVideo: creating video using profile [{}]", (Object)videoProfile);
            File tmpWorkingDir = this.createTempDir(this.getWorkingDir());
            FFMpegWrapper ffmpegWrapper = FFMpegWrapper.fromProfile(tmpFile, profile, tmpWorkingDir);
            ffmpegWrapper.setExecutableLocator(this.locator);
            FileInputStream fis = null;
            try {
                String renditionName = this.getRenditionName(ffmpegWrapper);
                File video = ffmpegWrapper.transcode();
                fis = new FileInputStream(video);
                Rendition rendition = asset.addRendition(renditionName, (InputStream)fis, ffmpegWrapper.getOutputMimetype());
                this.addEncodingMetadata(profile, rendition);
                video.delete();
            }
            catch (IOException e) {
                this.log.error(e.getMessage(), (Throwable)e);
                this.log.error("processVideo: failed creating video from profile [{}]: {}", (Object)videoProfile, (Object)e.getMessage());
            }
            finally {
                IOUtils.closeQuietly((InputStream)fis);
                try {
                    if (tmpWorkingDir != null) {
                        FileUtils.deleteDirectory((File)tmpWorkingDir);
                    }
                }
                catch (IOException e) {
                    this.log.warn("Could not delete ffmpeg's temporary working directory: {}", (Object)tmpWorkingDir.getPath());
                }
            }
        }
        this.log.info("finished processing asset [{}] in [{}ms].", (Object)asset.getPath(), (Object)(System.currentTimeMillis() - start));
    }

    private void addEncodingMetadata(VideoProfile profile, Rendition rendition) throws RepositoryException, PersistenceException {
        Resource contentRes = rendition.getChild("jcr:content");
        if (contentRes != null) {
            Node contentNode = (Node)contentRes.adaptTo(Node.class);
            contentNode.addMixin("dam:Metadata");
            Node metadataNode = null;
            metadataNode = !contentNode.hasNode("metadata") ? contentNode.addNode("metadata", "nt:unstructured") : contentNode.getNode("metadata");
            ValueMap profileVM = profile.getProperties();
            Set entries = profileVM.entrySet();
            for (Map.Entry entry : entries) {
                String key = (String)entry.getKey();
                if (this.ignoreProperty(key)) continue;
                if (ArrayUtils.indexOf((Object[])propertiesLong, (Object)key) >= 0) {
                    try {
                        Long value = new Long(Long.parseLong((String)entry.getValue()));
                        metadataNode.setProperty(key, value.longValue());
                    }
                    catch (Exception e) {
                        metadataNode.setProperty(key, (String)entry.getValue());
                    }
                    continue;
                }
                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 profileName = ffmpegWrapper.getProfileName();
        StringBuilder builder = new StringBuilder();
        builder.append("cq5dam.video.").append(profileName);
        if (ffmpegWrapper.getOutputSize() != null) {
            builder.append(".").append(ffmpegWrapper.getOutputSize().width).append(".").append(ffmpegWrapper.getOutputSize().height);
        }
        builder.append(".").append(outputFormat);
        return builder.toString();
    }

    public String[] getThumbnailConfigs(MetaDataMap metaData) {
        if (this.isLegacy(metaData)) {
            List configs = this.getValuesFromArgs(Arguments.CONFIGS.getArgumentName(), this.getLegacyArguments(metaData));
            return configs.toArray(new String[configs.size()]);
        }
        String[] configs = (String[])metaData.get(Arguments.CONFIGS.name(), String[].class);
        return configs != null ? configs : new String[]{};
    }

    public String[] getVideoProfiles(MetaDataMap metaData) {
        if (this.isLegacy(metaData)) {
            List profiles = this.getValuesFromArgs(Arguments.VIDEO_PROFILES.getArgumentName(), this.getLegacyArguments(metaData));
            return profiles.toArray(new String[profiles.size()]);
        }
        String[] profiles = (String[])metaData.get(Arguments.VIDEO_PROFILES.name(), String[].class);
        return profiles != null ? profiles : new String[]{};
    }

    private boolean isLegacy(MetaDataMap metaDataMap) {
        return metaDataMap.get(Arguments.PROCESS_ARGS.name(), String.class) != null;
    }

    private String[] getLegacyArguments(MetaDataMap metaData) {
        String processArgs = (String)metaData.get(Arguments.PROCESS_ARGS.name(), String.class);
        if (processArgs != null && !processArgs.equals("")) {
            return processArgs.split(",");
        }
        return new String[0];
    }

    @Override
    public String[] buildArguments(MetaDataMap metaData) {
        String processArgs = (String)metaData.get(Arguments.PROCESS_ARGS.name(), String.class);
        if (processArgs != null && !processArgs.equals("")) {
            return processArgs.split(",");
        }
        String[] configs = (String[])metaData.get(Arguments.CONFIGS.name(), String[].class);
        if (configs != null) {
            return configs;
        }
        return new String[0];
    }

    public static enum Arguments {
        PROCESS_ARGS(""),
        CONFIGS("tn"),
        VIDEO_PROFILES("profile");
        
        private String argumentName;

        private Arguments(String argumentName) {
            this.argumentName = argumentName;
        }

        public String getArgumentName() {
            return this.argumentName;
        }

        public String getArgumentPrefix() {
            return this.argumentName + ":";
        }
    }

}