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

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

There is a newer version: 2025.3.19823.20250304T101418Z-250200
Show newest version
package com.day.cq.dam.video;

import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT;
import static com.day.cq.dam.api.DamConstants.DC_EXTENT;
import static com.day.cq.dam.api.DamConstants.METADATA_FOLDER;
import static com.day.cq.dam.api.DamConstants.TIFF_IMAGELENGTH;
import static com.day.cq.dam.api.DamConstants.TIFF_IMAGEWIDTH;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;

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

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.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.mime.MimeTypeService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.commons.process.AbstractAssetWorkflowProcess;
import com.day.cq.dam.handler.ffmpeg.ExecutableLocator;
import com.day.cq.dam.handler.ffmpeg.FFMpegWrapper;
import com.day.cq.dam.handler.ffmpeg.FfmpegNotFoundException;
import com.day.cq.workflow.WorkflowException;
import com.day.cq.workflow.WorkflowSession;
import com.day.cq.workflow.exec.WorkItem;
import com.day.cq.workflow.metadata.MetaDataMap;

/**
 * The AbstractFFMpegProcess class prepares a video asset processed
 * using FFMpeg
 * 
 */
@Component(componentAbstract = true, metatype = true)
public abstract class AbstractFFMpegProcess extends AbstractAssetWorkflowProcess {

    protected final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * FFmpeg working directory. If relative, relative to sling.home.
     * 
     */
    @Property(value = "./logs/ffmpeg")
    public static final String PROP_WORKING_DIR = "ffmpeg.workingdir";

    @Reference(policy = ReferencePolicy.STATIC)
    protected MimeTypeService mimeTypeService;

    @Reference(policy = ReferencePolicy.STATIC)
    protected ExecutableLocator locator;

    private File workingDir;

