Java tutorial
/* * 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(); } } }