001package net.bramp.ffmpeg;
002
003import com.google.common.base.MoreObjects;
004import com.google.common.collect.ImmutableList;
005import com.google.gson.Gson;
006import java.io.IOException;
007import java.io.Reader;
008import java.util.List;
009import javax.annotation.Nonnull;
010import javax.annotation.Nullable;
011import net.bramp.ffmpeg.io.LoggingFilterReader;
012import net.bramp.ffmpeg.probe.FFmpegProbeResult;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016/**
017 * Wrapper around FFprobe
018 *
019 * @author bramp
020 */
021public class FFprobe extends FFcommon {
022
023  static final Logger LOG = LoggerFactory.getLogger(FFprobe.class);
024
025  static final String FFPROBE = "ffprobe";
026  static final String DEFAULT_PATH = MoreObjects.firstNonNull(System.getenv("FFPROBE"), FFPROBE);
027
028  static final Gson gson = FFmpegUtils.getGson();
029
030  public FFprobe() throws IOException {
031    this(DEFAULT_PATH, new RunProcessFunction());
032  }
033
034  public FFprobe(@Nonnull ProcessFunction runFunction) throws IOException {
035    this(DEFAULT_PATH, runFunction);
036  }
037
038  public FFprobe(@Nonnull String path) throws IOException {
039    this(path, new RunProcessFunction());
040  }
041
042  public FFprobe(@Nonnull String path, @Nonnull ProcessFunction runFunction) {
043    super(path, runFunction);
044  }
045
046  public FFmpegProbeResult probe(String mediaPath) throws IOException {
047    return probe(mediaPath, null);
048  }
049
050  /**
051   * Returns true if the binary we are using is the true ffprobe. This is to avoid conflict with
052   * avprobe (from the libav project), that some symlink to ffprobe.
053   *
054   * @return true iff this is the official ffprobe binary.
055   * @throws IOException If a I/O error occurs while executing ffprobe.
056   */
057  public boolean isFFprobe() throws IOException {
058    return version().startsWith("ffprobe");
059  }
060
061  /**
062   * Throws an exception if this is an unsupported version of ffprobe.
063   *
064   * @throws IllegalArgumentException if this is not the official ffprobe binary.
065   * @throws IOException If a I/O error occurs while executing ffprobe.
066   */
067  private void checkIfFFprobe() throws IllegalArgumentException, IOException {
068    if (!isFFprobe()) {
069      throw new IllegalArgumentException(
070          "This binary '" + path + "' is not a supported version of ffprobe");
071    }
072  }
073
074  @Override
075  public void run(List<String> args) throws IOException {
076    checkIfFFprobe();
077    super.run(args);
078  }
079
080  // TODO Add Probe Inputstream
081  public FFmpegProbeResult probe(String mediaPath, @Nullable String userAgent) throws IOException {
082    checkIfFFprobe();
083
084    ImmutableList.Builder<String> args = new ImmutableList.Builder<String>();
085
086    // TODO Add:
087    // .add("--show_packets")
088    // .add("--show_frames")
089
090    args.add(path).add("-v", "quiet");
091
092    if (userAgent != null) {
093      args.add("-user_agent", userAgent);
094    }
095
096    args.add("-print_format", "json")
097        .add("-show_error")
098        .add("-show_format")
099        .add("-show_streams")
100        .add("-show_chapters")
101        .add(mediaPath);
102
103    Process p = runFunc.run(args.build());
104    try {
105      Reader reader = wrapInReader(p);
106      if (LOG.isDebugEnabled()) {
107        reader = new LoggingFilterReader(reader, LOG);
108      }
109
110      FFmpegProbeResult result = gson.fromJson(reader, FFmpegProbeResult.class);
111
112      throwOnError(p);
113
114      if (result == null) {
115        throw new IllegalStateException("Gson returned null, which shouldn't happen :(");
116      }
117
118      return result;
119
120    } finally {
121      p.destroy();
122    }
123  }
124}