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

com.day.cq.dam.handler.ffmpeg.FFMpegWrapper Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
package com.day.cq.dam.handler.ffmpeg;

import static com.day.cq.dam.video.VideoConstants.*;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import javax.imageio.ImageIO;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.LogOutputStream;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.IOUtils;
import org.apache.sling.api.resource.ValueMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.day.cq.dam.video.VideoProfile;

public class FFMpegWrapper {

    /**
     * flag indicating the installation status of ffmpeg. null =
     * not tested, true = installed, false = not
     * installed
     */
    private Boolean installed = null;

    private static final Logger log = LoggerFactory.getLogger(FFMpegWrapper.class);
    private static final LoggerOutputStream LOG_STREAM;
    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("H:mm:ss.S");
    private static final String FFMPEG_CMD = "ffmpeg";

    private File input;
    private Dimension outputsize;

    private DefaultExecutor infoExecutor = new DefaultExecutor();
    private DefaultExecutor transcodeExecutor = new DefaultExecutor();
    private DefaultExecutor testExecutor = new DefaultExecutor();
    private long duration = -1;
    private boolean letterbox = false;
    private Dimension inputsize;
    private int bitrate = 0;
    private String fps = null;
    private int bitrateTolerance = 0;
    private String videoCodec = null;
    private String audioCodec = null;
    private int audioChannels = 0;
    private int audioSamplingRate = 0;
    private int audioBitrate = 0;
    private boolean twoPass = false;
    private String[] customFlags = null;
    private long startTime = 0;
    private long clipDuration = 0;
    private Rectangle crop;
    private String preset = null;
    private boolean fitInside;
    private String extension;
    private String outputMimetype;
    private String profileName;
    private String renditionSelector;
    private StringBuilder ffmpegOutput = new StringBuilder();

    public String getOutputExtension() {
        return extension;
    }

    public void setOutputExtension(String extension) {
        this.extension = extension;
    }

    public StringBuilder getFFMpegOutput() {
        return ffmpegOutput;
    }

    public String getProfileName() {
        return profileName;
    }

    public void setProfileName(String profileName) {
        this.profileName = profileName;
    }

    public String getRenditionSelector() {
        return renditionSelector;
    }

    public void setRenditionSelector(String renditionSelector) {
        this.renditionSelector = renditionSelector;
    }

    public String getOutputMimetype() {
        return outputMimetype;
    }

    public void setOutputMimetype(String outputMimetype) {
        this.outputMimetype = outputMimetype;
    }

    private ExecutableLocator locator = new ExecutableLocator() {
        public String getPath(String cmd) {
            return FFMPEG_CMD;
        }
    };

    static {
        TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
        LOG_STREAM = new LoggerOutputStream(log);
    }

    public void setTwoPass() {
        this.twoPass = true;
    }

    public void setTwoPass(boolean twoPass) {
        this.twoPass = twoPass;
    }

    public void setPreset(String preset) {
        this.preset = preset;
    }

    public void setAudioSamplingRate(int audioSamplingRate) {
        this.audioSamplingRate = audioSamplingRate;
    }

    public void setAudioBitrate(int audioBitrate) {
        this.audioBitrate = audioBitrate;
    }

    public void setVideoCodec(String videoCodec) {
        this.videoCodec = videoCodec;
    }

    public void setAudioCodec(String audioCodec) {
        this.audioCodec = audioCodec;
    }

    /**
     * Sets the output video bitrate in kbits/second
     * 
     * @param bitrate
     *            The bitrate
     */
    public void setBitrate(int bitrate) {
        this.bitrate = bitrate;
    }

    /**
     * Creates a wrapper to run ffmpeg for the given video file. Will run ffmpeg
     * in the current working directory of JVM.
     * 
     * @param file
     *            video file to call ffmpeg with
     */
    public FFMpegWrapper(File file) {
        this(file, null);
    }

