diff --git a/README.md b/README.md index 08402a2f..52066738 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ FFprobe ffprobe = new FFprobe("/path/to/ffprobe"); FFmpegBuilder builder = new FFmpegBuilder() .setInput("input.mp4") // Filename, or a FFmpegProbeResult + .done() .overrideOutputFiles(true) // Override the output if it exists .addOutput("output.mp4") // Filename for the destination @@ -100,6 +101,7 @@ FFmpegProbeResult in = ffprobe.probe("input.flv"); FFmpegBuilder builder = new FFmpegBuilder() .setInput(in) // Or filename + .done() .addOutput("output.mp4") .done(); diff --git a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegInputBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegInputBuilder.java new file mode 100644 index 00000000..9f5a247e --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegInputBuilder.java @@ -0,0 +1,75 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import net.bramp.ffmpeg.options.EncodingOptions; +import net.bramp.ffmpeg.probe.FFmpegProbeResult; + +import javax.annotation.CheckReturnValue; + +public abstract class AbstractFFmpegInputBuilder> extends AbstractFFmpegStreamBuilder { + private final FFmpegProbeResult probeResult; + + private boolean readAtNativeFrameRate; + /** + * Number of times input stream shall be looped. Loop 0 means no loop, loop -1 means infinite loop. + */ + private int streamLoop; + + protected AbstractFFmpegInputBuilder(FFmpegBuilder parent, String filename) { + this(parent, null, filename); + } + + protected AbstractFFmpegInputBuilder(FFmpegBuilder parent, FFmpegProbeResult probeResult, String filename) { + super(parent, filename); + this.probeResult = probeResult; + } + + public T readAtNativeFrameRate() { + this.readAtNativeFrameRate = true; + return getThis(); + } + + /** + * Sets number of times input stream shall be looped. Loop 0 means no loop, loop -1 means infinite loop. + * @param streamLoop loop counter + * @return this + */ + public T setStreamLoop(int streamLoop) { + this.streamLoop = streamLoop; + + return getThis(); + } + + public FFmpegProbeResult getProbeResult() { + return probeResult; + } + + @Override + @CheckReturnValue + @SuppressWarnings("unchecked") + protected T getThis() { + return (T) this; + } + + @Override + public EncodingOptions buildOptions() { + return null; + } + + @Override + protected void addGlobalFlags(FFmpegBuilder parent, ImmutableList.Builder args) { + if (this.readAtNativeFrameRate) { + args.add("-re"); + } + + if (this.streamLoop != 0) { + args.add("-stream_loop", Integer.toString(this.streamLoop)); + } + + super.addGlobalFlags(parent, args); + } + + public int getStreamLoop() { + return streamLoop; + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilder.java index 19b4be98..f081d181 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilder.java +++ b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilder.java @@ -19,7 +19,7 @@ import net.bramp.ffmpeg.probe.FFmpegProbeResult; /** Builds a representation of a single output/encoding setting */ -@SuppressWarnings({"DeprecatedIsStillUsed", "deprecation","unchecked"}) +@SuppressWarnings({"DeprecatedIsStillUsed", "unchecked"}) public abstract class AbstractFFmpegOutputBuilder> extends AbstractFFmpegStreamBuilder { static final Pattern trailingZero = Pattern.compile("\\.0*$"); @@ -67,6 +67,8 @@ public abstract class AbstractFFmpegOutputBuilder build(int pass) { Preconditions.checkState(parent != null, "Can not build without parent being set"); + return build(parent, pass); } @@ -265,15 +274,18 @@ protected List build(FFmpegBuilder parent, int pass) { checkArgument( targetSize != 0 || video_bit_rate != 0, "Target size, or video bitrate must be specified when using two-pass"); + + checkArgument(format != null, "Format must be specified when using two-pass"); } + if (targetSize > 0) { checkState(parent.inputs.size() == 1, "Target size does not support multiple inputs"); checkArgument( constantRateFactor == null, "Target size can not be used with constantRateFactor"); - String firstInput = parent.inputs.iterator().next(); - FFmpegProbeResult input = parent.inputProbes.get(firstInput); + AbstractFFmpegInputBuilder firstInput = parent.inputs.iterator().next(); + FFmpegProbeResult input = firstInput.getProbeResult(); checkState(input != null, "Target size must be used with setInput(FFmpegProbeResult)"); @@ -316,6 +328,10 @@ protected void addGlobalFlags(FFmpegBuilder parent, ImmutableList.Builder args) { } } + @Override + protected void addSourceTarget(int pass, ImmutableList.Builder args) { + if (filename != null && uri != null) { + throw new IllegalStateException("Only one of filename and uri can be set"); + } + + // Output + if (pass == 1) { + args.add(DEVNULL); + } else if (filename != null) { + args.add(filename); + } else if (uri != null) { + args.add(uri.toString()); + } else { + assert false; + } + } + @CheckReturnValue @Override protected T getThis() { @@ -431,4 +465,8 @@ public String getVideoFilter() { public String getVideoBitStreamFilter() { return video_bit_stream_filter; } + + public String getComplexFilter() { + return complexFilter; + } } diff --git a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java index 5d0613d3..992a3d0a 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java +++ b/src/main/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilder.java @@ -55,7 +55,7 @@ */ public abstract class AbstractFFmpegStreamBuilder> { - private static final String DEVNULL = SystemUtils.IS_OS_WINDOWS ? "NUL" : "/dev/null"; + protected static final String DEVNULL = SystemUtils.IS_OS_WINDOWS ? "NUL" : "/dev/null"; final FFmpegBuilder parent; @@ -551,11 +551,6 @@ protected List build(int pass) { 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); @@ -589,24 +584,13 @@ protected List build(FFmpegBuilder parent, int pass) { args.addAll(extra_args); - if (filename != null && uri != null) { - throw new IllegalStateException("Only one of filename and uri can be set"); - } - - // Output - if (pass == 1) { - args.add(DEVNULL); - } else if (filename != null) { - args.add(filename); - } else if (uri != null) { - args.add(uri.toString()); - } else { - assert false; - } + addSourceTarget(pass, args); return args.build(); } + protected abstract void addSourceTarget(int pass, ImmutableList.Builder args); + protected void addGlobalFlags(FFmpegBuilder parent, ImmutableList.Builder args) { if (strict != FFmpegBuilder.Strict.NORMAL) { args.add("-strict", strict.toString()); diff --git a/src/main/java/net/bramp/ffmpeg/builder/FFmpegBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/FFmpegBuilder.java index fa1f5455..6f8945b0 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/FFmpegBuilder.java +++ b/src/main/java/net/bramp/ffmpeg/builder/FFmpegBuilder.java @@ -17,6 +17,8 @@ import javax.annotation.CheckReturnValue; import net.bramp.ffmpeg.FFmpegUtils; import net.bramp.ffmpeg.probe.FFmpegProbeResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Builds a ffmpeg command line @@ -25,6 +27,8 @@ */ public class FFmpegBuilder { + private static final Logger log = LoggerFactory.getLogger(FFmpegBuilder.class); + public enum Strict { VERY, // strictly conform to an older more strict version of the specifications or reference // software @@ -40,7 +44,7 @@ public String toString() { } } - /** Log level options: https://ffmpeg.org/ffmpeg.html#Generic-options */ + /** Log level options: ffmpeg documentation */ public enum Verbosity { QUIET, PANIC, @@ -73,7 +77,7 @@ public String toString() { String format; Long startOffset; // in millis boolean read_at_native_frame_rate = false; - final List inputs = new ArrayList<>(); + final List> inputs = new ArrayList<>(); final Map inputProbes = new TreeMap<>(); final List extra_args = new ArrayList<>(); @@ -121,22 +125,39 @@ public FFmpegBuilder setUserAgent(String userAgent) { return this; } + /** + * Makes ffmpeg read the first input at the native frame read + * @return this + * @deprecated Use {@link AbstractFFmpegInputBuilder#readAtNativeFrameRate()} instead + */ + @Deprecated public FFmpegBuilder readAtNativeFrameRate() { this.read_at_native_frame_rate = true; return this; } - public FFmpegBuilder addInput(FFmpegProbeResult result) { + public FFmpegFileInputBuilder addInput(FFmpegProbeResult result) { checkNotNull(result); String filename = checkNotNull(result.getFormat()).getFilename(); - inputProbes.put(filename, result); - return addInput(filename); + + return this.doAddInput(new FFmpegFileInputBuilder(this, filename, result)); } - public FFmpegBuilder addInput(String filename) { + public FFmpegFileInputBuilder addInput(String filename) { checkNotNull(filename); - inputs.add(filename); - return this; + + return this.doAddInput(new FFmpegFileInputBuilder(this, filename)); + } + + public > FFmpegBuilder addInput(T input) { + return this.doAddInput(input).done(); + } + + protected > T doAddInput(T input) { + checkNotNull(input); + + inputs.add(input); + return input; } protected void clearInputs() { @@ -144,27 +165,51 @@ protected void clearInputs() { inputProbes.clear(); } - public FFmpegBuilder setInput(FFmpegProbeResult result) { + public FFmpegFileInputBuilder setInput(FFmpegProbeResult result) { clearInputs(); return addInput(result); } - public FFmpegBuilder setInput(String filename) { + public FFmpegFileInputBuilder setInput(String filename) { clearInputs(); return addInput(filename); } + public > FFmpegBuilder setInput(T input) { + checkNotNull(input); + + clearInputs(); + inputs.add(input); + + return this; + } + public FFmpegBuilder setThreads(int threads) { checkArgument(threads > 0, "threads must be greater than zero"); this.threads = threads; return this; } + /** + * Sets the format for the first input stream + * @param format, the format of this input stream, not null + * @return this + * @deprecated Specify this option on an input stream using {@link AbstractFFmpegStreamBuilder#setFormat(String)} + */ + @Deprecated public FFmpegBuilder setFormat(String format) { this.format = checkNotNull(format); return this; } + /** + * Sets the start offset for the first input stream + * @param duration the amount of the offset, measured in terms of the unit + * @param units the unit that the duration is measured in, not null + * @return this + * @deprecated Specify this option on an input or output stream using {@link AbstractFFmpegStreamBuilder#setStartOffset(long, TimeUnit)} + */ + @Deprecated public FFmpegBuilder setStartOffset(long duration, TimeUnit units) { checkNotNull(units); @@ -183,7 +228,9 @@ public FFmpegBuilder addProgress(URI uri) { * * @param filter the complex filter string * @return this + * @deprecated Use {@link AbstractFFmpegOutputBuilder#setComplexFilter(String)} instead */ + @Deprecated public FFmpegBuilder setComplexFilter(String filter) { this.complexFilter = checkNotEmpty(filter, "filter must not be empty"); return this; @@ -327,6 +374,7 @@ public List build() { } if (startOffset != null) { + log.warn("Using FFmpegBuilder#setStartOffset is deprecated. Specify it on the inputStream or outputStream instead"); args.add("-ss", FFmpegUtils.toTimecode(startOffset, TimeUnit.MILLISECONDS)); } @@ -335,10 +383,12 @@ public List build() { } if (format != null) { + log.warn("Using FFmpegBuilder#setFormat is deprecated. Specify it on the inputStream or outputStream instead"); args.add("-f", format); } if (read_at_native_frame_rate) { + log.warn("Using FFmpegBuilder#readAtNativeFrameRate is deprecated. Specify it on the inputStream instead"); args.add("-re"); } @@ -348,8 +398,8 @@ public List build() { args.addAll(extra_args); - for (String input : inputs) { - args.add("-i", input); + for (AbstractFFmpegInputBuilder input : this.inputs) { + args.addAll(input.build(this, pass)); } if (pass > 0) { @@ -369,6 +419,7 @@ public List build() { } if (!Strings.isNullOrEmpty(complexFilter)) { + log.warn("Using FFmpegBuilder#setComplexFilter is deprecated. Specify it on the outputStream instead"); args.add("-filter_complex", complexFilter); } diff --git a/src/main/java/net/bramp/ffmpeg/builder/FFmpegFileInputBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/FFmpegFileInputBuilder.java new file mode 100644 index 00000000..330283ac --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/FFmpegFileInputBuilder.java @@ -0,0 +1,31 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import net.bramp.ffmpeg.probe.FFmpegProbeResult; + +public class FFmpegFileInputBuilder extends AbstractFFmpegInputBuilder { + public FFmpegFileInputBuilder(FFmpegBuilder parent, String filename) { + super(parent, filename); + } + + public FFmpegFileInputBuilder(FFmpegBuilder parent, String filename, FFmpegProbeResult result) { + super(parent, result, filename); + + } + + @Override + protected void addSourceTarget(int pass, ImmutableList.Builder args) { + if (filename != null && uri != null) { + throw new IllegalStateException("Only one of filename and uri can be set"); + } + + // Input + if (filename != null) { + args.add("-i", filename); + } else if (uri != null) { + args.add("-i", uri.toString()); + } else { + assert false; + } + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java index 58708b42..4fe23cd5 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java +++ b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java @@ -14,7 +14,7 @@ /** * Metadata spec, as described in the "map_metadata" section of - * https://www.ffmpeg.org/ffmpeg-all.html#Main-options + * Main options */ @Immutable public class MetadataSpecifier { diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java b/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java index 40482d86..ab2b2242 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java +++ b/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java @@ -3,7 +3,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static net.bramp.ffmpeg.builder.MetadataSpecifier.checkValidKey; -/** https://ffmpeg.org/ffmpeg.html#Stream-specifiers */ +/** Stream specifier */ public class StreamSpecifier { private final String spec; diff --git a/src/test/java/net/bramp/ffmpeg/ExamplesTest.java b/src/test/java/net/bramp/ffmpeg/ExamplesTest.java index c6fe44e1..8b77b27f 100644 --- a/src/test/java/net/bramp/ffmpeg/ExamplesTest.java +++ b/src/test/java/net/bramp/ffmpeg/ExamplesTest.java @@ -45,9 +45,10 @@ public void testExample1() throws IOException { new FFmpegBuilder() .addExtraArgs("-rtbufsize", "1500M") .addExtraArgs("-re") - .setFormat("dshow") .setInput( "video=\"Microsoft Camera Rear\":audio=\"Microphone Array (Realtek High Definition Audio(SST))\"") + .setFormat("dshow") + .done() .addOutput("rtmp://a.rtmp.youtube.com/live2/1234-5678") .setFormat("flv") .addExtraArgs("-bufsize", "4000k") @@ -68,7 +69,7 @@ public void testExample1() throws IOException { String expected = "ffmpeg\\win64\\bin\\ffmpeg.exe -y -v error" - + " -f dshow -rtbufsize 1500M -re" + + " -rtbufsize 1500M -re -f dshow" + " -i video=\"Microsoft Camera Rear\":audio=\"Microphone Array (Realtek High Definition Audio(SST))\"" + " -f flv" + " -vcodec libx264 -pix_fmt yuv420p -s 426x240 -r 30/1 -b:v 2000000" @@ -86,6 +87,7 @@ public void testExample2() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .setInput("input.mkv") + .done() .addOutput("output.ogv") .setVideoCodec("libtheora") .addExtraArgs("-qscale:v", "7") @@ -111,6 +113,7 @@ public void testExample3() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .setInput("sample.avi") + .done() .addOutput("thumbnail.png") .setFrames(1) .setVideoFilter("select='gte(n\\,10)',scale=200:-1") @@ -132,6 +135,7 @@ public void testExample4() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .setInput("rtsp://192.168.1.1:1234/") + .done() .addOutput("img%03d.jpg") .setFormat("image2") .done(); @@ -152,7 +156,7 @@ public void testExample5() throws IOException { FFmpeg ffmpeg = new FFmpeg("/path/to/ffmpeg", func); FFprobe ffprobe = new FFprobe("/path/to/ffprobe", func); - FFmpegBuilder builder = new FFmpegBuilder().setInput("input").addOutput("output.mp4").done(); + FFmpegBuilder builder = new FFmpegBuilder().setInput("input").done().addOutput("output.mp4").done(); FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe); @@ -166,6 +170,7 @@ public void testExample6() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .addInput("image%03d.png") + .done() .addOutput("output.mp4") .setVideoFrameRate(FFmpeg.FPS_24) .done(); @@ -181,11 +186,13 @@ public void testExample7() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .addInput("original.mp4") + .done() .addInput("spot.mp4") - .setComplexFilter( - "[1:v]scale=368:207,setpts=PTS-STARTPTS+5/TB [ov]; " - + "[0:v][ov] overlay=x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2:enable='between(t,5,15)' [v]") + .done() .addOutput("with-video.mp4") + .setComplexFilter( + "[1:v]scale=368:207,setpts=PTS-STARTPTS+5/TB [ov]; " + + "[0:v][ov] overlay=x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2:enable='between(t,5,15)' [v]") .addExtraArgs("-map", "[v]") .addExtraArgs("-map", "0:a") .setVideoCodec("libx264") @@ -199,9 +206,9 @@ public void testExample7() throws IOException { "ffmpeg -y -v error" + " -i original.mp4" + " -i spot.mp4" - + " -filter_complex [1:v]scale=368:207,setpts=PTS-STARTPTS+5/TB [ov]; [0:v][ov] overlay=x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2:enable='between(t,5,15)' [v]" + " -preset ultrafast" + " -crf 20" + + " -filter_complex [1:v]scale=368:207,setpts=PTS-STARTPTS+5/TB [ov]; [0:v][ov] overlay=x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2:enable='between(t,5,15)' [v]" + " -vcodec libx264" + " -acodec copy" + " -map [v]" @@ -219,6 +226,7 @@ public void testExample8() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .addInput("original.mp4") + .done() .setVideoFilter("select='gte(n\\,10)',scale=200:-1") .addOutput("hevc-video.mp4") .addExtraArgs("-tag:v", "hvc1") @@ -244,6 +252,7 @@ public void testExample9() throws IOException { new FFmpegBuilder() .setVerbosity(FFmpegBuilder.Verbosity.DEBUG) .setInput("input.mp3") + .done() .overrideOutputFiles(true) // Override the output if it exists .addOutput("left.mp3") .addExtraArgs("-map_channel", "0.0.0") @@ -269,8 +278,11 @@ public void testExample10() throws IOException { "ffmpeg -y -v error" + " -f webm_dash_manifest" + " -i audio.webm" + + " -f webm_dash_manifest" + " -i video_1.webm" + + " -f webm_dash_manifest" + " -i video_2.webm" + + " -f webm_dash_manifest" + " -i video_3.webm" + " -vcodec copy -acodec copy" + " -map 0 -map 1 -map 2 -map 3" @@ -311,7 +323,7 @@ public void testExample10() throws IOException { @Test @Ignore("because this test will invoke /path/to/ffmpeg.") public void testExample11() throws IOException, InterruptedException { - FFmpegBuilder builder = new FFmpegBuilder().setInput("input").addOutput("output.mp4").done(); + FFmpegBuilder builder = new FFmpegBuilder().setInput("input").done().addOutput("output.mp4").done(); List args = new ArrayList<>(); args.add("/path/to/ffmpeg"); @@ -333,6 +345,7 @@ public void testExampleExample() throws IOException { new FFmpegBuilder() .setInput("input.mp4") .setStartOffset(1, TimeUnit.MINUTES) + .done() .addOutput("output.mp4") .setDuration(1, TimeUnit.MINUTES) .setVideoCodec("copy") diff --git a/src/test/java/net/bramp/ffmpeg/FFmpegExecutorTest.java b/src/test/java/net/bramp/ffmpeg/FFmpegExecutorTest.java index 8b996283..d3ceaa79 100644 --- a/src/test/java/net/bramp/ffmpeg/FFmpegExecutorTest.java +++ b/src/test/java/net/bramp/ffmpeg/FFmpegExecutorTest.java @@ -86,6 +86,7 @@ public void testNormal() throws InterruptedException, ExecutionException, IOExce .setInput(getWebserverRoot() + Samples.base_big_buck_bunny_720p_1mb) .addExtraArgs("-probesize", "1000000") // .setStartOffset(1500, TimeUnit.MILLISECONDS) + .done() .overrideOutputFiles(true) .addOutput(Samples.output_mp4) .setFrames(100) @@ -121,6 +122,7 @@ public void testTwoPass() throws InterruptedException, ExecutionException, IOExc FFmpegBuilder builder = new FFmpegBuilder() .setInput(in) + .done() .overrideOutputFiles(true) .addOutput(Samples.output_mp4) .setFormat("mp4") @@ -143,6 +145,7 @@ public void testFilter() throws InterruptedException, ExecutionException, IOExce FFmpegBuilder builder = new FFmpegBuilder() .setInput(Samples.big_buck_bunny_720p_1mb) + .done() .overrideOutputFiles(true) .addOutput(Samples.output_mp4) .setFormat("mp4") @@ -163,6 +166,7 @@ public void testMetaTags() throws InterruptedException, ExecutionException, IOEx FFmpegBuilder builder = new FFmpegBuilder() .setInput(Samples.big_buck_bunny_720p_1mb) + .done() .overrideOutputFiles(true) .addOutput(Samples.output_mp4) .setFormat("mp4") @@ -187,6 +191,7 @@ public void testStdout() throws InterruptedException, ExecutionException, IOExce FFmpegBuilder builder = new FFmpegBuilder() .setInput(Samples.big_buck_bunny_720p_1mb) + .done() .addStdoutOutput() .setFormat("s8") .setAudioChannels(1) @@ -215,8 +220,9 @@ public void testProgress() throws InterruptedException, ExecutionException, IOEx FFmpegBuilder builder = new FFmpegBuilder() - .readAtNativeFrameRate() // Slows the test down .setInput(in) + .readAtNativeFrameRate() // Slows the test down + .done() .overrideOutputFiles(true) .addOutput(Samples.output_mp4) .done(); @@ -240,9 +246,11 @@ public void testProgress() throws InterruptedException, ExecutionException, IOEx public void testIssue112() { FFmpegBuilder builder = new FFmpegBuilder() - .setInput(Samples.testscreen_jpg) - .addInput(Samples.test_mp3) + .addInput(Samples.testscreen_jpg) .addExtraArgs("-loop", "1") + .done() + .addInput(Samples.test_mp3) + .done() .overrideOutputFiles(true) .addOutput(Samples.output_mp4) .setFormat("mp4") diff --git a/src/test/java/net/bramp/ffmpeg/FFmpegTest.java b/src/test/java/net/bramp/ffmpeg/FFmpegTest.java index bbc0ec35..10f3dbaf 100644 --- a/src/test/java/net/bramp/ffmpeg/FFmpegTest.java +++ b/src/test/java/net/bramp/ffmpeg/FFmpegTest.java @@ -2,18 +2,22 @@ import static org.hamcrest.Matchers.hasItem; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; import static org.mockito.Mockito.*; import static org.mockito.hamcrest.MockitoHamcrest.argThat; import java.io.IOException; import java.util.List; +import java.util.concurrent.TimeUnit; import com.google.common.collect.Lists; +import net.bramp.ffmpeg.builder.FFmpegBuilder; import net.bramp.ffmpeg.fixtures.Codecs; import net.bramp.ffmpeg.fixtures.Filters; import net.bramp.ffmpeg.fixtures.Formats; import net.bramp.ffmpeg.fixtures.ChannelLayouts; import net.bramp.ffmpeg.fixtures.PixelFormats; +import net.bramp.ffmpeg.fixtures.Samples; import net.bramp.ffmpeg.info.Filter; import net.bramp.ffmpeg.lang.NewProcessAnswer; import org.junit.Before; @@ -61,6 +65,32 @@ public void testVersion() throws Exception { verify(runFunc, times(1)).run(argThatHasItem("-version")); } + @Test + public void testStartOffsetOption() throws Exception { + FFmpeg ffmpeg = new FFmpeg(); + + FFmpegBuilder builder = ffmpeg.builder().addInput(Samples.big_buck_bunny_720p_1mb).setStartOffset(1, TimeUnit.SECONDS).done().addOutput(Samples.output_mp4).done(); + + try { + ffmpeg.run(builder); + } catch (Throwable t) { + fail(t.getClass().getSimpleName() + " was thrown"); + } + } + + @Test + public void testDurationOption() throws Exception { + FFmpeg ffmpeg = new FFmpeg(); + + FFmpegBuilder builder = ffmpeg.builder().addInput(Samples.big_buck_bunny_720p_1mb).setDuration(1, TimeUnit.SECONDS).done().addOutput(Samples.output_mp4).done(); + + try { + ffmpeg.run(builder); + } catch (Throwable t) { + fail(t.getClass().getSimpleName() + " was thrown"); + } + } + @Test public void testCodecs() throws IOException { // Run twice, the second should be cached diff --git a/src/test/java/net/bramp/ffmpeg/InputOutputTest.java b/src/test/java/net/bramp/ffmpeg/InputOutputTest.java new file mode 100644 index 00000000..bd3e579c --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/InputOutputTest.java @@ -0,0 +1,287 @@ +package net.bramp.ffmpeg; + +import com.google.common.collect.ImmutableList; +import net.bramp.ffmpeg.builder.FFmpegBuilder; +import net.bramp.ffmpeg.lang.NewProcessAnswer; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static net.bramp.ffmpeg.FFmpegTest.argThatHasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class InputOutputTest { + @Mock + ProcessFunction runFunc; + + FFmpeg ffmpeg; + + @Before + public void before() throws IOException { + when(runFunc.run(argThatHasItem("-version"))) + .thenAnswer(new NewProcessAnswer("avconv-version")); + + ffmpeg = new FFmpeg(runFunc); + } + + @Test + public void setInputFormat() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .setFormat("mp4") + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-f", "mp4", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void setInputFormatMultiple() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .setFormat("mp4") + .done() + .addInput("input.mkv") + .setFormat("matroschka") + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-f", "mp4", "-i", "input.mp4", "-f", "matroschka", "-i", "input.mkv", "output.mp4"))); + } + + @Test + public void setStartOffsetOnInput() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .setStartOffset(10, TimeUnit.SECONDS) + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-ss", "00:00:10", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void setStartOffsetOnOutput() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setStartOffset(10, TimeUnit.SECONDS) + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-i", "input.mp4", "-ss", "00:00:10", "output.mp4"))); + } + + @Test + public void setStartOffsetOnInputAndOutput() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .setStartOffset(1, TimeUnit.SECONDS) + .done() + .addOutput("output.mp4") + .setStartOffset(10, TimeUnit.SECONDS) + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-ss", "00:00:01", "-i", "input.mp4", "-ss", "00:00:10", "output.mp4"))); + } + + @Test + public void readAtNativeFrameRate() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .readAtNativeFrameRate() + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-re", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void readAtNativeFrameRateMultiple() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .readAtNativeFrameRate() + .done() + .addInput("input.mkv") + .readAtNativeFrameRate() + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-re", "-i", "input.mp4", "-re", "-i", "input.mkv", "output.mp4"))); + } + + @Test + public void outputCodec() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setVideoCodec("libx264") + .setAudioCodec("aac") + .setSubtitleCodec("vtt") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-i", "input.mp4", "-vcodec", "libx264", "-acodec", "aac", "-scodec", "vtt", "output.mp4"))); + } + + @Test + public void inputCodec() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .setVideoCodec("libx264") + .setAudioCodec("aac") + .setSubtitleCodec("vtt") + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-vcodec", "libx264", "-acodec", "aac", "-scodec", "vtt", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void inputVideoDisabled() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .disableVideo() + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-vn", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void outputVideoDisabled() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .disableVideo() + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-i", "input.mp4", "-vn", "output.mp4"))); + } + + @Test + public void inputAudioDisabled() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .disableAudio() + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-an", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void outputAudioDisabled() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .disableAudio() + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-i", "input.mp4", "-an", "output.mp4"))); + } + + @Test + public void inputSubtitleDisabled() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .disableSubtitle() + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-sn", "-i", "input.mp4", "output.mp4"))); + } + + @Test + public void outputSubtitleDisabled() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .disableSubtitle() + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-i", "input.mp4", "-sn", "output.mp4"))); + } + + @Test + public void setExtraArgsToOnMultipleInputs() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .addExtraArgs("-t", "10") + .done() + .addInput("input.mkv") + .addExtraArgs("-t", "20") + .done() + .addOutput("output.mp4") + .done() + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-t", "10", "-i", "input.mp4", "-t", "20", "-i", "input.mkv", "output.mp4"))); + } + + @Test + public void testAddExtraArgsOnInputsAndOutputs() { + List command = new FFmpegBuilder() + .addExtraArgs("-global", "args") + .setVerbosity(FFmpegBuilder.Verbosity.INFO) + .addInput("input.mp4") + .addExtraArgs("-input_args", "1") + .done() + .addInput("input.mkv") + .addExtraArgs("-input_args", "2") + .done() + .addOutput("output.mp4") + .addExtraArgs("-output_args", "1") + .done() + .addOutput("output.mkv") + .addExtraArgs("-output_args", "2") + .done() + .build(); + + assertThat( + command, + is(ImmutableList.of( + "-y", "-v", "info", "-global", "args", + "-input_args", "1", "-i", "input.mp4", + "-input_args", "2", "-i", "input.mkv", + "-output_args", "1", "output.mp4", + "-output_args", "2", "output.mkv") + )); + } +} diff --git a/src/test/java/net/bramp/ffmpeg/ReadmeTest.java b/src/test/java/net/bramp/ffmpeg/ReadmeTest.java index e2256a43..2815893a 100644 --- a/src/test/java/net/bramp/ffmpeg/ReadmeTest.java +++ b/src/test/java/net/bramp/ffmpeg/ReadmeTest.java @@ -44,7 +44,9 @@ public void testVideoEncoding() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .setInput(inFilename) // Filename, or a FFmpegProbeResult + .done() .setInput(in) + .done() .overrideOutputFiles(true) // Override the output if it exists .addOutput("output.mp4") // Filename for the destination .setFormat("mp4") // Format is inferred from filename, or can be set @@ -109,6 +111,7 @@ public void testProgress() throws IOException { FFmpegBuilder builder = new FFmpegBuilder() .setInput(in) // Or filename + .done() .addOutput("output.mp4") .done(); diff --git a/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegInputBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegInputBuilderTest.java new file mode 100644 index 00000000..42d763a1 --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegInputBuilderTest.java @@ -0,0 +1,41 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +public abstract class AbstractFFmpegInputBuilderTest extends AbstractFFmpegStreamBuilderTest { + @Override + protected abstract AbstractFFmpegInputBuilder getBuilder(); + + @Test + public void testReadAtNativeFrameRate() { + List command = getBuilder() + .readAtNativeFrameRate() + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-re"))); + } + + @Test + public void testSetStreamLoopInfinit() { + List command = getBuilder() + .setStreamLoop(-1) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-stream_loop", "-1"))); + } + + @Test + public void testSetStreamLoopCounter() { + List command = getBuilder() + .setStreamLoop(2) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-stream_loop", "2"))); + } +} diff --git a/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilderTest.java new file mode 100644 index 00000000..483c06c0 --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegOutputBuilderTest.java @@ -0,0 +1,114 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public abstract class AbstractFFmpegOutputBuilderTest extends AbstractFFmpegStreamBuilderTest { + + @Override + protected abstract AbstractFFmpegOutputBuilder getBuilder(); + + @Test + public void testConstantRateFactor() { + List command = getBuilder() + .setConstantRateFactor(5) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-crf", "5"))); + } + + @Test + public void testAudioSampleFormat() { + List command = getBuilder() + .setAudioSampleFormat("asf") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-sample_fmt", "asf"))); + } + + @Test + public void testAudioBitrate() { + List command = getBuilder() + .setAudioBitRate(1_000) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-b:a", "1000"))); + } + + @Test + public void testAudioQuality() { + List command = getBuilder() + .setAudioQuality(5) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-qscale:a", "5"))); + } + + @Test + public void testSetAudioBitStreamFilter() { + List command = getBuilder() + .setAudioBitStreamFilter("filter") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-bsf:a", "filter"))); + } + + @Test + public void testSetVideoBitRate() { + List command = getBuilder() + .setVideoBitRate(1_000_000) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-b:v", "1000000"))); + } + + @Test + public void testSetVideoQuality() { + List command = getBuilder() + .setVideoQuality(20) + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-qscale:v", "20"))); + } + + @Test + public void testSetVideoPreset() { + List command = getBuilder() + .setVideoPreset("main") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-vpre", "main"))); + } + + @Test + public void testSetVideoFilter() { + List command = getBuilder() + .setVideoFilter("filter") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-vf", "filter"))); + } + + @Test + public void testSetVideoBitStreamFilter() { + List command = getBuilder() + .setVideoBitStreamFilter("bit-stream-filter") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-bsf:v", "bit-stream-filter"))); + } + + @Test + public void testSetComplexFilter() { + List command = getBuilder() + .setComplexFilter("complex-filter") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-filter_complex", "complex-filter"))); + } +} \ No newline at end of file diff --git a/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilderTest.java new file mode 100644 index 00000000..19cc6f6f --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/AbstractFFmpegStreamBuilderTest.java @@ -0,0 +1,230 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import org.apache.commons.lang3.math.Fraction; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +public abstract class AbstractFFmpegStreamBuilderTest { + protected abstract AbstractFFmpegStreamBuilder getBuilder(); + + protected abstract List removeCommon(List command); + + @Test + public void testSetFormat() { + List command = getBuilder().setFormat("mp4").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-f", "mp4"))); + } + + @Test + public void testSetStartOffset() { + List command = getBuilder().setStartOffset(10, TimeUnit.SECONDS).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-ss", "00:00:10"))); + } + + @Test + public void testSetDuration() { + List command = getBuilder().setDuration(5, TimeUnit.SECONDS).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-t", "00:00:05"))); + } + + @Test + public void testAddMetaTagKeyValue() { + List command = getBuilder().addMetaTag("key", "value").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-metadata", "key=value"))); + } + + @Test + public void testAddMetaTagSpecKeyValue() { + List command = getBuilder().addMetaTag(MetadataSpecifier.stream(1), "key", "value").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-metadata:s:1", "key=value"))); + } + + @Test + public void testDisableAudio() { + List command = getBuilder().disableAudio().build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-an"))); + } + + @Test + public void testSetAudioCodec() { + List command = getBuilder().setAudioCodec("acc").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-acodec", "acc"))); + } + + @Test + public void testSetAudioChannels() { + List command = getBuilder().setAudioChannels(7).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-ac", "7"))); + } + + @Test + public void testSetAudioSampleRate() { + List command = getBuilder().setAudioSampleRate(44100).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-ar", "44100"))); + } + + @Test + public void testSetAudioPreset() { + List command = getBuilder().setAudioPreset("ac").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-apre", "ac"))); + } + + @Test + public void testDisableVideo() { + List command = getBuilder().disableVideo().build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-vn"))); + } + + @Test + public void testSetVideoCodec() { + List command = getBuilder().setVideoCodec("libx264").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-vcodec", "libx264"))); + } + + @Test + public void testSetVideoCopyInkf() { + List command = getBuilder().setVideoCopyInkf(true).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-copyinkf"))); + } + + @Test + public void testSetVideoFrameRateDouble() { + List command = getBuilder().setVideoFrameRate(1d / 60d).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-r", "1/60"))); + } + + @Test + public void testSetVideoFrameRateFraction() { + List command = getBuilder().setVideoFrameRate(Fraction.ONE_THIRD).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-r", "1/3"))); + } + + @Test + public void testSetVideoFrameRateFramesPer() { + List command = getBuilder().setVideoFrameRate(30, 1).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-r", "30/1"))); + } + + @Test + public void testSetVideoWidth() { + List command = getBuilder().setVideoWidth(1920).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of())); + } + + @Test + public void testSetVideoHeight() { + List command = getBuilder().setVideoHeight(1080).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of())); + } + + @Test + public void testSetVideoWidthAndHeight() { + List command = getBuilder().setVideoWidth(1920).setVideoHeight(1080).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-s", "1920x1080"))); + } + + @Test + public void testSetVideoSize() { + List command = getBuilder().setVideoResolution("1920x1080").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-s", "1920x1080"))); + } + + @Test + public void testSetVideoMovflags() { + List command = getBuilder().setVideoMovFlags("mov").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-movflags", "mov"))); + } + + @Test + public void testSetVideoFrames() { + List command = getBuilder().setFrames(30).build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-vframes", "30"))); + } + + @Test + public void testSetVideoPixelFormat() { + List command = getBuilder().setVideoPixelFormat("yuv420").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-pix_fmt", "yuv420"))); + } + + @Test + public void testDisableSubtitle() { + List command = getBuilder().disableSubtitle().build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-sn"))); + } + + @Test + public void testSetSubtitlePreset() { + List command = getBuilder().setSubtitlePreset("ac").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-spre", "ac"))); + } + + @Test + public void testSetSubtitleCodec() { + List command = getBuilder().setSubtitleCodec("vtt").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-scodec", "vtt"))); + } + + @Test + public void testSetPreset() { + List command = getBuilder().setPreset("pre").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-preset", "pre"))); + } + + @Test + public void testSetPresetFilename() { + List command = getBuilder().setPresetFilename("pre.txt").build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-fpre", "pre.txt"))); + } + + @Test + public void testSetStrict() { + List command = getBuilder().setStrict(FFmpegBuilder.Strict.STRICT).build(0); + + assertEquals("strict", command.get(command.indexOf("-strict") + 1)); + } + + @Test + public void testAddExtraArgs() { + List command = getBuilder() + .addExtraArgs("-some", "args") + .build(0); + + assertThat(removeCommon(command), is(ImmutableList.of("-some", "args"))); + } +} diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java index 483ca688..1685786b 100644 --- a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java @@ -4,7 +4,6 @@ import static net.bramp.ffmpeg.FFmpeg.AUDIO_FORMAT_S16; import static net.bramp.ffmpeg.FFmpeg.AUDIO_SAMPLE_48000; import static net.bramp.ffmpeg.FFmpeg.FPS_30; -import static net.bramp.ffmpeg.builder.FFmpegBuilder.Verbosity; import static net.bramp.ffmpeg.builder.MetadataSpecifier.*; import static net.bramp.ffmpeg.builder.StreamSpecifier.tag; import static net.bramp.ffmpeg.builder.StreamSpecifier.usable; @@ -13,7 +12,6 @@ import static org.junit.Assert.assertEquals; import com.google.common.collect.ImmutableList; -import java.io.IOException; import java.net.URI; import java.util.List; import java.util.concurrent.TimeUnit; @@ -27,9 +25,6 @@ @SuppressWarnings("unused") public class FFmpegBuilderTest { - - public FFmpegBuilderTest() throws IOException {} - @Test public void testNormal() { @@ -40,6 +35,7 @@ public void testNormal() { "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36") .setInput("input") .setStartOffset(1500, TimeUnit.MILLISECONDS) + .done() .overrideOutputFiles(true) .addOutput("output") .setFormat("mp4") @@ -102,6 +98,7 @@ public void testDisabled() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .disableAudio() .disableSubtitle() @@ -119,6 +116,7 @@ public void testFilter() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .disableAudio() .disableSubtitle() @@ -147,6 +145,7 @@ public void testFilterAndScale() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .setVideoResolution(320, 240) .setVideoFilter("scale='trunc(ow/a/2)*2:320'") @@ -180,6 +179,7 @@ public void testSetOptions() { EncodingOptions options = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .useOptions(main) .useOptions(audio) @@ -203,6 +203,7 @@ public void testVideoCodecWithEnum() { EncodingOptions options = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .useOptions(main) .useOptions(audio) @@ -220,6 +221,7 @@ public void testMultipleOutputs() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output1") .setVideoResolution(320, 240) .done() @@ -243,6 +245,7 @@ public void testConflictingVideoSize() { List unused = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .setVideoResolution(320, 240) .setVideoResolution("ntsc") @@ -255,6 +258,7 @@ public void testURIOutput() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput(URI.create("udp://10.1.0.102:1234")) .setVideoResolution(320, 240) .done() @@ -271,6 +275,7 @@ public void testURIAndFilenameOutput() { List unused = new FFmpegBuilder() .setInput("input") + .done() .addOutput(URI.create("udp://10.1.0.102:1234")) .setFilename("filename") .done() @@ -279,13 +284,13 @@ public void testURIAndFilenameOutput() { @Test(expected = IllegalArgumentException.class) public void testAddEmptyFilename() { - List unused = new FFmpegBuilder().setInput("input").addOutput("").done().build(); + List unused = new FFmpegBuilder().setInput("input").done().addOutput("").done().build(); } @Test(expected = IllegalArgumentException.class) public void testSetEmptyFilename() { List unused = - new FFmpegBuilder().setInput("input").addOutput("output").setFilename("").done().build(); + new FFmpegBuilder().setInput("input").done().addOutput("output").setFilename("").done().build(); } @Test @@ -294,6 +299,7 @@ public void testMetaTags() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .addMetaTag("comment", "My Comment") .addMetaTag("title", "\"Video\"") @@ -324,6 +330,7 @@ public void testMetaTagsWithSpecifier() { List args = new FFmpegBuilder() .setInput("input") + .done() .addOutput("output") .addMetaTag("title", "Movie Title") .addMetaTag(chapter(0), "author", "Bob") @@ -378,6 +385,7 @@ public void testExtraArgs() { new FFmpegBuilder() .addExtraArgs("-a", "b") .setInput("input") + .done() .addOutput("output") .addExtraArgs("-c", "d") .disableAudio() @@ -396,6 +404,7 @@ public void testVbr() { List args = new FFmpegBuilder() .setInput("input") + .done() .setVBR(2) .addOutput("output") .done() @@ -412,6 +421,7 @@ public void testVbrNegativeParam() { List args = new FFmpegBuilder() .setInput("input") + .done() .setVBR(-3) .addOutput("output") .done() @@ -423,6 +433,7 @@ public void testVbrQualityExceedsRange() { List args = new FFmpegBuilder() .setInput("input") + .done() .setVBR(10) .addOutput("output") .done() @@ -439,7 +450,9 @@ public void testMultipleInput() { List args = new FFmpegBuilder() .addInput("input1") + .done() .addInput("input2") + .done() .addOutput("output") .done() .build(); @@ -453,6 +466,7 @@ public void testAlternativeBuilderPattern() { List args = new FFmpegBuilder() .addInput("input") + .done() .addOutput(new FFmpegOutputBuilder().setFilename("output.mp4").setVideoCodec("libx264")) .addOutput(new FFmpegOutputBuilder().setFilename("output.flv").setVideoCodec("flv")) .build(); @@ -478,6 +492,7 @@ public void testPresets() { List args = new FFmpegBuilder() .addInput("input") + .done() .addOutput("output") .setPreset("a") .setPresetFilename("b") @@ -500,6 +515,7 @@ public void testThreads() { new FFmpegBuilder() .setThreads(2) .addInput("input") + .done() .addOutput("output") .done() .build(); @@ -509,6 +525,22 @@ public void testThreads() { ImmutableList.of("-y", "-v", "error", "-threads", "2", "-i", "input", "output")); } + @Test + public void testSetLoop() { + List args = + new FFmpegBuilder() + .addInput("input") + .setStreamLoop(2) + .done() + .addOutput("output") + .done() + .build(); + + assertEquals( + args, + ImmutableList.of("-y", "-v", "error", "-stream_loop", "2", "-i", "input", "output")); + } + @Test(expected = IllegalArgumentException.class) public void testZeroThreads() { new FFmpegBuilder().setThreads(0); @@ -518,4 +550,39 @@ public void testZeroThreads() { public void testNegativeNumberOfThreads() { new FFmpegBuilder().setThreads(-1); } + + @Test + public void testQuestion156(){ + List args = + new FFmpegBuilder() + .overrideOutputFiles(true) + .setVerbosity(FFmpegBuilder.Verbosity.INFO) + // X11 screen input + .addInput(":0.0+0,0") + .setFormat("x11grab") + .setVideoResolution("1280x720") + .setVideoFrameRate(30) + .addExtraArgs("-draw_mouse", "0") + .addExtraArgs("-thread_queue_size", "4096") + .done() + // alsa audio input + .addInput("hw:0,1,0") + .setFormat("alsa") + .addExtraArgs("-thread_queue_size", "4096") + .done() + // Youtube output + .addOutput("rtmp://a.rtmp.youtube.com/live2/XXX") + .setAudioCodec("aac") + .setFormat("flv") + .done() + .build(); + + assertEquals( + ImmutableList.of("-y", "-v", "info", + "-f", "x11grab", "-s", "1280x720", "-r", "30/1", "-draw_mouse", "0", "-thread_queue_size", "4096", "-i", ":0.0+0,0", + "-f", "alsa", "-thread_queue_size", "4096", "-i", "hw:0,1,0", + "-f", "flv", "-acodec", "aac", "rtmp://a.rtmp.youtube.com/live2/XXX"), + args + ); + } } diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTwoPassTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTwoPassTest.java new file mode 100644 index 00000000..c7788791 --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTwoPassTest.java @@ -0,0 +1,91 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +import java.util.List; + +import static net.bramp.ffmpeg.builder.AbstractFFmpegStreamBuilder.DEVNULL; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class FFmpegBuilderTwoPassTest { + + @Test + public void firstPass() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setVideoBitRate(1_000_000) + .setFormat("mp4") + .done() + .setPass(1) + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-an", "-i", "input.mp4", "-pass", "1", "-f", "mp4", "-b:v", "1000000", "-an", DEVNULL))); + } + + @Test + public void secondPass() { + List command = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setVideoBitRate(1_000_000) + .setFormat("mp4") + .done() + .setPass(2) + .build(); + + assertThat(command, is(ImmutableList.of("-y", "-v", "error", "-i", "input.mp4", "-pass", "2", "-f", "mp4", "-b:v", "1000000", "output.mp4"))); + } + + @Test(expected = IllegalArgumentException.class) + public void firstPassNoBitrate() { + List ignored = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setFormat("mp4") + .done() + .setPass(1) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void secondPassNoBitrate() { + List ignored = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setFormat("mp4") + .done() + .setPass(2) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void firstPassNoFormat() { + List ignored = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setVideoBitRate(1_000_000) + .done() + .setPass(1) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void secondPassNoFormat() { + List ignored = new FFmpegBuilder() + .addInput("input.mp4") + .done() + .addOutput("output.mp4") + .setVideoBitRate(1_000_000) + .done() + .setPass(2) + .build(); + } +} diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegFileInputBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegFileInputBuilderTest.java new file mode 100644 index 00000000..5e55b123 --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegFileInputBuilderTest.java @@ -0,0 +1,32 @@ +package net.bramp.ffmpeg.builder; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; + +import java.util.List; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +public class FFmpegFileInputBuilderTest extends AbstractFFmpegInputBuilderTest { + @Override + protected AbstractFFmpegInputBuilder getBuilder() { + return new FFmpegBuilder().addInput("input.mp4"); + } + + @Override + protected List removeCommon(List command) { + assertEquals(command.get(command.size() - 1), "input.mp4"); + assertEquals(command.get(command.size() - 2), "-i"); + + return command.subList(0, command.size() - 2); + } + + @Test + public void testFileName() { + List command = new FFmpegBuilder().addInput("input.mp4").build(0); + + assertThat(command, is(ImmutableList.of("-i", "input.mp4"))); + } +} \ No newline at end of file diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegHlsOutputBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegHlsOutputBuilderTest.java index 8131113e..9dd13a17 100644 --- a/src/test/java/net/bramp/ffmpeg/builder/FFmpegHlsOutputBuilderTest.java +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegHlsOutputBuilderTest.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.TimeUnit; @@ -12,18 +13,35 @@ import java.io.IOException; import java.util.List; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class FFmpegHlsOutputBuilderTest { +public class FFmpegHlsOutputBuilderTest extends AbstractFFmpegOutputBuilderTest { public FFmpegHlsOutputBuilderTest() { } + @Override + protected AbstractFFmpegOutputBuilder getBuilder() { + return new FFmpegBuilder().addInput("input.mp4").done().addHlsOutput("output.m3u8"); + } + + @Override + protected List removeCommon(List command) { + assertEquals("-f", command.get(0)); + assertEquals("hls", command.get(1)); + assertEquals("output.m3u8", command.get(command.size() - 1)); + + return command.subList(2, command.size() - 1); + } + @Test public void testAddHlsOutput() { List args = new FFmpegBuilder() .setInput("input") + .done() .addHlsOutput("output.m3u8") .setHlsTime(5, TimeUnit.MILLISECONDS) .setHlsBaseUrl("test1234/") @@ -43,6 +61,7 @@ public void mixedHlsAndDefault() { List args = new FFmpegBuilder() .setInput("input") + .done() .addHlsOutput("output.m3u8") .setHlsTime(5, TimeUnit.MILLISECONDS) .setHlsBaseUrl("test1234/") @@ -61,12 +80,16 @@ public void mixedHlsAndDefault() { @Test public void testConvertVideoToHls() throws IOException { + Path manifestFilePath = Paths.get("tmp/output.m3u8"); + Path segmentFilePath = Paths.get("tmp/file000.ts"); + Files.createDirectories(Paths.get("tmp/")); - Files.deleteIfExists(Paths.get("tmp/output.m3u8")); - Files.deleteIfExists(Paths.get("tmp/file000.m3u8")); + Files.deleteIfExists(manifestFilePath); + Files.deleteIfExists(segmentFilePath); List command = new FFmpegBuilder() .setInput(Samples.TEST_PREFIX + Samples.base_big_buck_bunny_720p_1mb) + .done() .addHlsOutput("tmp/output.m3u8") .setHlsTime(5, TimeUnit.SECONDS) .setHlsBaseUrl("test1234/") @@ -79,7 +102,108 @@ public void testConvertVideoToHls() throws IOException { new FFmpeg().run(command); - assertTrue(Files.exists(Paths.get("tmp/output.m3u8"))); - assertTrue(Files.exists(Paths.get("tmp/file000.ts"))); + assertTrue(Files.exists(manifestFilePath)); + assertTrue(Files.exists(segmentFilePath)); + } + + @Test + public void testConvertVideoToHlsSetHlsTime() throws IOException { + Path manifestFilePath = Paths.get("tmp/output.m3u8"); + Path segmentFilePath = Paths.get("tmp/file000.ts"); + + Files.createDirectories(Paths.get("tmp/")); + Files.deleteIfExists(manifestFilePath); + Files.deleteIfExists(segmentFilePath); + + List command = new FFmpegBuilder() + .setInput(Samples.TEST_PREFIX + Samples.base_big_buck_bunny_720p_1mb) + .done() + .addHlsOutput("tmp/output.m3u8") + .setHlsTime(5, TimeUnit.SECONDS) + .setHlsSegmentFileName("tmp/file%03d.ts") + .done() + .build(); + + new FFmpeg().run(command); + + assertTrue(Files.exists(manifestFilePath)); + assertTrue(Files.exists(segmentFilePath)); + } + + @Test + public void testConvertVideoToHlsSetHlsInitTime() throws IOException { + Path manifestFilePath = Paths.get("tmp/output.m3u8"); + Path segmentFilePath = Paths.get("tmp/file000.ts"); + + Files.createDirectories(Paths.get("tmp/")); + Files.deleteIfExists(manifestFilePath); + Files.deleteIfExists(segmentFilePath); + + List command = new FFmpegBuilder() + .setInput(Samples.TEST_PREFIX + Samples.base_big_buck_bunny_720p_1mb) + .done() + .addHlsOutput("tmp/output.m3u8") + .setHlsInitTime(5, TimeUnit.SECONDS) + .setHlsSegmentFileName("tmp/file%03d.ts") + .done() + .build(); + + new FFmpeg().run(command); + + assertTrue(Files.exists(manifestFilePath)); + assertTrue(Files.exists(segmentFilePath)); + } + + @Test + public void testHlsTime() { + List command = new FFmpegHlsOutputBuilder(new FFmpegBuilder(), "output.m3u8") + .setHlsTime(5, TimeUnit.SECONDS) + .build(0); + + assertThat(command, is(ImmutableList.of("-f", "hls", "-hls_time", "00:00:05", "output.m3u8"))); + } + + @Test + public void testHlsSegmentFileName() { + List command = new FFmpegHlsOutputBuilder(new FFmpegBuilder(), "output.m3u8") + .setHlsSegmentFileName("segment%03d.ts") + .build(0); + + assertThat(command, is(ImmutableList.of("-f", "hls", "-hls_segment_filename", "segment%03d.ts", "output.m3u8"))); + } + + @Test + public void testHlsInitTime() { + List command = new FFmpegHlsOutputBuilder(new FFmpegBuilder(), "output.m3u8") + .setHlsInitTime(10, TimeUnit.MILLISECONDS) + .build(0); + + assertThat(command, is(ImmutableList.of("-f", "hls", "-hls_init_time", "00:00:00.01", "output.m3u8"))); + } + + @Test + public void testHlsListSize() { + List command = new FFmpegHlsOutputBuilder(new FFmpegBuilder(), "output.m3u8") + .setHlsListSize(3) + .build(0); + + assertThat(command, is(ImmutableList.of("-f", "hls", "-hls_list_size", "3", "output.m3u8"))); + } + + @Test + public void testHlsBaseUrl() { + List command = new FFmpegHlsOutputBuilder(new FFmpegBuilder(), "output.m3u8") + .setHlsBaseUrl("/base") + .build(0); + + assertThat(command, is(ImmutableList.of("-f", "hls", "-hls_base_url", "/base", "output.m3u8"))); + } + + @Override + public void testSetFormat() { + List command = getBuilder().setFormat("hls").build(0); + + // removeCommon already asserts -f hls and removes that part. Therefore: Expecting no more elements + assertEquals(removeCommon(command).size(), 0); } } diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilderTest.java new file mode 100644 index 00000000..766ef34a --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilderTest.java @@ -0,0 +1,20 @@ +package net.bramp.ffmpeg.builder; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class FFmpegOutputBuilderTest extends AbstractFFmpegOutputBuilderTest { + + @Override + protected AbstractFFmpegOutputBuilder getBuilder() { + return new FFmpegBuilder().addInput("input.mp4").done().addOutput("output.mp4"); + } + + @Override + protected List removeCommon(List command) { + assertEquals("output.mp4", command.get(command.size() - 1)); + + return command.subList(0, command.size() - 1); + } +} \ No newline at end of file diff --git a/src/test/java/net/bramp/ffmpeg/nut/NutReaderTest.java b/src/test/java/net/bramp/ffmpeg/nut/NutReaderTest.java index 8943067f..5f9cdbb3 100644 --- a/src/test/java/net/bramp/ffmpeg/nut/NutReaderTest.java +++ b/src/test/java/net/bramp/ffmpeg/nut/NutReaderTest.java @@ -40,6 +40,7 @@ public void testNutReader() List args = new FFmpegBuilder() .setInput(Samples.big_buck_bunny_720p_1mb) + .done() .addStdoutOutput() .setFormat("nut") .setVideoCodec("rawvideo")