diff --git a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java index a1eb0547..be4d87e7 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java +++ b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java @@ -66,6 +66,9 @@ public abstract class AbstractFFmpegStreamBuilder inputs = new ArrayList<>(); + final List inputStreams = new ArrayList<>(); final Map inputProbes = new TreeMap<>(); final List extra_args = new ArrayList<>(); @@ -241,6 +242,12 @@ public FFmpegOutputBuilder addOutput(URI uri) { return output; } + public FFmpegInputBuilder addInputStream(String device){ + FFmpegInputBuilder input = new FFmpegInputBuilder(this, device); + inputStreams.add(input); + return input; + } + /** * Adds an existing FFmpegOutputBuilder. This is similar to calling the other addOuput methods but * instead allows an existing FFmpegOutputBuilder to be used, and reused. @@ -275,7 +282,9 @@ public FFmpegOutputBuilder addStdoutOutput() { public List build() { ImmutableList.Builder args = new ImmutableList.Builder(); - Preconditions.checkArgument(!inputs.isEmpty(), "At least one input must be specified"); + Preconditions.checkArgument(!inputs.isEmpty() || !inputStreams.isEmpty(), "At least one input must be specified"); + Preconditions.checkArgument(!(!inputs.isEmpty() && !inputStreams.isEmpty()), + "Cannot specify both an input file/URI and an input device"); Preconditions.checkArgument(!outputs.isEmpty(), "At least one output must be specified"); args.add(override ? "-y" : "-n"); @@ -285,6 +294,12 @@ public List build() { args.add("-user_agent", user_agent); } + if(inputs.isEmpty()){ //build the input streams, using the AbstractFFmpegStreamBuilder + for(FFmpegInputBuilder inputStream: inputStreams){ + args.addAll(inputStream.build(this, pass)); + } + } + if (startOffset != null) { args.add("-ss", FFmpegUtils.toTimecode(startOffset, TimeUnit.MILLISECONDS)); } @@ -301,7 +316,7 @@ public List build() { args.add("-progress", progress.toString()); } - args.addAll(extra_args); + if(!inputs.isEmpty()) args.addAll(extra_args); for (String input : inputs) { args.add("-i", input); @@ -333,4 +348,5 @@ public List build() { return args.build(); } + } diff --git a/src/main/java/net/bramp/ffmpeg/builder/FFmpegInputBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/FFmpegInputBuilder.java new file mode 100644 index 00000000..9b7a87c5 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/FFmpegInputBuilder.java @@ -0,0 +1,100 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import net.bramp.ffmpeg.options.EncodingOptions; +import org.apache.commons.lang3.SystemUtils; + +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; + +public class FFmpegInputBuilder extends AbstractFFmpegStreamBuilder { + + protected int thread_queue_size; + protected int device_number; //device number for multiple devices with the same name + protected static final String DEVNULL = SystemUtils.IS_OS_WINDOWS ? "NUL" : "/dev/null"; + + public FFmpegInputBuilder(FFmpegBuilder parent, String source) { + super(parent); + device_number = 0; + this.input = source; + } + + public FFmpegInputBuilder(FFmpegBuilder parent, String source, int device_number) { + this(parent, source); + this.device_number = device_number; + } + + @Override + protected FFmpegInputBuilder getThis() { + return this; + } + + //TO-DO: something here??? + @Override + public EncodingOptions buildOptions() { + return null; + } + + public FFmpegInputBuilder setThreadQueueSize(int thread_queue_size){ + this.thread_queue_size = thread_queue_size; + this.addExtraArgs("-thread_queue_size", String.valueOf(thread_queue_size)); + return this; + } + + @Override + protected List build(FFmpegBuilder parent, int pass) { + checkNotNull(parent); + + if (pass > 0) { + // TODO Write a test for this: + checkArgument(format != null, "Format must be specified when using two-pass"); + } + + ImmutableList.Builder args = new ImmutableList.Builder<>(); + + addGlobalFlags(parent, args); + + if (video_enabled) { + addVideoFlags(parent, args); + } else { + args.add("-vn"); + } + + if (audio_enabled && pass != 1) { + addAudioFlags(args); + } else { + args.add("-an"); + } + + if (subtitle_enabled) { + if (!Strings.isNullOrEmpty(subtitle_preset)) { + args.add("-spre", subtitle_preset); + } + } else { + args.add("-sn"); + } + + args.addAll(extra_args); + + if (filename != null && uri != null) { + throw new IllegalStateException("Only one of filename and uri can be set"); + } + + // Input + if (pass == 1) { + args.add(DEVNULL); + } else if (input != null) { + args.add("-video_device_number", "" + device_number); + args.add("-i", input); + } else { + assert (false); + } + + return args.build(); + } +} diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java index 51b115db..74cd237c 100644 --- a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.net.URI; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; @@ -432,4 +433,40 @@ public void testPresets() { "-y", "-v", "error", "-i", "input", "-preset", "a", "-fpre", "b", "-vpre", "c", "-apre", "d", "-spre", "e", "output")); } + + @Test + public void testFFmpegInputBuilder(){ + String cmd = "-y -v info -f x11grab -s 1280x720 -r 30/1 -draw_mouse 0 -thread_queue_size 4096 -video_device_number 0 " + + "-i :0.0+0,0 -f alsa -thread_queue_size 4096 -video_device_number 0 -i hw:0,1,0 -f flv -acodec aac rtmp://a.rtmp.youtube.com/live2/XXX" ; + List args = + new FFmpegBuilder() + .overrideOutputFiles(true) + .setVerbosity(FFmpegBuilder.Verbosity.INFO) + .addInputStream(":0.0+0,0") + .setFormat("x11grab") + .addExtraArgs("-draw_mouse", "0") + .setVideoFrameRate(30) + .setVideoResolution("1280x720") + .setThreadQueueSize(4096) + .done() + .addInputStream("hw:0,1,0") + .setFormat("alsa") + .setThreadQueueSize(4096) + .done() + .addOutput("rtmp://a.rtmp.youtube.com/live2/XXX") + .setAudioCodec("aac") + .setFormat("flv") + .done() + .build(); + + assertEquals( + ImmutableList.copyOf(parseCmd(cmd)), + args + ); + } + + //convenience method to parse commands into the immutable list assert form + private List parseCmd(String cmd){ + return Arrays.asList(cmd.split(" ")); + } }