    /**
     * Creates a wrapper to run ffmpeg for the given video file. Provide a
     * different workingDir every time you create this FFMpegWrapper as
     * ffmpeg's temp file name in 2-pass encoding is hard coded in the ffmpeg
     * build, which causes problem while running simultaneous multiple
     * encodings. Even you use -passlogfile option, x264_2pass.log file name is
     * hard coded in the older ffmpeg builds. For more details please refer to
     * bug:42346.
     * 
     * @param file video file to call ffmpeg with
     * @param workingDir a working directory for ffmpeg; if null,
     *            the current working directory of the JVM is used
     */
    public FFMpegWrapper(File file, File workingDir) {
        this.setInput(file);
        final InfoParser infoParser = new InfoParser(this);
        this.infoExecutor.setStreamHandler(new PumpStreamHandler(infoParser, infoParser));
        this.infoExecutor.setExitValue(1);
        LOG_STREAM.setWrapper(this);
        this.transcodeExecutor.setStreamHandler(new PumpStreamHandler(LOG_STREAM, LOG_STREAM));

        if (workingDir != null) {
            this.infoExecutor.setWorkingDirectory(workingDir);
            this.transcodeExecutor.setWorkingDirectory(workingDir);
            this.testExecutor.setWorkingDirectory(workingDir);
        }
    }

    public void setInput(File f) {
        this.input = f;
    }

    public void setExecutableLocator(ExecutableLocator locator) {
        this.locator = locator;
    }

    public void setOutputSize(Dimension dimension) {
        this.outputsize = dimension;
        // outputsize must be a multiple of two
        if (this.outputsize != null) {
            this.outputsize.setSize(this.outputsize.width % 2 + this.outputsize.width, this.outputsize.height % 2
                    + this.outputsize.height);
        }
    }

    public Dimension getOutputSize() {
        return outputsize;
    }

    public long getInputDuration() {
        if (this.duration > -1) {
            return this.duration;
        }

        try {
            info();
        } catch (IOException e) {
            return -1;
        }

        return this.duration;
    }

    public Dimension getInputSize() {
        if (this.inputsize != null) {
            return this.inputsize;
        }

        try {
            info();
        } catch (IOException e) {
            log.warn("getInputSize: I/O error: ", e);
            return null;
        }

        return this.inputsize;
    }

    private String getFfmpegPath() {
        String path = locator.getPath(FFMPEG_CMD);
        if (path == null) {
            log.info("Could not find ffmpeg's location, trying direct call to '{}'", FFMPEG_CMD);
            path = FFMPEG_CMD;
        }
        return path;
    }

    private CommandLine ffmpeg() {

        if (!isFfmpegInstalled()) {
            throw new FfmpegNotFoundException("Could not find ffmpeg's executable");
        }
        String path = getFfmpegPath();
        CommandLine cmd = new CommandLine(path);
        return cmd;
    }

    private void info() throws IOException {
        CommandLine cli = ffmpeg();
        cli.addArgument("-i");
        cli.addArgument(input.getPath());

        log.info("**** Exec (info): [{}]", cli);
        this.infoExecutor.execute(cli);
    }

    /**
     * Create thumbnails from the video.
     * 
     * @param thumbnails
     *            The number of desired thumbnails.
     * @param start
     *            The start position in seconds from where to take the first
     *            thumbnail.
     * @return The thumbnails.
     */
    public BufferedImage[] getThumbnails(final int thumbnails, final int start) {

        try {

            final double fraction = (getInputDuration() / 1000) / thumbnails;
            final long specifier = System.currentTimeMillis();
            int i = 0;

            while (i < thumbnails) {

                final double pos = i * fraction;
                final CommandLine cli = ffmpeg();

                if (start > 0) {
                    cli.addArgument("-itsoffset");
                    cli.addArgument("-" + String.valueOf(start));
                }

                cli.addArgument("-ss");
                cli.addArgument(String.valueOf(pos));

                cli.addArgument("-i");
                cli.addArgument(input.getPath());

                cli.addArgument("-vframes");
                cli.addArgument("1");

                cli.addArgument("-y");

                cli.addArgument(input.getParent() + File.separator + "tempthumb." + specifier + "." + i + ".jpg");

                log.debug("grabbing frame [{}] at pos [{}] for [" + input.getPath() + "]...", (i + 1), pos);
                log.info("**** Exec (thumbs): [{}]", cli);
                this.transcodeExecutor.execute(cli);

                i++;
            }

            File[] thumbFiles = input.getParentFile().listFiles(new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    return name.matches("tempthumb\\." + specifier + "\\.(\\d)+\\.jpg");
                }
            });

            log.info("created {} thumbnails", thumbFiles.length);

            BufferedImage[] images = new BufferedImage[thumbFiles.length];
            for (int j = 0; j < thumbFiles.length; j++) {

                FileInputStream fis = null;
                try {
                    fis = new FileInputStream(thumbFiles[j]);
                    images[j] = ImageIO.read(fis);
                } catch (Exception e) {
                    log.warn("error reading image [{}]", thumbFiles[j]);
                    images[j] = null;
                } finally {
                    IOUtils.closeQuietly(fis);
                }

                thumbFiles[j].delete();
            }

            return images;
        } catch (IOException e) {
            return null;
        }
    }
    
    /**
     * Create clip from the video.
     * 
     * @param startTime
     *            startTime of the clip.
     * @param endTime
     *            The end position in seconds of the clip.
     * @param mimeType
     *              Mime type for the clip.
     * @return The thumbnails.
     */
    public File getClip(final double startTime, final double endTime, String mimeType) {

        try {

            final double duration = endTime - startTime;
            final String extension = mimeType.substring(mimeType.indexOf("/") + 1);
            final CommandLine cli = ffmpeg();

             cli.addArgument("-ss");
             cli.addArgument( String.valueOf(startTime));

             cli.addArgument("-t");
             cli.addArgument(String.valueOf(duration));
             
             cli.addArgument("-i");
             cli.addArgument(input.getPath());
             
             cli.addArgument("-strict");
             cli.addArgument("experimental");
             cli.addArgument(input.getParent() + File.separator + "clipped."  + extension);

             //log.debug("grabbing frame [{}] at pos [{}] for [" + input.getPath() + "]...", (i + 1), pos);
             //log.info("**** Exec (thumbs): [{}]", cli);
             this.transcodeExecutor.execute(cli);
             
             File[] clippedFiles= input.getParentFile().listFiles(new FilenameFilter() {
                 public boolean accept(File dir, String name) {
                     return name.startsWith("clipped");
                 }
             });
             
             return clippedFiles[0];

        } catch (IOException e) {
            return null;
        }
    }

    public File join(File other, String name) throws IOException {
        CommandLine intermediate1cli = ffmpeg();
        CommandLine intermediate2cli = ffmpeg();
        CommandLine joincli = ffmpeg();

        File intermediate1file = new File(input.getParentFile(), "intermediate."
                + input.getName().replaceAll("\\.\\w+", "") + ".mpg");
        File intermediate2file = new File(other.getParentFile(), "intermediate."
                + other.getName().replaceAll("\\.\\w+", "") + ".mpg");
        File joinfile = new File(input.getParentFile(), name + ".intermediate."
                + input.getName().replaceAll("\\.\\w+", "") + ".mpg");

        File outputfile = new File(input.getParentFile(), name + "." + input.getName());

        intermediate1cli.addArgument("-i");
        intermediate1cli.addArgument(input.getPath());
        intermediate1cli.addArgument("-y");
        intermediate1cli.addArgument("-sameq");
        intermediate1cli.addArgument("-r");
        intermediate1cli.addArgument("24");
        intermediate1cli.addArgument(intermediate1file.getPath());
        // transcode the first file into intermediate format
        log.info("**** Exec (intermediate join 1): [{}]", joincli);
        this.transcodeExecutor.execute(intermediate1cli);

        intermediate2cli.addArgument("-i");
        intermediate2cli.addArgument(other.getPath());
        intermediate2cli.addArgument("-y");
        intermediate2cli.addArgument("-sameq");
        intermediate2cli.addArgument("-r");
        intermediate2cli.addArgument("24");
        intermediate2cli.addArgument(intermediate2file.getPath());
        // transcode the second file into intermediate format
        log.info("**** Exec (intermediate join 2): [{}]", joincli);
        this.transcodeExecutor.execute(intermediate2cli);

        FileOutputStream join = null;
        FileInputStream isOne = null;
        FileInputStream isTwo = null;
        try {
            join = new FileOutputStream(joinfile);
            isOne = new FileInputStream(intermediate1file);
            IOUtils.copy(isOne, join);
            join.flush();
            
            isTwo = new FileInputStream(intermediate2file);
            IOUtils.copy(isTwo, join);
        } finally {
            IOUtils.closeQuietly(join);
            IOUtils.closeQuietly(isOne);
            IOUtils.closeQuietly(isTwo);
        }

        joincli.addArgument("-i");
        joincli.addArgument(joinfile.getPath());
        joincli.addArgument("-y");
        joincli.addArgument("-sameq");
        joincli.addArgument("-s");
        Dimension size = this.getInputSize();
        joincli.addArgument(size.width + "x" + size.height);
        joincli.addArgument(outputfile.getPath());

        log.info("**** Exec (join): [{}]", joincli);
        this.transcodeExecutor.execute(joincli);

        intermediate1file.delete();
        intermediate2file.delete();
        joinfile.delete();

        return outputfile;
    }

    public File trim(String name) throws IOException {
        CommandLine cli = ffmpeg();

        cli.addArgument("-i");
        cli.addArgument(input.getPath());

        cli.addArgument("-y");

        if (this.startTime > 0) {
            Date start = new Date(this.startTime);
            cli.addArgument("-ss");
            cli.addArgument(TIME_FORMAT.format(start));
        }

        if (this.clipDuration > 0) {
            Date clipduration = new Date(this.clipDuration);
            cli.addArgument("-t");
            cli.addArgument(TIME_FORMAT.format(clipduration));
        }

        Dimension outputsize = this.getInputSize();
        if (this.crop != null) {
            int cropleft = this.crop.getLocation().x;
            cropleft += cropleft % 2;
            cropleft = Math.max(0, cropleft);

            int croptop = this.crop.getLocation().y;
            croptop += croptop % 2;
            croptop = Math.max(0, croptop);

            int cropright = outputsize.width - cropleft - this.crop.getSize().width;
            cropright -= cropright % 2;
            cropright = Math.max(0, cropright);

            int cropbottom = outputsize.height - croptop - this.crop.getSize().height;
            cropbottom -= cropbottom % 2;
            cropbottom = Math.max(0, cropbottom);
            addArgument(cli, "croptop", croptop);
            addArgument(cli, "cropleft", cropleft);
            addArgument(cli, "cropbottom", cropbottom);
            addArgument(cli, "cropright", cropright);

            // calculate remaining size
            outputsize.setSize(outputsize.width - cropleft - cropright, outputsize.height - croptop - cropbottom);
        }
        // TODO: not sure if this is right (had to remove the second argument
        // from addResizeArguments())
        this.outputsize = outputsize;

        // resize, using the remaining size after cropping
        addResizeArguments(cli);

        File output = new File(input.getParentFile(), name + "." + input.getName());

        cli.addArgument(output.getPath());

        log.info("**** Exec (trim): [{}]", cli);
        this.transcodeExecutor.execute(cli);

        return output;
    }

    public File transcode() throws IOException {
        return transcode(getOutputExtension());
    }

    public File transcode(String extension) throws IOException {
        CommandLine cli = ffmpeg();

        cli.addArgument("-i");
        cli.addArgument(input.getPath());

        addResizeArguments(cli);

        addArgument(cli, "b", this.bitrate, "k");
        addArgument(cli, "r", this.fps);
        addArgument(cli, "bt", this.bitrateTolerance, "k");
        addArgument(cli, "vcodec", this.videoCodec);
        addArgument(cli, "acodec", this.audioCodec);
        addArgument(cli, "ac", this.audioChannels);
        addArgument(cli, "ar", this.audioSamplingRate);
        addArgument(cli, "ab", this.audioBitrate, "k");

        if (this.customFlags != null) {
            cli.addArguments(this.customFlags, true);
        }

        if (this.preset != null) {
            addArgument(cli, "vpre", this.preset);
        }

        cli.addArgument("-y");
        File output = new File(input.getParentFile(), this.input.getName().replaceAll("\\.\\w+", "") + ".out."
                + extension);

        // resetting the output builder
        ffmpegOutput = new StringBuilder();

        try {

            if (this.twoPass) {
                CommandLine pass2 = ffmpeg().addArguments(cli.getArguments());

                addArgument(cli, "pass", 1);
                cli.addArgument(output.getPath());

                addArgument(pass2, "pass", 2);
                pass2.addArgument(output.getPath());

                log.info("**** Exec (pass1): [{}]", cli.toString());
                ffmpegOutput.append(cli.toString()).append("\n\n");
                this.transcodeExecutor.execute(cli);
                log.info("**** Exec (pass2): [{}]", pass2.toString());
                ffmpegOutput.append(pass2.toString()).append("\n\n");
                this.transcodeExecutor.execute(pass2);
            } else {
                cli.addArgument(output.getPath());
                log.info("**** Exec: [{}]", cli.toString());
                ffmpegOutput.append(cli.toString()).append("\n\n");
                this.transcodeExecutor.execute(cli);
            }
            if (output.exists()) {
                return output;
            } else {
                throw new IOException("Transcoding did not create an output file");
            }
        } catch (ExecuteException e) {
            throw ((IOException) new IOException(e.getMessage()).initCause(e));
        }
    }

    private void addResizeArguments(CommandLine cli) {
        if (outputsize != null) {
            if (letterbox) {
                cli.addArgument("-aspect");
                cli.addArgument(outputsize.width + ":" + outputsize.height);
            }

            // do not stretch
            Dimension inputSize = getInputSize();
            // input size may be null if the file has no video(audio-only)
            if (inputSize != null) {
                int maxwidth = (outputsize.width > inputSize.width) ? inputSize.width : outputsize.width;
                int maxheight = (outputsize.height > inputSize.height) ? inputSize.height : outputsize.height;
        
                outputsize.setSize(new Dimension(maxwidth, maxheight));
        
                // Don't need resize if calculated output size is same as input size,
                // resize may result in loss of 1 pixel from input size
                if (fitInside && !(outputsize.width == inputSize.width && outputsize.height == inputSize.height)) {
                    float inputRatio = (float) inputSize.width / (float) inputSize.height;
                    float outputRatio = (float) outputsize.width / (float) outputsize.height;
                    if (inputRatio > outputRatio) {
                        // need to keep the width of the desired output size
                        // and adapt the height
        
                        // eg. 16:9 to 4:3 (landscape)
                        // 3:4 to 9:16 (portrait)
        
                        int height = Math.round(outputsize.width / inputRatio);
        
                        if (letterbox) {
                            int padding = outputsize.height - height;
        
                            // padding has to be a multiple of two
                            int padtop = padding / 2;
                            padtop = padtop + (padtop % 2);
                            int padbottom = padding / 2;
                            padbottom = padbottom - (padbottom % 2);
        
                            cli.addArgument("-padtop");
                            cli.addArgument("" + padtop);
                            cli.addArgument("-padbottom");
                            cli.addArgument("" + padbottom);
                        }
        
                        // keep output size even, some encoders don't accept odd output size. e.g. H264
                        outputsize.height = (height / 2) * 2;
        
                    } else if (inputRatio < outputRatio) {
                        // need to keep the height of the desired output size
                        // and adapt the width
        
                        // eg. 4:3 to 16:9 (landscape)
                        // 9:16 to 3:4 (portrait)
        
                        int width = Math.round(outputsize.height * inputRatio);
        
                        if (letterbox) {
                            int padding = outputsize.width - width;
        
                            // padding has to be a multiple of two
                            int padleft = padding / 2;
                            padleft = padleft + (padleft % 2);
                            int padright = padding / 2;
                            padright = padright - (padright % 2);
        
                            cli.addArgument("-padleft");
                            cli.addArgument("" + padleft);
                            cli.addArgument("-padright");
                            cli.addArgument("" + padright);
                        }
        
                        // keep output size even, some encoders don't accept odd output size. e.g. H264
                        outputsize.width = (width / 2) * 2;
                    }
                }
            }
            cli.addArgument("-s");
            cli.addArgument(this.outputsize.width + "x" + this.outputsize.height);
        }
    }

    public void setLetterbox() {
        this.letterbox = true;
    }

    public void setLetterbox(boolean letterbox) {
        this.letterbox = letterbox;
    }

    public void setFitInside() {
        this.fitInside = true;
    }

    public void setFitInside(boolean fitInside) {
        this.fitInside = fitInside;
    }

    public BufferedImage getFilmStrip(int frames, int width) {
        BufferedImage[] thumbs = this.getThumbnails(frames, 0);
        BufferedImage combined = new BufferedImage(frames * thumbs[0].getWidth(), thumbs[0].getHeight(),
                BufferedImage.TYPE_INT_RGB);
        Graphics g = combined.getGraphics();
        for (int i = 0; i < thumbs.length; i++) {
            g.drawImage(thumbs[i], (thumbs[0].getWidth()) * i, 0, null);
        }

        double ratio = (double) width / (double) combined.getWidth();
        int outHeight = (int) (combined.getHeight() * ratio);

        BufferedImage resized = new BufferedImage(width, outHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D h = resized.createGraphics();
        AffineTransform scale = AffineTransform.getScaleInstance(ratio, ratio);
        h.drawRenderedImage(combined, scale);
        return resized;
    }

    public void setFps(String fps) {
        this.fps = fps;
    }

    public void setBitrateTolerance(int i) {
        this.bitrateTolerance = i;
    }

    private void addArgument(CommandLine cli, String argument, int value) {
        if (value > 0) {
            cli.addArgument("-" + argument);
            cli.addArgument("" + value);
        }
    }

    private void addArgument(CommandLine cli, String argument, int value, String suffix) {
        if (value > 0) {
            cli.addArgument("-" + argument);
            cli.addArgument(value + suffix);
        }
    }

//    private void addArgument(CommandLine cli, String argument, double value) {
//        if (value > 0) {
//            cli.addArgument("-" + argument);
//            cli.addArgument("" + value);
//        }
//    }

    private void addArgument(CommandLine cli, String argument, String value) {
        if (value != null) {
            cli.addArgument("-" + argument);
            cli.addArgument(value);
        }
    }

    public void setAudioChannels(int i) {
        this.audioChannels = i;
    }

    public void setCustomFlags(String[] flags) {
        this.customFlags = flags;
    }

    public void setStartTime(long i) {
        this.startTime = i;
    }

    public void setClipDuration(long i) {
        this.clipDuration = i;
    }

    public void setCropArea(Rectangle rectangle) {
        this.crop = rectangle;
    }

    public void setInputsize(final Dimension inputsize) {
        this.inputsize = inputsize;
    }

    public void setDuration(final long duration) {
        this.duration = duration;
    }

    private class InfoParser extends LogOutputStream {

        private FFMpegWrapper wrapper;

        public InfoParser(final FFMpegWrapper wrapper) {
            this.wrapper = wrapper;
        }

        @Override
        protected void processLine(final String line, final int level) {
            String trimmedLine = line.trim();
            if (trimmedLine.matches("Duration: .*")) {

                // contains the duration
                String duration = trimmedLine.replaceFirst("Duration: (\\d\\d:\\d\\d:\\d\\d.\\d\\d),.*", "$1");

                try {
                    Date time = TIME_FORMAT.parse(duration);
                    wrapper.setDuration(time.getTime());
                } catch (ParseException e) {
                    // ignore
                }

            } else if (trimmedLine.matches("Stream.* Video: .* \\d+x\\d+.*")) {
                String width = trimmedLine.replaceFirst("Stream.* Video: .* (\\d+)x\\d+.*", "$1");
                String height = trimmedLine.replaceFirst("Stream.* Video: .* \\d+x(\\d+).*", "$1");
                wrapper.setInputsize(new Dimension(Integer.parseInt(width), Integer.parseInt(height)));
            }
        }
    }

    /**
     * Turns a video profile into a FFMpegWrapper.
     * 
     * @param inputFile
     *              property file
     * @param profile
     *            can be the cq:Page or jcr:content node of a profile page
     * @param workingDir
     *            working directory for ffmpeg
     * @return  configured FFMpegWrapper
     */
    public static FFMpegWrapper fromProfile(File inputFile, VideoProfile profile, File workingDir) {
        ValueMap props = profile.getProperties();

        FFMpegWrapper wrapper = new FFMpegWrapper(inputFile, workingDir);
        wrapper.setProfileName(profile.getName());
        wrapper.setRenditionSelector(props.get(PN_RENDITION_SELECTOR, String.class));
        wrapper.setOutputExtension(props.get(PN_OUTPUT_EXTENSION, String.class));
        wrapper.setOutputMimetype(props.get(PN_OUTPUT_MIMETYPE, String.class));
        wrapper.setTwoPass(props.get(PN_TWO_PASS, Boolean.FALSE));

        boolean customOnly = props.get(PN_CUSTOM_ARGS_ONLY, Boolean.FALSE);
        if (!customOnly) {
            wrapper.setFps(props.get(PN_VIDEO_FRAME_RATE, String.class));
            wrapper.setBitrate(props.get(PN_VIDEO_BITRATE, Integer.valueOf(0)));
            wrapper.setBitrateTolerance(props.get(PN_VIDEO_BITRATE_TOLERANCE, Integer.valueOf(0)));
            wrapper.setVideoCodec(props.get(PN_VIDEO_CODEC, String.class));
            wrapper.setAudioCodec(props.get(PN_AUDIO_CODEC, String.class));
            wrapper.setAudioChannels(props.get(PN_AUDIO_CHANNELS, Integer.valueOf(0)));
            wrapper.setAudioSamplingRate(props.get(PN_AUDIO_SAMPLING_RATE, Integer.valueOf(0)));
            wrapper.setAudioBitrate(props.get(PN_AUDIO_BITRATE, Integer.valueOf(0)));
            wrapper.setOutputSize(profile.getOutputSize());
            wrapper.setFitInside(props.get(PN_VIDEO_FIT_INSIDE, Boolean.FALSE));
            wrapper.setLetterbox(props.get(PN_VIDEO_LETTERBOX, Boolean.FALSE));
        }
        wrapper.setCustomFlags(formatCustomArgs(props.get(PN_CUSTOM_ARGS, String.class)));
        return wrapper;
    }

    /**
     * 
     * @param customArgs
     * @return
     */
    private static String[] formatCustomArgs(String customArgs) {
        if (customArgs != null) {
            List formattetArgs = new ArrayList();
            String[] splitted = customArgs.split(" ");
            for (String splitt : splitted) {
                formattetArgs.add(splitt.trim());
            }
            return formattetArgs.toArray(new String[formattetArgs.size()]);
        }
        return null;
    }

    /**
     * Tests if the ffmpeg executable cann be found and executed.
     * 
     * @return true if called without error and returning exit code
     *         0, false otherwise.
     */
    private boolean isFfmpegInstalled() {
        if (installed == null) {
            String path = getFfmpegPath();
            CommandLine cmd = new CommandLine(path);
            cmd.addArgument("-version");
            try {
                int exitValue = testExecutor.execute(cmd);
                installed = Boolean.valueOf(exitValue == 0);
            } catch (ExecuteException e) {
                log.warn(e.getMessage(), e);
                installed = Boolean.FALSE;
            } catch (IOException e) {
                log.warn(e.getMessage(), e);
                installed = Boolean.FALSE;
            }

        }
        return installed.booleanValue();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy