From dcc0e0e76c53dcdcfe66b04926d65eaf4f30c45d Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 18 Jun 2016 15:33:40 -0700 Subject: [PATCH 1/2] Half finished spec idea. --- .../ffmpeg/builder/ChapterMetadataSpec.java | 16 +++ .../ffmpeg/builder/FFmpegOutputBuilder.java | 8 ++ .../ffmpeg/builder/GlobalMetadataSpec.java | 15 ++ .../bramp/ffmpeg/builder/MetadataSpec.java | 48 +++++++ .../ffmpeg/builder/ProgramMetadataSpec.java | 16 +++ .../ffmpeg/builder/StreamMetadataSpec.java | 18 +++ .../net/bramp/ffmpeg/builder/StreamSpec.java | 135 ++++++++++++++++++ .../ffmpeg/builder/MetadataSpecTest.java | 18 +++ .../bramp/ffmpeg/builder/StreamSpecTest.java | 28 ++++ 9 files changed, 302 insertions(+) create mode 100644 src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java create mode 100644 src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java create mode 100644 src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java diff --git a/src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java new file mode 100644 index 00000000..1bdec202 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java @@ -0,0 +1,16 @@ +package net.bramp.ffmpeg.builder; + +public class ChapterMetadataSpec extends MetadataSpec { + + public final int index; + + protected ChapterMetadataSpec(int index) { + super(Type.Chapter); + this.index = index; + } + + @Override + public String toString() { + return "c:" + index; + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java index 0fd79026..dcaa3455 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java +++ b/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java @@ -309,6 +309,14 @@ public FFmpegOutputBuilder addMetaTag(String key, String value) { return this; } + + public FFmpegOutputBuilder addMetaTag(MetadataSpec metadata_spec, String key, String value) { + checkNotNull(key, "Key may not be null"); + checkNotNull(value, "Value may not be null"); + meta_tags.add(key + "=" + value.replace("\"", "")); + return this; + } + public FFmpegOutputBuilder setAudioCodec(String codec) { this.audio_enabled = true; this.audio_codec = checkNotNull(codec); diff --git a/src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java new file mode 100644 index 00000000..cf3153f7 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java @@ -0,0 +1,15 @@ +package net.bramp.ffmpeg.builder; + +public class GlobalMetadataSpec extends MetadataSpec { + + protected final static GlobalMetadataSpec SINGLETON = new GlobalMetadataSpec(); + + protected GlobalMetadataSpec() { + super(Type.Global); + } + + @Override + public String toString() { + return "g"; + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java new file mode 100644 index 00000000..4117cf29 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java @@ -0,0 +1,48 @@ +package net.bramp.ffmpeg.builder; + +// metadata_spec can be: +// g global (If metadata specifier is omitted, it defaults to global.) +// s[:stream_spec] +// c:chapter_index +// p:program_index +// index is meant to be zero based, by negitive is allowed as dummy values + + +/** + * Metadata spec, as described in the "map_metadata" section of + * https://www.ffmpeg.org/ffmpeg-all.html#Main-options + */ +public abstract class MetadataSpec { + + enum Type { + Global, Stream, Chapter, Program + } + + public final Type type; + + protected MetadataSpec(Type type) { + this.type = type; + } + + public abstract String toString(); + + public static GlobalMetadataSpec global() { + return GlobalMetadataSpec.SINGLETON; + } + + public static ChapterMetadataSpec chapter(int index) { + return new ChapterMetadataSpec(index); + } + + public static ProgramMetadataSpec program(int index) { + return new ProgramMetadataSpec(index); + } + + public static StreamMetadataSpec stream(StreamSpec spec) { + return new StreamMetadataSpec(spec); + } + + public static StreamMetadataSpec stream(int spec) { + return new StreamMetadataSpec(spec); + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java new file mode 100644 index 00000000..46d3c362 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java @@ -0,0 +1,16 @@ +package net.bramp.ffmpeg.builder; + +public class ProgramMetadataSpec extends MetadataSpec { + + public final int index; + + protected ProgramMetadataSpec(int index) { + super(Type.Program); + this.index = index; + } + + @Override + public String toString() { + return "p:" + index; + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java new file mode 100644 index 00000000..24d37029 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java @@ -0,0 +1,18 @@ +package net.bramp.ffmpeg.builder; + +import static com.google.common.base.Preconditions.checkNotNull; + +public class StreamMetadataSpec extends MetadataSpec { + + public final StreamSpec spec; + + protected StreamMetadataSpec(StreamSpec spec) { + super(Type.Stream); + this.spec = checkNotNull(spec); + } + + @Override + public String toString() { + return "s:" + spec.toString(); + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java b/src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java new file mode 100644 index 00000000..1300f41f --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java @@ -0,0 +1,135 @@ +package net.bramp.ffmpeg.builder; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * https://ffmpeg.org/ffmpeg.html#Stream-specifiers + */ +public abstract class StreamSpec { + + enum Type { + StreamIndex, Program, StreamId, Metadata, Usable + } + + enum StreamType { + Any(""), + Video("v"), + PureVideo("V"), + Audio("a"), + Subtiles("s"), + Data("d"), + Attachments("t"); + + final String prefix; + + StreamType(String prefix) { + this.prefix = prefix; + } + + public String toString() { + return prefix; + } + } + + public final Type type; + + protected StreamSpec(Type type) { + this.type = type; + } + + public static class IndexStreamSpec extends StreamSpec { + + } + + public static class ProgramStreamSpec extends StreamSpec { + + } + + public static class IdStreamSpec extends StreamSpec { + + } + + public static class MetadataStreamSpec extends StreamSpec { + + public final String key, value; + + protected MetadataStreamSpec(String key, String value) { + super(Type.Metadata); + this.key = checkNotNull(key); + this.value = value; + } + + @Override + public String toString() { + return "m:" + key + (value != null ? ":" + value : ""); + } + } + + public static class UsableStreamSpec extends StreamSpec { + protected final static UsableStreamSpec SINGLETON = new UsableStreamSpec(); + + protected UsableStreamSpec() { + super(Type.Usable); + } + + @Override + public String toString() { + return "u"; + } + } + + public abstract String toString(); + + public StreamSpec stream(int stream_index) { + return null; + } + + public StreamSpec stream(StreamType stream_type) { + checkNotNull(stream_type); + return null; + } + + public StreamSpec stream(StreamType stream_type, int stream_index) { + checkNotNull(stream_type); + return null; + } + + public StreamSpec program(int program_id) { + return null; + } + + public StreamSpec program(int program_id, int stream_index) { + return null; + } + + public StreamSpec id(int stream_id) { + return null; + } + + public StreamSpec tag(String key) { + checkNotNull(key); + return null; + } + + public StreamSpec tag(String key, String value) { + checkNotNull(key); + checkNotNull(value); + return null; + } + + public StreamSpec usable() { + return UsableStreamSpec.SINGLETON; + } +} + + +// metadata_spec can be: +// g global (If metadata specifier is omitted, it defaults to global.) +// s[:stream_spec] +// c:chapter_index +// p:program_index +// index is meant to be zero based, by negitive is allowed as dummy values +/* + * public abstract class MetadataSpec { global() stream(index(0)) stream(video(), index(0)) + * chapter(1) program(1) } + */ diff --git a/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java b/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java new file mode 100644 index 00000000..5403fa58 --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java @@ -0,0 +1,18 @@ +package net.bramp.ffmpeg.builder; + +import org.junit.Test; + +import static net.bramp.ffmpeg.builder.MetadataSpec.*; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + +public class MetadataSpecTest { + + @Test + public void testMetaSpec() { + assertThat(global().toString(), is("g")); + assertThat(chapter(1).toString(), is("c:1")); + assertThat(program(1).toString(), is("p:1")); + assertThat(stream(1).toString(), is("s:1")); + } +} diff --git a/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java b/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java new file mode 100644 index 00000000..c93328e7 --- /dev/null +++ b/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java @@ -0,0 +1,28 @@ +package net.bramp.ffmpeg.builder; + +import static net.bramp.ffmpeg.builder.MetadataSpec.stream; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; + +public class StreamSpecTest { + + @Test + public void testStreamSpec() { + assertThat(stream(1).toString(), is("1")); + assertThat(stream(VIDEO, 1).toString(), is("v:1")); + + assertThat(chapter(1).toString(), is("c:1")); + assertThat(program(1).toString(), is("p:1")); + } + + addMetaTag("key", "value", ); + addMetaTag("key", "value", chapter(1)); + addMetaTag("key", "value", program(1)); + addMetaTag("key", "value", stream(0)); + addMetaTag("key", "value", stream(VIDEO, 0); + addMetaTag("key", "value", stream(AUDIO, 0); + addMetaTag("key", "value", stream(id(0x502)); + addMetaTag("key", "value", stream(tag("key2", "value2"))); // This one is confusing, key2 refers to the output stream, and would apply key/value to any output stream already with key2/value2. + */ +} +} From 8c2f4ab59fcf2b37a124e341bcd46f9e5206dc0d Mon Sep 17 00:00:00 2001 From: Andrew Brampton Date: Sat, 18 Jun 2016 20:12:30 -0700 Subject: [PATCH 2/2] Add support for metadata_specifier. Fixes #25 --- .../ffmpeg/builder/ChapterMetadataSpec.java | 16 --- .../ffmpeg/builder/FFmpegOutputBuilder.java | 52 +++++-- .../ffmpeg/builder/GlobalMetadataSpec.java | 15 -- .../bramp/ffmpeg/builder/MetadataSpec.java | 48 ------- .../ffmpeg/builder/MetadataSpecifier.java | 76 ++++++++++ .../ffmpeg/builder/ProgramMetadataSpec.java | 16 --- .../ffmpeg/builder/StreamMetadataSpec.java | 18 --- .../net/bramp/ffmpeg/builder/StreamSpec.java | 135 ------------------ .../bramp/ffmpeg/builder/StreamSpecifier.java | 118 +++++++++++++++ .../ffmpeg/builder/StreamSpecifierType.java | 43 ++++++ .../ffmpeg/builder/FFmpegBuilderTest.java | 43 +++++- .../ffmpeg/builder/MetadataSpecTest.java | 12 +- .../bramp/ffmpeg/builder/StreamSpecTest.java | 37 ++--- 13 files changed, 346 insertions(+), 283 deletions(-) delete mode 100644 src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java delete mode 100644 src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java delete mode 100644 src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java delete mode 100644 src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java delete mode 100644 src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java delete mode 100644 src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java create mode 100644 src/main/java/net/bramp/ffmpeg/builder/StreamSpecifierType.java diff --git a/src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java deleted file mode 100644 index 1bdec202..00000000 --- a/src/main/java/net/bramp/ffmpeg/builder/ChapterMetadataSpec.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.bramp.ffmpeg.builder; - -public class ChapterMetadataSpec extends MetadataSpec { - - public final int index; - - protected ChapterMetadataSpec(int index) { - super(Type.Chapter); - this.index = index; - } - - @Override - public String toString() { - return "c:" + index; - } -} diff --git a/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java b/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java index dcaa3455..243f3679 100644 --- a/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java +++ b/src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java @@ -11,7 +11,6 @@ import net.bramp.ffmpeg.probe.FFmpegProbeResult; import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.math.Fraction; -import org.apache.commons.lang3.tuple.ImmutablePair; import java.net.URI; import java.util.List; @@ -22,6 +21,7 @@ import static com.google.common.base.Preconditions.checkState; import static net.bramp.ffmpeg.FFmpegUtils.millisecondsToString; import static com.google.common.base.Preconditions.checkNotNull; +import static net.bramp.ffmpeg.builder.MetadataSpecifier.checkValidKey; /** * Builds a representation of a single output/encoding setting @@ -303,17 +303,49 @@ public FFmpegOutputBuilder setComplexVideoFilter(String filter) { * @return this */ public FFmpegOutputBuilder addMetaTag(String key, String value) { - checkNotNull(key, "Key may not be null"); - checkNotNull(value, "Value may not be null"); - meta_tags.add(key + "=" + value.replace("\"", "")); + checkValidKey(key); + checkNotNull(value); + meta_tags.add("-metadata"); + meta_tags.add(key + "=" + value); return this; } - - public FFmpegOutputBuilder addMetaTag(MetadataSpec metadata_spec, String key, String value) { - checkNotNull(key, "Key may not be null"); - checkNotNull(value, "Value may not be null"); - meta_tags.add(key + "=" + value.replace("\"", "")); + /** + * Add metadata on output streams. Which keys are possible depends on the used codec. + * + *
+   * {@code
+   * import static net.bramp.ffmpeg.builder.MetadataSpecifier.*;
+   * import static net.bramp.ffmpeg.builder.StreamSpecifier.*;
+   * import static net.bramp.ffmpeg.builder.StreamSpecifierType.*;
+   * 
+   * new FFmpegBuilder()
+   *   .addMetaTag("title", "Movie Title") // Annotate whole file
+   *   .addMetaTag(chapter(0), "author", "Bob") // Annotate first chapter
+   *   .addMetaTag(program(0), "comment", "Awesome") // Annotate first program
+   *   .addMetaTag(stream(0), "copyright", "Megacorp") // Annotate first stream
+   *   .addMetaTag(stream(Video), "framerate", "24fps") // Annotate all video streams
+   *   .addMetaTag(stream(Video, 0), "artist", "Joe") // Annotate first video stream
+   *   .addMetaTag(stream(Audio, 0), "language", "eng") // Annotate first audio stream
+   *   .addMetaTag(stream(Subtitle, 0), "language", "fre") // Annotate first subtitle stream
+   *   .addMetaTag(usable(), "year", "2010") // Annotate all streams with a usable configuration
+   * }
+   * 
+ * + * assertThat(global().spec(), is("g")); assertThat(chapter(1).spec(), is("c:1")); + * assertThat(program(1).spec(), is("p:1")); assertThat(stream(1).spec(), is("s:1")); + * assertThat(stream(id(1)).spec(), is("s:i:1")); + * + * @param spec Metadata specifier, e.g `MetadataSpec.stream(Audio, 0)` + * @param key Metadata key, e.g. "comment" + * @param value Value to set for key + * @return this + */ + public FFmpegOutputBuilder addMetaTag(MetadataSpecifier spec, String key, String value) { + checkValidKey(key); + checkNotNull(value); + meta_tags.add("-metadata:" + spec.spec()); + meta_tags.add(key + "=" + value); return this; } @@ -526,7 +558,7 @@ protected List build(int pass) { } for (String meta : meta_tags) { - args.add("-metadata").add("\"" + meta + "\""); + args.add(meta); } if (video_enabled) { diff --git a/src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java deleted file mode 100644 index cf3153f7..00000000 --- a/src/main/java/net/bramp/ffmpeg/builder/GlobalMetadataSpec.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.bramp.ffmpeg.builder; - -public class GlobalMetadataSpec extends MetadataSpec { - - protected final static GlobalMetadataSpec SINGLETON = new GlobalMetadataSpec(); - - protected GlobalMetadataSpec() { - super(Type.Global); - } - - @Override - public String toString() { - return "g"; - } -} diff --git a/src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java deleted file mode 100644 index 4117cf29..00000000 --- a/src/main/java/net/bramp/ffmpeg/builder/MetadataSpec.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.bramp.ffmpeg.builder; - -// metadata_spec can be: -// g global (If metadata specifier is omitted, it defaults to global.) -// s[:stream_spec] -// c:chapter_index -// p:program_index -// index is meant to be zero based, by negitive is allowed as dummy values - - -/** - * Metadata spec, as described in the "map_metadata" section of - * https://www.ffmpeg.org/ffmpeg-all.html#Main-options - */ -public abstract class MetadataSpec { - - enum Type { - Global, Stream, Chapter, Program - } - - public final Type type; - - protected MetadataSpec(Type type) { - this.type = type; - } - - public abstract String toString(); - - public static GlobalMetadataSpec global() { - return GlobalMetadataSpec.SINGLETON; - } - - public static ChapterMetadataSpec chapter(int index) { - return new ChapterMetadataSpec(index); - } - - public static ProgramMetadataSpec program(int index) { - return new ProgramMetadataSpec(index); - } - - public static StreamMetadataSpec stream(StreamSpec spec) { - return new StreamMetadataSpec(spec); - } - - public static StreamMetadataSpec stream(int spec) { - return new StreamMetadataSpec(spec); - } -} diff --git a/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java new file mode 100644 index 00000000..a49064a8 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java @@ -0,0 +1,76 @@ +package net.bramp.ffmpeg.builder; + +// metadata_spec can be: +// g global (If metadata specifier is omitted, it defaults to global.) +// s[:stream_spec] +// c:chapter_index +// p:program_index +// index is meant to be zero based, by negitive is allowed as dummy values + + +import com.google.common.base.CharMatcher; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Metadata spec, as described in the "map_metadata" section of + * https://www.ffmpeg.org/ffmpeg-all.html#Main-options + */ +public class MetadataSpecifier { + + final String spec; + + private MetadataSpecifier(String spec) { + this.spec = checkNotNull(spec); + } + + private MetadataSpecifier(String prefix, int index) { + this.spec = checkNotNull(prefix) + ":" + index; + } + + private MetadataSpecifier(String prefix, StreamSpecifier spec) { + this.spec = checkNotNull(prefix) + ":" + checkNotNull(spec).spec(); + } + + public String spec() { + return spec; + } + + public static String checkValidKey(String key) { + checkNotNull(key); + checkArgument(!key.isEmpty(), "key must not be empty"); + checkArgument(key.matches("[a-zA-Z0-9_]+"), "key must only contain letters, numbers, and _"); + return key; + } + + public static MetadataSpecifier global() { + return new MetadataSpecifier("g"); + } + + public static MetadataSpecifier chapter(int index) { + return new MetadataSpecifier("c", index); + } + + public static MetadataSpecifier program(int index) { + return new MetadataSpecifier("p", index); + } + + public static MetadataSpecifier stream(int index) { + return new MetadataSpecifier("s", StreamSpecifier.stream(index)); + } + + public static MetadataSpecifier stream(StreamSpecifierType type) { + return new MetadataSpecifier("s", StreamSpecifier.stream(type)); + } + + public static MetadataSpecifier stream(StreamSpecifierType stream_type, int stream_index) { + return new MetadataSpecifier("s", StreamSpecifier.stream(stream_type, stream_index)); + } + + public static MetadataSpecifier stream(StreamSpecifier spec) { + checkNotNull(spec); + return new MetadataSpecifier("s", spec); + } + +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java deleted file mode 100644 index 46d3c362..00000000 --- a/src/main/java/net/bramp/ffmpeg/builder/ProgramMetadataSpec.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.bramp.ffmpeg.builder; - -public class ProgramMetadataSpec extends MetadataSpec { - - public final int index; - - protected ProgramMetadataSpec(int index) { - super(Type.Program); - this.index = index; - } - - @Override - public String toString() { - return "p:" + index; - } -} diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java b/src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java deleted file mode 100644 index 24d37029..00000000 --- a/src/main/java/net/bramp/ffmpeg/builder/StreamMetadataSpec.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.bramp.ffmpeg.builder; - -import static com.google.common.base.Preconditions.checkNotNull; - -public class StreamMetadataSpec extends MetadataSpec { - - public final StreamSpec spec; - - protected StreamMetadataSpec(StreamSpec spec) { - super(Type.Stream); - this.spec = checkNotNull(spec); - } - - @Override - public String toString() { - return "s:" + spec.toString(); - } -} diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java b/src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java deleted file mode 100644 index 1300f41f..00000000 --- a/src/main/java/net/bramp/ffmpeg/builder/StreamSpec.java +++ /dev/null @@ -1,135 +0,0 @@ -package net.bramp.ffmpeg.builder; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * https://ffmpeg.org/ffmpeg.html#Stream-specifiers - */ -public abstract class StreamSpec { - - enum Type { - StreamIndex, Program, StreamId, Metadata, Usable - } - - enum StreamType { - Any(""), - Video("v"), - PureVideo("V"), - Audio("a"), - Subtiles("s"), - Data("d"), - Attachments("t"); - - final String prefix; - - StreamType(String prefix) { - this.prefix = prefix; - } - - public String toString() { - return prefix; - } - } - - public final Type type; - - protected StreamSpec(Type type) { - this.type = type; - } - - public static class IndexStreamSpec extends StreamSpec { - - } - - public static class ProgramStreamSpec extends StreamSpec { - - } - - public static class IdStreamSpec extends StreamSpec { - - } - - public static class MetadataStreamSpec extends StreamSpec { - - public final String key, value; - - protected MetadataStreamSpec(String key, String value) { - super(Type.Metadata); - this.key = checkNotNull(key); - this.value = value; - } - - @Override - public String toString() { - return "m:" + key + (value != null ? ":" + value : ""); - } - } - - public static class UsableStreamSpec extends StreamSpec { - protected final static UsableStreamSpec SINGLETON = new UsableStreamSpec(); - - protected UsableStreamSpec() { - super(Type.Usable); - } - - @Override - public String toString() { - return "u"; - } - } - - public abstract String toString(); - - public StreamSpec stream(int stream_index) { - return null; - } - - public StreamSpec stream(StreamType stream_type) { - checkNotNull(stream_type); - return null; - } - - public StreamSpec stream(StreamType stream_type, int stream_index) { - checkNotNull(stream_type); - return null; - } - - public StreamSpec program(int program_id) { - return null; - } - - public StreamSpec program(int program_id, int stream_index) { - return null; - } - - public StreamSpec id(int stream_id) { - return null; - } - - public StreamSpec tag(String key) { - checkNotNull(key); - return null; - } - - public StreamSpec tag(String key, String value) { - checkNotNull(key); - checkNotNull(value); - return null; - } - - public StreamSpec usable() { - return UsableStreamSpec.SINGLETON; - } -} - - -// metadata_spec can be: -// g global (If metadata specifier is omitted, it defaults to global.) -// s[:stream_spec] -// c:chapter_index -// p:program_index -// index is meant to be zero based, by negitive is allowed as dummy values -/* - * public abstract class MetadataSpec { global() stream(index(0)) stream(video(), index(0)) - * chapter(1) program(1) } - */ diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java b/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java new file mode 100644 index 00000000..419f27e6 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java @@ -0,0 +1,118 @@ +package net.bramp.ffmpeg.builder; + +import static com.google.common.base.Preconditions.checkNotNull; +import static net.bramp.ffmpeg.builder.MetadataSpecifier.checkValidKey; + +/** + * + * https://ffmpeg.org/ffmpeg.html#Stream-specifiers + */ +public class StreamSpecifier { + + final String spec; + + private StreamSpecifier(String spec) { + this.spec = spec; + } + + public String spec() { + return spec; + } + + /** + * Matches the stream with this index. + * + * @param index + * @return + */ + public static StreamSpecifier stream(int index) { + return new StreamSpecifier(String.valueOf(index)); + } + + /** + * Matches all streams of this type. + * + * @param type + * @return + */ + public static StreamSpecifier stream(StreamSpecifierType type) { + checkNotNull(type); + return new StreamSpecifier(type.toString()); + } + + /** + * Matches the stream number stream_index of this type. + * + * @param type + * @param index + * @return + */ + public static StreamSpecifier stream(StreamSpecifierType type, int index) { + checkNotNull(type); + return new StreamSpecifier(type.toString() + ":" + index); + } + + /** + * Matches all streams in the program. + * + * @param program_id + * @return + */ + public static StreamSpecifier program(int program_id) { + return new StreamSpecifier("p:" + program_id); + } + + /** + * Matches the stream with number stream_index in the program with the id program_id. + * + * @param program_id + * @param stream_index + * @return + */ + public static StreamSpecifier program(int program_id, int stream_index) { + return new StreamSpecifier("p:" + program_id + ":" + stream_index); + } + + /** + * Match the stream by stream id (e.g. PID in MPEG-TS container). + * + * @param stream_id + * @return + */ + public static StreamSpecifier id(int stream_id) { + return new StreamSpecifier("i:" + stream_id); + } + + /** + * Matches all streams with the given metadata tag. + * + * @param key + * @return + */ + public static StreamSpecifier tag(String key) { + return new StreamSpecifier("m:" + checkValidKey(key)); + } + + /** + * Matches streams with the metadata tag key having the specified value. + * + * @param key + * @param value + * @return + */ + public static StreamSpecifier tag(String key, String value) { + checkValidKey(key); + checkNotNull(value); + return new StreamSpecifier("m:" + key + ":" + value); + } + + /** + * Matches streams with usable configuration, the codec must be defined and the essential + * information such as video dimension or audio sample rate must be present. + * + * @return + */ + public static StreamSpecifier usable() { + return new StreamSpecifier("u"); + } +} diff --git a/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifierType.java b/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifierType.java new file mode 100644 index 00000000..83b09fb9 --- /dev/null +++ b/src/main/java/net/bramp/ffmpeg/builder/StreamSpecifierType.java @@ -0,0 +1,43 @@ +package net.bramp.ffmpeg.builder; + +public enum StreamSpecifierType { + /** + * Video + */ + Video("v"), + + /** + * Video streams which are not attached pictures, video thumbnails or cover arts. + */ + PureVideo("V"), + + /** + * Audio + */ + Audio("a"), + + /** + * Subtitles + */ + Subtitle("s"), + + /** + * Data + */ + Data("d"), + + /** + * Attachment + */ + Attachment("t"); + + final String prefix; + + StreamSpecifierType(String prefix) { + this.prefix = prefix; + } + + public String toString() { + return prefix; + } +} diff --git a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java index edc268cc..b8186eaf 100644 --- a/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java +++ b/src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java @@ -1,7 +1,7 @@ package net.bramp.ffmpeg.builder; +import com.google.common.base.Joiner; import net.bramp.ffmpeg.FFmpeg; -import net.bramp.ffmpeg.builder.FFmpegBuilder; import net.bramp.ffmpeg.options.AudioEncodingOptions; import net.bramp.ffmpeg.options.EncodingOptions; import net.bramp.ffmpeg.options.MainEncodingOptions; @@ -14,6 +14,12 @@ import java.util.concurrent.TimeUnit; import static com.nitorcreations.Matchers.reflectEquals; +import static net.bramp.ffmpeg.builder.MetadataSpecifier.chapter; +import static net.bramp.ffmpeg.builder.MetadataSpecifier.program; +import static net.bramp.ffmpeg.builder.MetadataSpecifier.stream; +import static net.bramp.ffmpeg.builder.StreamSpecifier.tag; +import static net.bramp.ffmpeg.builder.StreamSpecifier.usable; +import static net.bramp.ffmpeg.builder.StreamSpecifierType.*; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -173,16 +179,45 @@ public void testMetaTags() { List args = new FFmpegBuilder() .setInput("input") .addOutput("output") - .disableAudio() - .disableSubtitle() .addMetaTag("comment", "My Comment") .addMetaTag("title", "\"Video\"") + .addMetaTag("author", "a=b:c") + .done() + .build(); + // @formatter:on + + assertThat(args, + is(Arrays.asList("-y", "-v", "error", "-i", "input", "-metadata", "comment=My Comment", + "-metadata", "title=\"Video\"", "-metadata", "author=a=b:c", "output"))); + } + + @Test + public void testMetaTagsWithSpecifier() { + // @formatter:off + List args = new FFmpegBuilder() + .setInput("input") + .addOutput("output") + .addMetaTag("title", "Movie Title") + .addMetaTag(chapter(0), "author", "Bob") + .addMetaTag(program(0), "comment", "Awesome") + .addMetaTag(stream(0), "copyright", "Megacorp") + .addMetaTag(stream(Video), "framerate", "24fps") + .addMetaTag(stream(Video, 0), "artist", "Joe") + .addMetaTag(stream(Audio, 0), "language", "eng") + .addMetaTag(stream(Subtitle, 0), "language", "fre") + .addMetaTag(stream(usable()), "year", "2010") + .addMetaTag(stream(tag("key")), "a", "b") + .addMetaTag(stream(tag("key", "value")), "a", "b") .done() .build(); // @formatter:on assertThat(args, is(Arrays.asList("-y", "-v", "error", "-i", "input", "-metadata", - "\"comment=My Comment\"", "-metadata", "\"title=Video\"", "-an", "-sn", "output"))); + "title=Movie Title", "-metadata:c:0", "author=Bob", "-metadata:p:0", "comment=Awesome", + "-metadata:s:0", "copyright=Megacorp", "-metadata:s:v", "framerate=24fps", + "-metadata:s:v:0", "artist=Joe", "-metadata:s:a:0", "language=eng", "-metadata:s:s:0", + "language=fre", "-metadata:s:u", "year=2010", "-metadata:s:m:key", "a=b", + "-metadata:s:m:key:value", "a=b", "output"))); } @Test diff --git a/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java b/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java index 5403fa58..2f7e19b3 100644 --- a/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java +++ b/src/test/java/net/bramp/ffmpeg/builder/MetadataSpecTest.java @@ -2,7 +2,8 @@ import org.junit.Test; -import static net.bramp.ffmpeg.builder.MetadataSpec.*; +import static net.bramp.ffmpeg.builder.MetadataSpecifier.*; +import static net.bramp.ffmpeg.builder.StreamSpecifier.id; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; @@ -10,9 +11,10 @@ public class MetadataSpecTest { @Test public void testMetaSpec() { - assertThat(global().toString(), is("g")); - assertThat(chapter(1).toString(), is("c:1")); - assertThat(program(1).toString(), is("p:1")); - assertThat(stream(1).toString(), is("s:1")); + assertThat(global().spec(), is("g")); + assertThat(chapter(1).spec(), is("c:1")); + assertThat(program(1).spec(), is("p:1")); + assertThat(stream(1).spec(), is("s:1")); + assertThat(stream(id(1)).spec(), is("s:i:1")); } } diff --git a/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java b/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java index c93328e7..8206c1f0 100644 --- a/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java +++ b/src/test/java/net/bramp/ffmpeg/builder/StreamSpecTest.java @@ -1,6 +1,9 @@ package net.bramp.ffmpeg.builder; -import static net.bramp.ffmpeg.builder.MetadataSpec.stream; +import org.junit.Test; + +import static net.bramp.ffmpeg.builder.StreamSpecifier.*; +import static net.bramp.ffmpeg.builder.StreamSpecifierType.*; import static org.hamcrest.core.Is.is; import static org.junit.Assert.*; @@ -8,21 +11,23 @@ public class StreamSpecTest { @Test public void testStreamSpec() { - assertThat(stream(1).toString(), is("1")); - assertThat(stream(VIDEO, 1).toString(), is("v:1")); + assertThat(stream(1).spec(), is("1")); + assertThat(stream(Video).spec(), is("v")); - assertThat(chapter(1).toString(), is("c:1")); - assertThat(program(1).toString(), is("p:1")); - } + assertThat(stream(Video, 1).spec(), is("v:1")); + assertThat(stream(PureVideo, 1).spec(), is("V:1")); + assertThat(stream(Audio, 1).spec(), is("a:1")); + assertThat(stream(Subtitle, 1).spec(), is("s:1")); + assertThat(stream(Data, 1).spec(), is("d:1")); + assertThat(stream(Attachment, 1).spec(), is("t:1")); - addMetaTag("key", "value", ); - addMetaTag("key", "value", chapter(1)); - addMetaTag("key", "value", program(1)); - addMetaTag("key", "value", stream(0)); - addMetaTag("key", "value", stream(VIDEO, 0); - addMetaTag("key", "value", stream(AUDIO, 0); - addMetaTag("key", "value", stream(id(0x502)); - addMetaTag("key", "value", stream(tag("key2", "value2"))); // This one is confusing, key2 refers to the output stream, and would apply key/value to any output stream already with key2/value2. - */ -} + assertThat(program(1).spec(), is("p:1")); + assertThat(program(1, 2).spec(), is("p:1:2")); + + assertThat(id(1).spec(), is("i:1")); + + assertThat(tag("key").spec(), is("m:key")); + assertThat(tag("key", "value").spec(), is("m:key:value")); + assertThat(usable().spec(), is("u")); + } }