    public void execute(WorkItem workItem, WorkflowSession wfSession, MetaDataMap metaData) throws WorkflowException {

        try (ResourceResolver resolver1 = resourceResolverFactory
                .getResourceResolver(Collections.singletonMap("user.jcr.session", (Object) wfSession.getSession()))) {
            final Asset asset = getAssetFromPayload(workItem, resolver1);

            if (asset == null) {
                String wfPayload = workItem.getWorkflowData().getPayload().toString();
                String message = "execute: cannot process video, asset [{" + wfPayload
                        + "}] in payload doesn't exist for workflow [{" + workItem.getId() + "}].";
                throw new WorkflowException(message);
            }

            final String assetMimeType = asset.getMimeType();
            if (assetMimeType == null || !assetMimeType.startsWith("video/")) {
                if (!asset.getName().endsWith(".mov") || !asset.getName().endsWith(".m4v")
                        || !asset.getName().endsWith(".avi") || !asset.getName().endsWith(".flv")) {
                    log.info("execute: asset [{}] is not of a video mime type, asset ignored.", asset.getPath());
                    return;
                }
            }

            File tmpDir = null;
            File tmpWorkingDir = null;
            FileOutputStream fos = null;
            InputStream is = null;
            FFMpegWrapper wrapper = null;
            try {
                // creating temp directory
                tmpDir = File.createTempFile("cqdam", null);
                tmpDir.delete();
                tmpDir.mkdir();

                // creating temp working directory for ffmpeg
                tmpWorkingDir = createTempDir(getWorkingDir());

                // streaming file to temp directory
                final File tmpFile = new File(tmpDir, asset.getName().replace(' ', '_').replace("\'", "_"));
                fos = new FileOutputStream(tmpFile);
                is = asset.getOriginal().getStream();
                IOUtils.copy(is, fos);

                processVideo(metaData, asset, tmpFile, wfSession);

                // get information about original video file (size, video length, ...)
                wrapper = new FFMpegWrapper(tmpFile, tmpWorkingDir);
                wrapper.setExecutableLocator(locator);

                final ResourceResolver resolver = getResourceResolver(wfSession.getSession());
                final Resource assetResource = asset.adaptTo(Resource.class);
                final Resource metadata = resolver.getResource(assetResource, JCR_CONTENT + "/" + METADATA_FOLDER);

                if (null != metadata) {

                    final Node metadataNode = metadata.adaptTo(Node.class);
                    metadataNode.setProperty(DC_EXTENT, wrapper.getInputDuration());

                    double width = (null != wrapper.getInputSize()) ? wrapper.getInputSize().getWidth() : 0;
                    double height = (null != wrapper.getInputSize()) ? wrapper.getInputSize().getHeight() : 0;

                    if (width > 0 && height > 0) {
                        metadataNode.setProperty(TIFF_IMAGEWIDTH, (long) wrapper.getInputSize().width);
                        metadataNode.setProperty(TIFF_IMAGELENGTH, (long) wrapper.getInputSize().height);
                    }

                    metadataNode.getSession().save();
                } else {
                    log.warn("execute: failed setting metdata for asset [{}] in workflow [{}], no metdata node found.",
                            asset.getPath(), workItem.getId());
                }

            } catch (IOException e) {
                throw new WorkflowException(e);
            } catch (RepositoryException e) {
                throw new WorkflowException(e);
            } catch (FfmpegNotFoundException e) {
                log.error(e.getMessage(), e);
            } finally {
                IOUtils.closeQuietly(is);
                IOUtils.closeQuietly(fos);
                try {
                    // cleaning up temp directory
                    if (tmpDir != null) {
                        FileUtils.deleteDirectory(tmpDir);
                    }
                } catch (IOException e) {
                    log.error("Could not delete temp directory: {}", tmpDir.getPath());
                    throw new WorkflowException(e);

                }
                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());
                }
            }
        } catch (LoginException e) {
            throw new WorkflowException("Failed to get Resource Resolver");
        }
    }

    public File getWorkingDir() {
        workingDir.mkdir();
        return workingDir;
    }

    private File resolveWorkingDir(String slingHome, String path) {
        if (path == null) {
            path = "";
        }
        // ensure proper separator in the path (esp. for systems, which do
        // not use "slash" as a separator, e.g Windows)
        path = path.replace('/', File.separatorChar);

        // create a file instance and check whether this is absolute. If not
        // create a new absolute file instance with the base dir (sling.home or
        // working dir of current JVM) and get the absolute path name from that
        File workingDir = new File(path);
        if (!workingDir.isAbsolute()) {
            File baseDir = new File((slingHome == null) ? "" /* jvm working dir */ : slingHome).getAbsoluteFile();
            workingDir = new File(baseDir, path).getAbsoluteFile();
        }
        try {
            log.info("ffmpeg working directory: {}", workingDir.getCanonicalPath());
        } catch (IOException e) {
            log.info("ffmpeg working directory: {}", workingDir.getAbsolutePath());
        }

        return workingDir;
    }

    protected void activate(ComponentContext ctx) {
        String slingHome = ctx.getBundleContext().getProperty("sling.home");
        workingDir = resolveWorkingDir(slingHome, (String) ctx.getProperties().get(PROP_WORKING_DIR));
    }

    /**
     * creates a temporary directory in the given dir.
     * 
     * @param parentDir parent directory in which temporary directory is to be
     *                  created
     * @return File object for tempDir
     */
    protected File createTempDir(File parentDir) {
        File tempDir = null;
        try {
            tempDir = File.createTempFile("cqdam", null, parentDir);
            tempDir.delete();
            tempDir.mkdir();
        } catch (IOException e) {
            log.warn("could not create temp directory in the [{}] with the exception", parentDir, e);
        }
        return tempDir;
    }

    abstract String[] buildArguments(MetaDataMap metaData);

    abstract void processVideo(final MetaDataMap metaData, final Asset asset, final File tmpFile,
            final WorkflowSession wfSession) throws IOException, RepositoryException;

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy