com.appcel.facade.encoder.service.impl.EncoderServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.appcel.facade.encoder.service.impl.EncoderServiceImpl.java

Source

/*
 * Copyright (c) 2006, Pointdew Inc. All rights reserved.
 * 
 * Encoder A Java Audio/Video Encoder (based on encoderExcutor)
 * 
 * Copyright (C) 2006-2014 LiRongSheng (http://www.pointdew.com)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.pointdew.com>.
 */
package com.appcel.facade.encoder.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.stereotype.Service;

import com.appcel.core.encoder.executor.EncoderBufferedReader;
import com.appcel.core.encoder.executor.EncoderExecutor;
import com.appcel.core.encoder.locator.AbstractEncoderLocator;
import com.appcel.core.encoder.locator.EncoderLocator;
import com.appcel.facade.encoder.DTO.AudioAttributes;
import com.appcel.facade.encoder.DTO.AudioInfoDTO;
import com.appcel.facade.encoder.DTO.EncodingAttributes;
import com.appcel.facade.encoder.DTO.MultimediaInfo;
import com.appcel.facade.encoder.DTO.VideoAttributes;
import com.appcel.facade.encoder.DTO.VideoInfoDTO;
import com.appcel.facade.encoder.DTO.VideoSizeDTO;
import com.appcel.facade.encoder.enums.EncoderExecutorEnum;
import com.appcel.facade.encoder.exception.EncoderException;
import com.appcel.facade.encoder.exception.InputFormatException;
import com.appcel.facade.encoder.listener.EncoderProgressListener;
import com.appcel.facade.encoder.service.EncoderService;

/**
 * 
 * Defiend class file the Encoder.java
 * 
 * Main class of the package. Instances can encode audio and video streams.
 * 
 * @author LiRongSheng
 * @author Rock.Lee
 * @version appcel 1.0.0
 * @since JDK-1.7.0
 * @date 2014-12-2
 *
 */
@Service("encoderService")
public class EncoderServiceImpl implements EncoderService {

    /**
     * This regexp is used to parse the encoderExcutor output about the supported
     * formats.
     */
    private static final Pattern FORMAT_PATTERN = Pattern.compile("^\\s*([D ])([E ])\\s+([\\w,]+)\\s+.+$");

