Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions src/main/java/net/bramp/ffmpeg/builder/FFmpegOutputBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -303,9 +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;
}

/**
* Add metadata on output streams. Which keys are possible depends on the used codec.
*
* <pre>
* {@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
* }
* </pre>
*
* 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;
}

Expand Down Expand Up @@ -518,7 +558,7 @@ protected List<String> build(int pass) {
}

for (String meta : meta_tags) {
args.add("-metadata").add("\"" + meta + "\"");
args.add(meta);
}

if (video_enabled) {
Expand Down
76 changes: 76 additions & 0 deletions src/main/java/net/bramp/ffmpeg/builder/MetadataSpecifier.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
118 changes: 118 additions & 0 deletions src/main/java/net/bramp/ffmpeg/builder/StreamSpecifier.java
Original file line number Diff line number Diff line change
@@ -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");
}
}
43 changes: 43 additions & 0 deletions src/main/java/net/bramp/ffmpeg/builder/StreamSpecifierType.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
43 changes: 39 additions & 4 deletions src/test/java/net/bramp/ffmpeg/builder/FFmpegBuilderTest.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -173,16 +179,45 @@ public void testMetaTags() {
List<String> 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<String> 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
Expand Down
Loading