Skip to content

Commit

Permalink
Issue/138 build ffprobe command (#268)
Browse files Browse the repository at this point in the history
* Allow probing with custom arguments

* Parse ffprobe response for -show_frames and -show_packets

* Use FFprobe.path instead of providing executable manually

* Add FFprobeBuilder

* Use FFprobeBuilder when testing -show_packets and -show_frames

* chore: Remove stream api usage

* Test probe parameter options
  • Loading branch information
Euklios authored Mar 13, 2024
1 parent f315cfc commit 1374982
Show file tree
Hide file tree
Showing 14 changed files with 27,695 additions and 41 deletions.
3 changes: 3 additions & 0 deletions src/main/java/net/bramp/ffmpeg/FFmpegUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.bramp.commons.lang3.math.gson.FractionAdapter;
import net.bramp.ffmpeg.adapter.FFmpegPacketsAndFramesAdapter;
import net.bramp.ffmpeg.gson.LowercaseEnumTypeAdapterFactory;
import net.bramp.ffmpeg.gson.NamedBitsetAdapter;
import net.bramp.ffmpeg.probe.FFmpegDisposition;
import net.bramp.ffmpeg.probe.FFmpegFrameOrPacket;
import org.apache.commons.lang3.math.Fraction;

/** Helper class with commonly used methods */
Expand Down Expand Up @@ -129,6 +131,7 @@ private static Gson setupGson() {

builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
builder.registerTypeAdapter(Fraction.class, new FractionAdapter());
builder.registerTypeAdapter(FFmpegFrameOrPacket.class, new FFmpegPacketsAndFramesAdapter());
builder.registerTypeAdapter(
FFmpegDisposition.class, new NamedBitsetAdapter<>(FFmpegDisposition.class));

Expand Down
59 changes: 29 additions & 30 deletions src/main/java/net/bramp/ffmpeg/FFprobe.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package net.bramp.ffmpeg;

import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import net.bramp.ffmpeg.builder.FFprobeBuilder;
import net.bramp.ffmpeg.io.LoggingFilterReader;
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Reader;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;

/**
* Wrapper around FFprobe
*
Expand Down Expand Up @@ -77,34 +81,24 @@ public void run(List<String> args) throws IOException {
super.run(args);
}

// TODO Add Probe Inputstream
public FFmpegProbeResult probe(String mediaPath, @Nullable String userAgent, @Nullable String... extraArgs) throws IOException {
checkIfFFprobe();

ImmutableList.Builder<String> args = new ImmutableList.Builder<String>();

// TODO Add:
// .add("--show_packets")
// .add("--show_frames")
public FFmpegProbeResult probe(String mediaPath, @Nullable String userAgent) throws IOException {
return probe(this.builder().setInput(mediaPath).setUserAgent(userAgent));
}

args.add(path).add("-v", "quiet");
public FFmpegProbeResult probe(FFprobeBuilder builder) throws IOException {
checkNotNull(builder);
return probe(builder.build());
}

if (userAgent != null) {
args.add("-user_agent", userAgent);
}

if (extraArgs != null) {
args.add(extraArgs);
}
public FFmpegProbeResult probe(String mediaPath, @Nullable String userAgent, @Nullable String... extraArgs) throws IOException {
return probe(this.builder().setInput(mediaPath).setUserAgent(userAgent).addExtraArgs(extraArgs).build());
}

args.add("-print_format", "json")
.add("-show_error")
.add("-show_format")
.add("-show_streams")
.add("-show_chapters")
.add(mediaPath);
// TODO Add Probe Inputstream
public FFmpegProbeResult probe(List<String> args) throws IOException {
checkIfFFprobe();

Process p = runFunc.run(args.build());
Process p = runFunc.run(path(args));
try {
Reader reader = wrapInReader(p);
if (LOG.isDebugEnabled()) {
Expand All @@ -125,4 +119,9 @@ public FFmpegProbeResult probe(String mediaPath, @Nullable String userAgent, @Nu
p.destroy();
}
}

@CheckReturnValue
public FFprobeBuilder builder() {
return new FFprobeBuilder();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package net.bramp.ffmpeg.adapter;

import com.google.gson.*;
import net.bramp.ffmpeg.probe.FFmpegFrame;
import net.bramp.ffmpeg.probe.FFmpegFrameOrPacket;
import net.bramp.ffmpeg.probe.FFmpegPacket;

import java.lang.reflect.Type;

public class FFmpegPacketsAndFramesAdapter implements JsonDeserializer<FFmpegFrameOrPacket> {
@Override
public FFmpegFrameOrPacket deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
if (jsonElement instanceof JsonObject) {
final String objectType = ((JsonObject) jsonElement).get("type").getAsString();

if (objectType.equals("packet")) {
return jsonDeserializationContext.deserialize(jsonElement, FFmpegPacket.class);
} else {
return jsonDeserializationContext.deserialize(jsonElement, FFmpegFrame.class);
}
}

return null;
}
}
102 changes: 102 additions & 0 deletions src/main/java/net/bramp/ffmpeg/builder/FFprobeBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package net.bramp.ffmpeg.builder;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

import javax.annotation.CheckReturnValue;
import java.util.ArrayList;
import java.util.List;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static net.bramp.ffmpeg.Preconditions.checkNotEmpty;

/**
* Builds a ffprobe command line
*/
public class FFprobeBuilder {
private boolean showFormat = true;
private boolean showStreams = true;
private boolean showChapters = true;
private boolean showFrames = false;
private boolean showPackets = false;
private String userAgent;
private String input;

private final List<String> extraArgs = new ArrayList<>();

public FFprobeBuilder setShowFormat(boolean showFormat) {
this.showFormat = showFormat;
return this;
}

public FFprobeBuilder setShowStreams(boolean showStreams) {
this.showStreams = showStreams;
return this;
}

public FFprobeBuilder setShowChapters(boolean showChapters) {
this.showChapters = showChapters;
return this;
}

public FFprobeBuilder setShowFrames(boolean showFrames) {
this.showFrames = showFrames;
return this;
}

public FFprobeBuilder setShowPackets(boolean showPackets) {
this.showPackets = showPackets;
return this;
}

public FFprobeBuilder setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}

public FFprobeBuilder setInput(String filename) {
checkNotNull(filename);
this.input = filename;
return this;
}

public FFprobeBuilder addExtraArgs(String... values) {
checkArgument(values != null, "extraArgs can not be null");
checkArgument(values.length > 0, "one or more values must be supplied");
checkNotEmpty(values[0], "first extra arg may not be empty");

for (String value : values) {
extraArgs.add(checkNotNull(value));
}
return this;
}

@CheckReturnValue
public List<String> build() {
ImmutableList.Builder<String> args = new ImmutableList.Builder<>();

Preconditions.checkNotNull(input, "Input must be specified");

args
.add("-v", "quiet")
.add("-print_format", "json")
.add("-show_error");

if (userAgent != null) {
args.add("-user_agent", userAgent);
}

args.addAll(extraArgs);

if (showFormat) args.add("-show_format");
if (showStreams) args.add("-show_streams");
if (showChapters) args.add("-show_chapters");
if (showPackets) args.add("-show_packets");
if (showFrames) args.add("-show_frames");

args.add(input);

return args.build();
}
}
27 changes: 27 additions & 0 deletions src/main/java/net/bramp/ffmpeg/probe/FFmpegFrame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package net.bramp.ffmpeg.probe;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bramp.ffmpeg.shared.CodecType;

@SuppressFBWarnings(
value = {"UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD"},
justification = "POJO objects where the fields are populated by gson")
public class FFmpegFrame implements FFmpegFrameOrPacket {
public CodecType media_type;
public int stream_index;
public int key_frame;
public long pkt_pts;
public double pkt_pts_time;
public long pkt_dts;
public double pkt_dts_time;
public long best_effort_timestamp;
public float best_effort_timestamp_time;
public long pkt_duration;
public float pkt_duration_time;
public long pkt_pos;
public long pkt_size;
public String sample_fmt;
public int nb_samples;
public int channels;
public String channel_layout;
}
4 changes: 4 additions & 0 deletions src/main/java/net/bramp/ffmpeg/probe/FFmpegFrameOrPacket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.bramp.ffmpeg.probe;

public interface FFmpegFrameOrPacket {
}
21 changes: 21 additions & 0 deletions src/main/java/net/bramp/ffmpeg/probe/FFmpegPacket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package net.bramp.ffmpeg.probe;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bramp.ffmpeg.shared.CodecType;

@SuppressFBWarnings(
value = {"UUF_UNUSED_PUBLIC_OR_PROTECTED_FIELD"},
justification = "POJO objects where the fields are populated by gson")
public class FFmpegPacket implements FFmpegFrameOrPacket {
public CodecType codec_type;
public int stream_index;
public long pts;
public double pts_time;
public long dts;
public double dts_time;
public long duration;
public float duration_time;
public String size;
public String pos;
public String flags;
}
40 changes: 40 additions & 0 deletions src/main/java/net/bramp/ffmpeg/probe/FFmpegProbeResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.common.collect.ImmutableList;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

Expand All @@ -14,6 +16,9 @@ public class FFmpegProbeResult {
public FFmpegFormat format;
public List<FFmpegStream> streams;
public List<FFmpegChapter> chapters;
public List<FFmpegPacket> packets;
public List<FFmpegFrame> frames;
public List<FFmpegFrameOrPacket> packets_and_frames;

public FFmpegError getError() {
return error;
Expand All @@ -36,4 +41,39 @@ public List<FFmpegChapter> getChapters() {
if (chapters == null) return Collections.emptyList();
return ImmutableList.copyOf(chapters);
}

public List<FFmpegPacket> getPackets() {
if (packets == null) {
if (packets_and_frames != null) {
List<FFmpegPacket> tmp = new ArrayList<>();
for (FFmpegFrameOrPacket packetsAndFrame : packets_and_frames) {
if (packetsAndFrame instanceof FFmpegPacket) {
tmp.add((FFmpegPacket) packetsAndFrame);
}
}
packets = tmp;
} else {
return Collections.emptyList();
}
}

return ImmutableList.copyOf(packets);
}

public List<FFmpegFrame> getFrames() {
if (frames == null) {
if (packets_and_frames != null) {
List<FFmpegFrame> tmp = new ArrayList<>();
for (FFmpegFrameOrPacket packetsAndFrame : packets_and_frames) {
if (packetsAndFrame instanceof FFmpegFrame) {
tmp.add((FFmpegFrame) packetsAndFrame);
}
}
frames = tmp;
} else {
return Collections.emptyList();
}
}
return ImmutableList.copyOf(frames);
}
}
Loading

0 comments on commit 1374982

Please sign in to comment.