    /**
     * This regexp is used to parse the encoderExcutor output about the included
     * encoders/decoders.
     */
    private static final Pattern ENCODER_DECODER_PATTERN = Pattern.compile("^\\s*([D ])([E ])([AVS]).{3}\\s+(.+)$",
            Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the ongoing encoding
     * process.
     */
    private static final Pattern PROGRESS_INFO_PATTERN = Pattern.compile("\\s*(\\w+)\\s*=\\s*(\\S+)\\s*",
            Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the size of a video
     * stream.
     */
    private static final Pattern SIZE_PATTERN = Pattern.compile("(\\d+)x(\\d+)", Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the frame rate value
     * of a video stream.
     */
    private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("([\\d.]+)\\s+(?:fps|tb\\(r\\))",
            Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the bit rate value
     * of a stream.
     */
    private static final Pattern BIT_RATE_PATTERN = Pattern.compile("(\\d+)\\s+kb/s", Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the sampling rate of
     * an audio stream.
     */
    private static final Pattern SAMPLING_RATE_PATTERN = Pattern.compile("(\\d+)\\s+Hz", Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the channels number
     * of an audio stream.
     */
    private static final Pattern CHANNELS_PATTERN = Pattern.compile("(mono|stereo)", Pattern.CASE_INSENSITIVE);

    /**
     * This regexp is used to parse the encoderExcutor output about the success of an
     * encoding operation.
     */
    private static final Pattern SUCCESS_PATTERN = Pattern
            .compile("^\\s*video\\:\\S+\\s+audio\\:\\S+\\s+global headers\\:\\S+.*$", Pattern.CASE_INSENSITIVE);

    private EncoderExecutor encoderExcutor;

    /**
     * It builds an encoder using a {@link EncoderLocator} instance to
     * locate the ffmpeg executable to use.
     */
    public EncoderServiceImpl() {
        this.encoderExcutor = EncoderLocator.getMe().createEncoderExecutor(EncoderExecutorEnum.FFMPEGEXECUTOR);
    }

    /**
     * It builds an encoder with a custom {@link AbstractEncoderLocator}.
     * 
     * @param locator
     *            The locator picking up the ffmpeg executable used by the
     *            encoder.
     */
    public EncoderServiceImpl(EncoderExecutorEnum encoderEnum) {
        this.encoderExcutor = EncoderLocator.getMe().createEncoderExecutor(encoderEnum);
    }

    /**
     * Returns a list with the names of all the audio decoders bundled with the
     * ffmpeg distribution in use. An audio stream can be decoded only if a
     * decoder for its format is available.
     * 
     * @return A list with the names of all the included audio decoders.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public String[] getAudioDecoders() throws EncoderException {
        List<String> res = new ArrayList<String>();

        encoderExcutor.addArgument("-formats");
        try {
            encoderExcutor.execute();
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getInputStream()));
            String line;
            boolean evaluate = false;
            while ((line = reader.readLine()) != null) {
                if (line.trim().length() == 0) {
                    continue;
                }
                if (evaluate) {
                    Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
                    if (matcher.matches()) {
                        String decoderFlag = matcher.group(1);
                        String audioVideoFlag = matcher.group(3);
                        if ("D".equals(decoderFlag) && "A".equals(audioVideoFlag)) {
                            String name = matcher.group(4);
                            res.add(name);
                        }
                    } else {
                        break;
                    }
                } else if (line.trim().equals("Codecs:")) {
                    evaluate = true;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
        int size = res.size();
        String[] ret = new String[size];
        for (int i = 0; i < size; i++) {
            ret[i] = (String) res.get(i);
        }
        return ret;
    }

    /**
     * Returns a list with the names of all the audio encoders bundled with the
     * ffmpeg distribution in use. An audio stream can be encoded using one of
     * these encoders.
     * 
     * @return A list with the names of all the included audio encoders.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public String[] getAudioEncoders() throws EncoderException {
        List<String> res = new ArrayList<String>();

        encoderExcutor.addArgument("-formats");
        try {
            encoderExcutor.execute();
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getInputStream()));
            String line;
            boolean evaluate = false;
            while ((line = reader.readLine()) != null) {
                if (line.trim().length() == 0) {
                    continue;
                }
                if (evaluate) {
                    Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
                    if (matcher.matches()) {
                        String encoderFlag = matcher.group(2);
                        String audioVideoFlag = matcher.group(3);
                        if ("E".equals(encoderFlag) && "A".equals(audioVideoFlag)) {
                            String name = matcher.group(4);
                            res.add(name);
                        }
                    } else {
                        break;
                    }
                } else if (line.trim().equals("Codecs:")) {
                    evaluate = true;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
        int size = res.size();
        String[] ret = new String[size];
        for (int i = 0; i < size; i++) {
            ret[i] = (String) res.get(i);
        }
        return ret;
    }

    /** 
     * Returns a list with the names of all the video decoders bundled with the
     * ffmpeg distribution in use. A video stream can be decoded only if a
     * decoder for its format is available.
     * 
     * @return A list with the names of all the included video decoders.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public String[] getVideoDecoders() throws EncoderException {
        List<String> res = new ArrayList<String>();

        encoderExcutor.addArgument("-formats");
        try {
            encoderExcutor.execute();
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getInputStream()));
            String line;
            boolean evaluate = false;
            while ((line = reader.readLine()) != null) {
                if (line.trim().length() == 0) {
                    continue;
                }
                if (evaluate) {
                    Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
                    if (matcher.matches()) {
                        String decoderFlag = matcher.group(1);
                        String audioVideoFlag = matcher.group(3);
                        if ("D".equals(decoderFlag) && "V".equals(audioVideoFlag)) {
                            String name = matcher.group(4);
                            res.add(name);
                        }
                    } else {
                        break;
                    }
                } else if (line.trim().equals("Codecs:")) {
                    evaluate = true;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
        int size = res.size();
        String[] ret = new String[size];
        for (int i = 0; i < size; i++) {
            ret[i] = (String) res.get(i);
        }
        return ret;
    }

    /**
     * Returns a list with the names of all the video encoders bundled with the
     * ffmpeg distribution in use. A video stream can be encoded using one of
     * these encoders.
     * 
     * @return A list with the names of all the included video encoders.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public String[] getVideoEncoders() throws EncoderException {
        List<String> res = new ArrayList<String>();

        encoderExcutor.addArgument("-formats");
        try {
            encoderExcutor.execute();
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getInputStream()));
            String line;
            boolean evaluate = false;
            while ((line = reader.readLine()) != null) {
                if (line.trim().length() == 0) {
                    continue;
                }
                if (evaluate) {
                    Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
                    if (matcher.matches()) {
                        String encoderFlag = matcher.group(2);
                        String audioVideoFlag = matcher.group(3);
                        if ("E".equals(encoderFlag) && "V".equals(audioVideoFlag)) {
                            String name = matcher.group(4);
                            res.add(name);
                        }
                    } else {
                        break;
                    }
                } else if (line.trim().equals("Codecs:")) {
                    evaluate = true;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
        int size = res.size();
        String[] ret = new String[size];
        for (int i = 0; i < size; i++) {
            ret[i] = (String) res.get(i);
        }
        return ret;
    }

    /**
     * Returns a list with the names of all the file formats supported at
     * encoding time by the underlying ffmpeg distribution. A multimedia file
     * could be encoded and generated only if the specified format is in this
     * list.
     * 
     * @return A list with the names of all the supported file formats at
     *         encoding time.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public String[] getSupportedEncodingFormats() throws EncoderException {
        List<String> res = new ArrayList<String>();

        encoderExcutor.addArgument("-formats");
        try {
            encoderExcutor.execute();
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getInputStream()));
            String line;
            boolean evaluate = false;
            while ((line = reader.readLine()) != null) {
                if (line.trim().length() == 0) {
                    continue;
                }
                if (evaluate) {
                    Matcher matcher = FORMAT_PATTERN.matcher(line);
                    if (matcher.matches()) {
                        String encoderFlag = matcher.group(2);
                        if ("E".equals(encoderFlag)) {
                            String aux = matcher.group(3);
                            StringTokenizer st = new StringTokenizer(aux, ",");
                            while (st.hasMoreTokens()) {
                                String token = st.nextToken().trim();
                                if (!res.contains(token)) {
                                    res.add(token);
                                }
                            }
                        }
                    } else {
                        break;
                    }
                } else if (line.trim().equals("File formats:")) {
                    evaluate = true;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
        int size = res.size();
        String[] ret = new String[size];
        for (int i = 0; i < size; i++) {
            ret[i] = (String) res.get(i);
        }
        return ret;
    }

    /**
     * Returns a list with the names of all the file formats supported at
     * decoding time by the underlying ffmpeg distribution. A multimedia file
     * could be open and decoded only if its format is in this list.
     * 
     * @return A list with the names of all the supported file formats at
     *         decoding time.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public String[] getSupportedDecodingFormats() throws EncoderException {
        List<String> res = new ArrayList<String>();

        encoderExcutor.addArgument("-formats");
        try {
            encoderExcutor.execute();
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getInputStream()));
            String line;
            boolean evaluate = false;
            while ((line = reader.readLine()) != null) {
                if (line.trim().length() == 0) {
                    continue;
                }
                if (evaluate) {
                    Matcher matcher = FORMAT_PATTERN.matcher(line);
                    if (matcher.matches()) {
                        String decoderFlag = matcher.group(1);
                        if ("D".equals(decoderFlag)) {
                            String aux = matcher.group(3);
                            StringTokenizer st = new StringTokenizer(aux, ",");
                            while (st.hasMoreTokens()) {
                                String token = st.nextToken().trim();
                                if (!res.contains(token)) {
                                    res.add(token);
                                }
                            }
                        }
                    } else {
                        break;
                    }
                } else if (line.trim().equals("File formats:")) {
                    evaluate = true;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
        int size = res.size();
        String[] ret = new String[size];
        for (int i = 0; i < size; i++) {
            ret[i] = (String) res.get(i);
        }
        return ret;
    }

    /**
     * Returns a set informations about a multimedia file, if its format is
     * supported for decoding.
     * 
     * @param source
     *            The source multimedia file.
     * @return A set of informations about the file and its contents.
     * @throws InputFormatException
     *             If the format of the source file cannot be recognized and
     *             decoded.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    public MultimediaInfo getInfo(File source) throws InputFormatException, EncoderException {

        encoderExcutor.addArgument("-i");
        encoderExcutor.addArgument(source.getAbsolutePath());
        try {
            encoderExcutor.execute();
        } catch (EncoderException e) {
            throw new EncoderException(e);
        }
        try {
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getErrorStream()));
            return parseMultimediaInfo(source, reader);
        } finally {
            encoderExcutor.destroy();
        }
    }

    /**
     * Private utility. It parses the ffmpeg output, extracting informations
     * about a source multimedia file.
     * 
     * @param source
     *            The source multimedia file.
     * @param reader
     *            The ffmpeg output channel.
     * @return A set of informations about the source multimedia file and its
     *         contents.
     * @throws InputFormatException
     *             If the format of the source file cannot be recognized and
     *             decoded.
     * @throws EncoderException
     *             If a problem occurs calling the underlying ffmpeg executable.
     */
    private MultimediaInfo parseMultimediaInfo(File source, EncoderBufferedReader reader)
            throws InputFormatException, EncoderException {
        Pattern p1 = Pattern.compile("^\\s*Input #0, (\\w+).+$\\s*", Pattern.CASE_INSENSITIVE);
        Pattern p2 = Pattern.compile("^\\s*Duration: (\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d).*$",
                Pattern.CASE_INSENSITIVE);
        Pattern p3 = Pattern.compile("^\\s*Stream #\\S+: ((?:Audio)|(?:Video)|(?:Data)): (.*)\\s*$",
                Pattern.CASE_INSENSITIVE);
        MultimediaInfo info = null;
        try {
            int step = 0;
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                if (step == 0) {
                    String token = source.getAbsolutePath() + ": ";
                    if (line.startsWith(token)) {
                        String message = line.substring(token.length());
                        throw new InputFormatException(message);
                    }
                    Matcher m = p1.matcher(line);
                    if (m.matches()) {
                        String format = m.group(1);
                        info = new MultimediaInfo();
                        info.setFormat(format);
                        step++;
                    }
                } else if (step == 1) {
                    Matcher m = p2.matcher(line);
                    if (m.matches()) {
                        long hours = Integer.parseInt(m.group(1));
                        long minutes = Integer.parseInt(m.group(2));
                        long seconds = Integer.parseInt(m.group(3));
                        long dec = Integer.parseInt(m.group(4));
                        long duration = (dec * 100L) + (seconds * 1000L) + (minutes * 60L * 1000L)
                                + (hours * 60L * 60L * 1000L);
                        info.setDuration(duration);
                        step++;
                    } else {
                        step = 3;
                    }
                } else if (step == 2) {
                    Matcher m = p3.matcher(line);
                    if (m.matches()) {
                        String type = m.group(1);
                        String specs = m.group(2);
                        if ("Video".equalsIgnoreCase(type)) {
                            VideoInfoDTO video = new VideoInfoDTO();
                            StringTokenizer st = new StringTokenizer(specs, ",");
                            for (int i = 0; st.hasMoreTokens(); i++) {
                                String token = st.nextToken().trim();
                                if (i == 0) {
                                    video.setDecoder(token);
                                } else {
                                    boolean parsed = false;
                                    // Video size.
                                    Matcher m2 = SIZE_PATTERN.matcher(token);
                                    if (!parsed && m2.find()) {
                                        int width = Integer.parseInt(m2.group(1));
                                        int height = Integer.parseInt(m2.group(2));
                                        video.setSize(new VideoSizeDTO(width, height));
                                        parsed = true;
                                    }
                                    // Frame rate.
                                    m2 = FRAME_RATE_PATTERN.matcher(token);
                                    if (!parsed && m2.find()) {
                                        try {
                                            float frameRate = Float.parseFloat(m2.group(1));
                                            video.setFrameRate(frameRate);
                                        } catch (NumberFormatException e) {
                                            ;
                                        }
                                        parsed = true;
                                    }
                                    // Bit rate.
                                    m2 = BIT_RATE_PATTERN.matcher(token);
                                    if (!parsed && m2.find()) {
                                        int bitRate = Integer.parseInt(m2.group(1));
                                        video.setBitRate(bitRate);
                                        parsed = true;
                                    }
                                }
                            }
                            info.setVideo(video);
                        } else if ("Audio".equalsIgnoreCase(type)) {
                            AudioInfoDTO audio = new AudioInfoDTO();
                            StringTokenizer st = new StringTokenizer(specs, ",");
                            for (int i = 0; st.hasMoreTokens(); i++) {
                                String token = st.nextToken().trim();
                                if (i == 0) {
                                    audio.setDecoder(token);
                                } else {
                                    boolean parsed = false;
                                    // Sampling rate.
                                    Matcher m2 = SAMPLING_RATE_PATTERN.matcher(token);
                                    if (!parsed && m2.find()) {
                                        int samplingRate = Integer.parseInt(m2.group(1));
                                        audio.setSamplingRate(samplingRate);
                                        parsed = true;
                                    }
                                    // Channels.
                                    m2 = CHANNELS_PATTERN.matcher(token);
                                    if (!parsed && m2.find()) {
                                        String ms = m2.group(1);
                                        if ("mono".equalsIgnoreCase(ms)) {
                                            audio.setChannels(1);
                                        } else if ("stereo".equalsIgnoreCase(ms)) {
                                            audio.setChannels(2);
                                        }
                                        parsed = true;
                                    }
                                    // Bit rate.
                                    m2 = BIT_RATE_PATTERN.matcher(token);
                                    if (!parsed && m2.find()) {
                                        int bitRate = Integer.parseInt(m2.group(1));
                                        audio.setBitRate(bitRate);
                                        parsed = true;
                                    }
                                }
                            }
                            info.setAudio(audio);
                        }
                    } else {
                        step = 3;
                    }
                }
                if (step == 3) {
                    reader.reinsertLine(line);
                    break;
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        }
        if (info == null) {
            throw new InputFormatException();
        }
        return info;
    }

    /**
     * Private utility. Parse a line and try to match its contents against the
     * {@link EncoderServiceImpl#PROGRESS_INFO_PATTERN} pattern. It the line can be parsed,
     * it returns a hashtable with progress informations, otherwise it returns
     * null.
     * 
     * @param line
     *            The line from the ffmpeg output.
     * @return A hashtable with the value reported in the line, or null if the
     *         given line can not be parsed.
     */
    private Map<String, String> parseProgressInfoLine(String line) {
        Map<String, String> table = null;
        Matcher m = PROGRESS_INFO_PATTERN.matcher(line);
        while (m.find()) {
            if (table == null) {
                table = new Hashtable<String, String>();
            }
            String key = m.group(1);
            String value = m.group(2);
            table.put(key, value);
        }
        return table;
    }

    /**
     * Re-encode a multimedia file.
     * 
     * @param source
     *            The source multimedia file. It cannot be null. Be sure this
     *            file can be decoded (see
     *            {@link EncoderServiceImpl#getSupportedDecodingFormats()},
     *            {@link EncoderServiceImpl#getAudioDecoders()} and
     *            {@link EncoderServiceImpl#getVideoDecoders()}).
     * @param target
     *            The target multimedia re-encoded file. It cannot be null. If
     *            this file already exists, it will be overwrited.
     * @param attributes
     *            A set of attributes for the encoding process.
     * @throws IllegalArgumentException
     *             If both audio and video parameters are null.
     * @throws InputFormatException
     *             If the source multimedia file cannot be decoded.
     * @throws EncoderException
     *             If a problems occurs during the encoding process.
     */
    public void encode(File source, File target, EncodingAttributes attributes)
            throws IllegalArgumentException, InputFormatException, EncoderException {
        encode(source, target, attributes, null);
    }

    /**
     * Re-encode a multimedia file.
     * 
     * @param source
     *            The source multimedia file. It cannot be null. Be sure this
     *            file can be decoded (see
     *            {@link EncoderServiceImpl#getSupportedDecodingFormats()},
     *            {@link EncoderServiceImpl#getAudioDecoders()} and
     *            {@link EncoderServiceImpl#getVideoDecoders()}).
     * @param target
     *            The target multimedia re-encoded file. It cannot be null. If
     *            this file already exists, it will be overwrited.
     * @param attributes
     *            A set of attributes for the encoding process.
     * @param listener
     *            An optional progress listener for the encoding process. It can
     *            be null.
     * @throws IllegalArgumentException
     *             If both audio and video parameters are null.
     * @throws InputFormatException
     *             If the source multimedia file cannot be decoded.
     * @throws EncoderException
     *             If a problems occurs during the encoding process.
     */
    public void encode(File source, File target, EncodingAttributes attributes, EncoderProgressListener listener)
            throws IllegalArgumentException, InputFormatException, EncoderException {
        String formatAttribute = attributes.getFormat();
        Float offsetAttribute = attributes.getOffset();
        Float durationAttribute = attributes.getDuration();
        AudioAttributes audioAttributes = attributes.getAudioAttributes();
        VideoAttributes videoAttributes = attributes.getVideoAttributes();
        if (audioAttributes == null && videoAttributes == null) {
            throw new IllegalArgumentException("Both audio and video attributes are null");
        }
        target = target.getAbsoluteFile();
        target.getParentFile().mkdirs();

        if (offsetAttribute != null) {
            encoderExcutor.addArgument("-ss");
            encoderExcutor.addArgument(String.valueOf(offsetAttribute.floatValue()));
        }
        encoderExcutor.addArgument("-i");
        encoderExcutor.addArgument(source.getAbsolutePath());
        if (durationAttribute != null) {
            encoderExcutor.addArgument("-t");
            encoderExcutor.addArgument(String.valueOf(durationAttribute.floatValue()));
        }
        if (videoAttributes == null) {
            encoderExcutor.addArgument("-vn");
        } else {
            String codec = videoAttributes.getCodec();
            if (codec != null) {
                encoderExcutor.addArgument("-vcodec");
                encoderExcutor.addArgument(codec);
            }
            String tag = videoAttributes.getTag();
            if (tag != null) {
                encoderExcutor.addArgument("-vtag");
                encoderExcutor.addArgument(tag);
            }
            Integer bitRate = videoAttributes.getBitRate();
            if (bitRate != null) {
                encoderExcutor.addArgument("-b");
                encoderExcutor.addArgument(String.valueOf(bitRate.intValue()));
            }
            Integer frameRate = videoAttributes.getFrameRate();
            if (frameRate != null) {
                encoderExcutor.addArgument("-r");
                encoderExcutor.addArgument(String.valueOf(frameRate.intValue()));
            }
            VideoSizeDTO size = videoAttributes.getSize();
            if (size != null) {
                encoderExcutor.addArgument("-s");
                encoderExcutor
                        .addArgument(String.valueOf(size.getWidth()) + "x" + String.valueOf(size.getHeight()));
            }
        }
        if (audioAttributes == null) {
            encoderExcutor.addArgument("-an");
        } else {
            String codec = audioAttributes.getCodec();
            if (codec != null) {
                encoderExcutor.addArgument("-acodec");
                encoderExcutor.addArgument(codec);
            }
            Integer bitRate = audioAttributes.getBitRate();
            if (bitRate != null) {
                encoderExcutor.addArgument("-ab");
                encoderExcutor.addArgument(String.valueOf(bitRate.intValue()));
            }
            Integer channels = audioAttributes.getChannels();
            if (channels != null) {
                encoderExcutor.addArgument("-ac");
                encoderExcutor.addArgument(String.valueOf(channels.intValue()));
            }
            Integer samplingRate = audioAttributes.getSamplingRate();
            if (samplingRate != null) {
                encoderExcutor.addArgument("-ar");
                encoderExcutor.addArgument(String.valueOf(samplingRate.intValue()));
            }
            Integer volume = audioAttributes.getVolume();
            if (volume != null) {
                encoderExcutor.addArgument("-vol");
                encoderExcutor.addArgument(String.valueOf(volume.intValue()));
            }
        }
        encoderExcutor.addArgument("-f");
        encoderExcutor.addArgument(formatAttribute);
        encoderExcutor.addArgument("-y");
        encoderExcutor.addArgument(target.getAbsolutePath());
        try {
            encoderExcutor.execute();
        } catch (EncoderException e) {
            throw new EncoderException(e);
        }
        try {
            String lastWarning = null;
            long duration;
            long progress = 0;
            EncoderBufferedReader reader = null;
            reader = new EncoderBufferedReader(new InputStreamReader(encoderExcutor.getErrorStream()));
            MultimediaInfo info = parseMultimediaInfo(source, reader);
            if (durationAttribute != null) {
                duration = (long) Math.round((durationAttribute.floatValue() * 1000L));
            } else {
                duration = info.getDuration();
                if (offsetAttribute != null) {
                    duration -= (long) Math.round((offsetAttribute.floatValue() * 1000L));
                }
            }
            if (listener != null) {
                listener.sourceInfo(info);
            }
            int step = 0;
            String line;
            while ((line = reader.readLine()) != null) {
                if (step == 0) {
                    if (line.startsWith("WARNING: ")) {
                        if (listener != null) {
                            listener.message(line);
                        }
                    } else if (!line.startsWith("Output #0")) {
                        throw new EncoderException(line);
                    } else {
                        step++;
                    }
                } else if (step == 1) {
                    if (!line.startsWith("  ")) {
                        step++;
                    }
                }
                if (step == 2) {
                    if (!line.startsWith("Stream mapping:")) {
                        throw new EncoderException(line);
                    } else {
                        step++;
                    }
                } else if (step == 3) {
                    if (!line.startsWith("  ")) {
                        step++;
                    }
                }
                if (step == 4) {
                    line = line.trim();
                    if (line.length() > 0) {
                        Map<String, String> table = parseProgressInfoLine(line);
                        if (table == null) {
                            if (listener != null) {
                                listener.message(line);
                            }
                            lastWarning = line;
                        } else {
                            if (listener != null) {
                                String time = (String) table.get("time");
                                if (time != null) {
                                    int dot = time.indexOf('.');
                                    if (dot > 0 && dot == time.length() - 2 && duration > 0) {
                                        String p1 = time.substring(0, dot);
                                        String p2 = time.substring(dot + 1);
                                        try {
                                            long i1 = Long.parseLong(p1);
                                            long i2 = Long.parseLong(p2);
                                            progress = (i1 * 1000L) + (i2 * 100L);
                                            int perm = (int) Math
                                                    .round((double) (progress * 1000L) / (double) duration);
                                            if (perm > 1000) {
                                                perm = 1000;
                                            }
                                            listener.progress(perm);
                                        } catch (NumberFormatException e) {
                                            ;
                                        }
                                    }
                                }
                            }
                            lastWarning = null;
                        }
                    }
                }
            }
            if (lastWarning != null) {
                if (!SUCCESS_PATTERN.matcher(lastWarning).matches()) {
                    throw new EncoderException(lastWarning);
                }
            }
        } catch (IOException e) {
            throw new EncoderException(e);
        } finally {
            encoderExcutor.destroy();
        }
    }

}