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