/* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) * * 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.gnu.org/licenses/>. */ package it.sauronsoftware.jave; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Hashtable; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Main class of the package. Instances can encode audio and video streams. * * @author Carlo Pelliccia */ public class Encoder { /** * This regexp is used to parse the ffmpeg 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 ffmpeg 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 ffmpeg 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 ffmpeg 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 ffmpeg 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 ffmpeg 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 ffmpeg 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 ffmpeg 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 ffmpeg 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 ); /** * The locator of the ffmpeg executable used by this encoder. */ private FFMPEGLocator locator; /** * It builds an encoder using a {@link DefaultFFMPEGLocator} instance to * locate the ffmpeg executable to use. */ public Encoder() { this.locator = new DefaultFFMPEGLocator(); } /** * It builds an encoder with a custom {@link FFMPEGLocator}. * * @param locator The locator picking up the ffmpeg executable used by the * encoder. */ public Encoder( FFMPEGLocator locator ) { this.locator = locator; } /** * 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 { ArrayList res = new ArrayList(); FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-formats" ); try { ffmpeg.execute(); RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .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 { ffmpeg.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 { ArrayList res = new ArrayList(); FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-formats" ); try { ffmpeg.execute(); RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .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 { ffmpeg.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 { ArrayList res = new ArrayList(); FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-formats" ); try { ffmpeg.execute(); RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .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 { ffmpeg.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 { ArrayList res = new ArrayList(); FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-formats" ); try { ffmpeg.execute(); RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .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 { ffmpeg.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 { ArrayList res = new ArrayList(); FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-formats" ); try { ffmpeg.execute(); RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .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 { ffmpeg.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 { ArrayList res = new ArrayList(); FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-formats" ); try { ffmpeg.execute(); RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .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 { ffmpeg.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 { return getInfo( source, false ); } public MultimediaInfo getInfo( File source, boolean allowNoDurationInfo ) throws InputFormatException, EncoderException { FFMPEGExecutor ffmpeg = locator.createExecutor(); ffmpeg.addArgument( "-i" ); ffmpeg.addArgument( source.getAbsolutePath() ); try { ffmpeg.execute(); } catch ( IOException e ) { throw new EncoderException( e ); } try { RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .getErrorStream() ) ); return parseMultimediaInfo( source, reader, allowNoDurationInfo ); } finally { ffmpeg.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, RBufferedReader reader ) throws InputFormatException, EncoderException { return parseMultimediaInfo( source, reader, false ); } private MultimediaInfo parseMultimediaInfo( File source, RBufferedReader reader, boolean ignoreNoDuration ) 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 p2NA = Pattern.compile( "^\\s*Duration: N/A.*$", 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(); System.out.println( "[ENCODER]: " + line ); 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 { //Duration, bitrate info not available if ( ignoreNoDuration ) { Matcher mNA = p2NA.matcher( line ); if ( mNA.matches() ) { 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 ) ) { VideoInfo video = new VideoInfo(); 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 VideoSize( 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 ) ) { AudioInfo audio = new AudioInfo(); 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 Encoder#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 Hashtable parseProgressInfoLine( String line ) { Hashtable table = null; Matcher m = PROGRESS_INFO_PATTERN.matcher( line ); while ( m.find() ) { if ( table == null ) { table = new Hashtable(); } 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 Encoder#getSupportedDecodingFormats()}, * {@link Encoder#getAudioDecoders()} and * {@link Encoder#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 Encoder#getSupportedDecodingFormats()}, * {@link Encoder#getAudioDecoders()} and * {@link Encoder#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 { encode( source, target, attributes, listener, false ); } public void encode( File source, File target, EncodingAttributes attributes, EncoderProgressListener listener, boolean allowNoDurationInfo ) 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(); FFMPEGExecutor ffmpeg = locator.createExecutor(); if ( offsetAttribute != null ) { ffmpeg.addArgument( "-ss" ); ffmpeg.addArgument( String.valueOf( offsetAttribute.floatValue() ) ); } ffmpeg.addArgument( "-i" ); ffmpeg.addArgument( source.getAbsolutePath() ); if ( durationAttribute != null ) { ffmpeg.addArgument( "-t" ); ffmpeg.addArgument( String.valueOf( durationAttribute.floatValue() ) ); } if ( videoAttributes == null ) { ffmpeg.addArgument( "-vn" ); } else { String codec = videoAttributes.getCodec(); if ( codec != null ) { ffmpeg.addArgument( "-vcodec" ); ffmpeg.addArgument( codec ); } String tag = videoAttributes.getTag(); if ( tag != null ) { ffmpeg.addArgument( "-vtag" ); ffmpeg.addArgument( tag ); } Integer bitRate = videoAttributes.getBitRate(); if ( bitRate != null ) { ffmpeg.addArgument( "-b" ); ffmpeg.addArgument( String.valueOf( bitRate.intValue() ) ); } Integer frameRate = videoAttributes.getFrameRate(); if ( frameRate != null ) { ffmpeg.addArgument( "-r" ); ffmpeg.addArgument( String.valueOf( frameRate.intValue() ) ); } VideoSize size = videoAttributes.getSize(); if ( size != null ) { ffmpeg.addArgument( "-s" ); ffmpeg.addArgument( String.valueOf( size.getWidth() ) + "x" + String.valueOf( size.getHeight() ) ); } } if ( audioAttributes == null ) { ffmpeg.addArgument( "-an" ); } else { String codec = audioAttributes.getCodec(); if ( codec != null ) { ffmpeg.addArgument( "-acodec" ); ffmpeg.addArgument( codec ); } Integer bitRate = audioAttributes.getBitRate(); if ( bitRate != null ) { ffmpeg.addArgument( "-ab" ); ffmpeg.addArgument( String.valueOf( bitRate.intValue() ) ); } Integer channels = audioAttributes.getChannels(); if ( channels != null ) { ffmpeg.addArgument( "-ac" ); ffmpeg.addArgument( String.valueOf( channels.intValue() ) ); } Integer samplingRate = audioAttributes.getSamplingRate(); if ( samplingRate != null ) { ffmpeg.addArgument( "-ar" ); ffmpeg.addArgument( String.valueOf( samplingRate.intValue() ) ); } Integer volume = audioAttributes.getVolume(); if ( volume != null ) { ffmpeg.addArgument( "-vol" ); ffmpeg.addArgument( String.valueOf( volume.intValue() ) ); } } ffmpeg.addArgument( "-f" ); ffmpeg.addArgument( formatAttribute ); ffmpeg.addArgument( "-y" ); ffmpeg.addArgument( target.getAbsolutePath() ); try { ffmpeg.execute(); } catch ( IOException e ) { throw new EncoderException( e ); } try { String lastLine = null; long duration = 0; long progress = 0; RBufferedReader reader = null; reader = new RBufferedReader( new InputStreamReader( ffmpeg .getErrorStream() ) ); MultimediaInfo info = parseMultimediaInfo( source, reader, allowNoDurationInfo ); allowNoDurationInfo = ( allowNoDurationInfo && info.getDuration() == -1 ); if ( !allowNoDurationInfo ) { 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 ) { System.out.println( "[ENCODER]: " + line ); switch ( step ) { case 0: if ( line.startsWith( "WARNING: " ) ) { if ( listener != null ) { listener.message( line ); } } else { step++; } break; case 1: case 2: case 3: step++; break; case 4: line = line.trim(); lastLine = line; if ( line.length() > 0 ) { Hashtable table = parseProgressInfoLine( line ); if ( table == null ) { if ( listener != null ) { listener.message( 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 && ( !allowNoDurationInfo && 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; } progress = perm; listener.progress( perm ); } catch ( NumberFormatException e ) { ; } } } } } break; } } if ( lastLine != null ) { if ( !SUCCESS_PATTERN.matcher( lastLine ).matches() ) { throw new EncoderException( lastLine ); } else if ( progress < 1000 ) { listener.progress( 1000 ); } } } catch ( IOException e ) { throw new EncoderException( e ); } finally { ffmpeg.destroy(); } } }