001package net.bramp.ffmpeg; 002 003import static com.google.common.base.MoreObjects.firstNonNull; 004import static com.google.common.base.Preconditions.checkNotNull; 005 006import com.google.common.collect.ImmutableList; 007import java.io.BufferedReader; 008import java.io.IOException; 009import java.net.URISyntaxException; 010import java.util.ArrayList; 011import java.util.List; 012import java.util.regex.Matcher; 013import java.util.regex.Pattern; 014import javax.annotation.CheckReturnValue; 015import javax.annotation.Nonnull; 016import javax.annotation.Nullable; 017import net.bramp.ffmpeg.builder.FFmpegBuilder; 018import net.bramp.ffmpeg.info.Codec; 019import net.bramp.ffmpeg.info.Format; 020import net.bramp.ffmpeg.progress.ProgressListener; 021import net.bramp.ffmpeg.progress.ProgressParser; 022import net.bramp.ffmpeg.progress.TcpProgressParser; 023import org.apache.commons.lang3.math.Fraction; 024 025/** 026 * Wrapper around FFmpeg 027 * 028 * @author bramp 029 */ 030public class FFmpeg extends FFcommon { 031 032 public static final String FFMPEG = "ffmpeg"; 033 public static final String DEFAULT_PATH = firstNonNull(System.getenv("FFMPEG"), FFMPEG); 034 035 public static final Fraction FPS_30 = Fraction.getFraction(30, 1); 036 public static final Fraction FPS_29_97 = Fraction.getFraction(30000, 1001); 037 public static final Fraction FPS_24 = Fraction.getFraction(24, 1); 038 public static final Fraction FPS_23_976 = Fraction.getFraction(24000, 1001); 039 040 public static final int AUDIO_MONO = 1; 041 public static final int AUDIO_STEREO = 2; 042 043 public static final String AUDIO_FORMAT_U8 = "u8"; // 8 044 public static final String AUDIO_FORMAT_S16 = "s16"; // 16 045 public static final String AUDIO_FORMAT_S32 = "s32"; // 32 046 public static final String AUDIO_FORMAT_FLT = "flt"; // 32 047 public static final String AUDIO_FORMAT_DBL = "dbl"; // 64 048 049 @Deprecated public static final String AUDIO_DEPTH_U8 = AUDIO_FORMAT_U8; 050 @Deprecated public static final String AUDIO_DEPTH_S16 = AUDIO_FORMAT_S16; 051 @Deprecated public static final String AUDIO_DEPTH_S32 = AUDIO_FORMAT_S32; 052 @Deprecated public static final String AUDIO_DEPTH_FLT = AUDIO_FORMAT_FLT; 053 @Deprecated public static final String AUDIO_DEPTH_DBL = AUDIO_FORMAT_DBL; 054 055 public static final int AUDIO_SAMPLE_8000 = 8000; 056 public static final int AUDIO_SAMPLE_11025 = 11025; 057 public static final int AUDIO_SAMPLE_12000 = 12000; 058 public static final int AUDIO_SAMPLE_16000 = 16000; 059 public static final int AUDIO_SAMPLE_22050 = 22050; 060 public static final int AUDIO_SAMPLE_32000 = 32000; 061 public static final int AUDIO_SAMPLE_44100 = 44100; 062 public static final int AUDIO_SAMPLE_48000 = 48000; 063 public static final int AUDIO_SAMPLE_96000 = 96000; 064 065 static final Pattern CODECS_REGEX = 066 Pattern.compile("^ ([ D][ E][VAS][ S][ D][ T]) (\\S+)\\s+(.*)$"); 067 static final Pattern FORMATS_REGEX = Pattern.compile("^ ([ D][ E]) (\\S+)\\s+(.*)$"); 068 069 /** Supported codecs */ 070 List<Codec> codecs = null; 071 072 /** Supported formats */ 073 List<Format> formats = null; 074 075 public FFmpeg() throws IOException { 076 this(DEFAULT_PATH, new RunProcessFunction()); 077 } 078 079 public FFmpeg(@Nonnull ProcessFunction runFunction) throws IOException { 080 this(DEFAULT_PATH, runFunction); 081 } 082 083 public FFmpeg(@Nonnull String path) throws IOException { 084 this(path, new RunProcessFunction()); 085 } 086 087 public FFmpeg(@Nonnull String path, @Nonnull ProcessFunction runFunction) throws IOException { 088 super(path, runFunction); 089 version(); 090 } 091 092 /** 093 * Returns true if the binary we are using is the true ffmpeg. This is to avoid conflict with 094 * avconv (from the libav project), that some symlink to ffmpeg. 095 * 096 * @return true iff this is the official ffmpeg binary. 097 * @throws IOException If a I/O error occurs while executing ffmpeg. 098 */ 099 public boolean isFFmpeg() throws IOException { 100 return version().startsWith("ffmpeg"); 101 } 102 103 /** 104 * Throws an exception if this is an unsupported version of ffmpeg. 105 * 106 * @throws IllegalArgumentException if this is not the official ffmpeg binary. 107 * @throws IOException If a I/O error occurs while executing ffmpeg. 108 */ 109 private void checkIfFFmpeg() throws IllegalArgumentException, IOException { 110 if (!isFFmpeg()) { 111 throw new IllegalArgumentException( 112 "This binary '" + path + "' is not a supported version of ffmpeg"); 113 } 114 } 115 116 public synchronized @Nonnull List<Codec> codecs() throws IOException { 117 checkIfFFmpeg(); 118 119 if (this.codecs == null) { 120 codecs = new ArrayList<>(); 121 122 Process p = runFunc.run(ImmutableList.of(path, "-codecs")); 123 try { 124 BufferedReader r = wrapInReader(p); 125 String line; 126 while ((line = r.readLine()) != null) { 127 Matcher m = CODECS_REGEX.matcher(line); 128 if (!m.matches()) continue; 129 130 codecs.add(new Codec(m.group(2), m.group(3), m.group(1))); 131 } 132 133 throwOnError(p); 134 this.codecs = ImmutableList.copyOf(codecs); 135 } finally { 136 p.destroy(); 137 } 138 } 139 140 return codecs; 141 } 142 143 public synchronized @Nonnull List<Format> formats() throws IOException { 144 checkIfFFmpeg(); 145 146 if (this.formats == null) { 147 formats = new ArrayList<>(); 148 149 Process p = runFunc.run(ImmutableList.of(path, "-formats")); 150 try { 151 BufferedReader r = wrapInReader(p); 152 String line; 153 while ((line = r.readLine()) != null) { 154 Matcher m = FORMATS_REGEX.matcher(line); 155 if (!m.matches()) continue; 156 157 formats.add(new Format(m.group(2), m.group(3), m.group(1))); 158 } 159 160 throwOnError(p); 161 this.formats = ImmutableList.copyOf(formats); 162 } finally { 163 p.destroy(); 164 } 165 } 166 return formats; 167 } 168 169 protected ProgressParser createProgressParser(ProgressListener listener) throws IOException { 170 // TODO In future create the best kind for this OS, unix socket, named pipe, or TCP. 171 try { 172 // Default to TCP because it is supported across all OSes, and is better than UDP because it 173 // provides good properties such as in-order packets, reliability, error checking, etc. 174 return new TcpProgressParser(checkNotNull(listener)); 175 } catch (URISyntaxException e) { 176 throw new IOException(e); 177 } 178 } 179 180 @Override 181 public void run(List<String> args) throws IOException { 182 checkIfFFmpeg(); 183 super.run(args); 184 } 185 186 public void run(FFmpegBuilder builder) throws IOException { 187 run(builder, null); 188 } 189 190 public void run(FFmpegBuilder builder, @Nullable ProgressListener listener) throws IOException { 191 checkNotNull(builder); 192 193 if (listener != null) { 194 try (ProgressParser progressParser = createProgressParser(listener)) { 195 progressParser.start(); 196 builder = builder.addProgress(progressParser.getUri()); 197 198 run(builder.build()); 199 } 200 } else { 201 run(builder.build()); 202 } 203 } 204 205 @CheckReturnValue 206 public FFmpegBuilder builder() { 207 return new FFmpegBuilder(); 208 } 209 210 @Override 211 public String getPath() { 212 return path; 213 } 214}