com.itshidu.ffmpeg.builder.AbstractFFmpegStreamBuilder Maven / Gradle / Ivy
Show all versions of ffmpeg-java Show documentation
package com.itshidu.ffmpeg.builder;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.itshidu.ffmpeg.FFmpegUtils.toTimecode;
import static com.itshidu.ffmpeg.Preconditions.checkNotEmpty;
import static com.itshidu.ffmpeg.Preconditions.checkValidStream;
import static com.itshidu.ffmpeg.builder.MetadataSpecifier.checkValidKey;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.itshidu.ffmpeg.modelmapper.Mapper;
import com.itshidu.ffmpeg.options.AudioEncodingOptions;
import com.itshidu.ffmpeg.options.EncodingOptions;
import com.itshidu.ffmpeg.options.MainEncodingOptions;
import com.itshidu.ffmpeg.options.VideoEncodingOptions;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.math.Fraction;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* This abstract class holds flags that are both applicable to input and output streams in the
* ffmpeg command, while flags that apply to a particular direction (input/output) are located in
* {@link FFmpegOutputBuilder}.
*
* All possible flags can be found in the official
* ffmpeg page The discrimination criteria for flag location are the specifiers for each command
*
*
* - AbstractFFmpegStreamBuilder
*
* - (input/output):
-t duration (input/output)
* - (input/output,per-stream):
* -codec[:stream_specifier] codec (input/output,per-stream)
* - (global):
-filter_threads nb_threads (global)
*
*
* - FFmpegInputBuilder
*
* - (input):
-muxdelay seconds (input)
* - (input,per-stream):
-guess_layout_max channels (input,per-stream)
*
*
* - FFmpegOutputBuilder
*
* - (output):
-atag fourcc/tag (output)
* - (output,per-stream):
* -bsf[:stream_specifier] bitstream_filters (output,per-stream)
*
*
*
*
* @param A concrete class that extends from the AbstractFFmpegStreamBuilder
*/
public abstract class AbstractFFmpegStreamBuilder> {
private static final String DEVNULL = SystemUtils.IS_OS_WINDOWS ? "NUL" : "/dev/null";
final FFmpegBuilder parent;
/** Output filename or uri. Only one may be set */
public String filename;
public URI uri;
public String format;
public Long startOffset; // in milliseconds
public Long duration; // in milliseconds
public final List meta_tags = new ArrayList<>();
public boolean audio_enabled = true;
public String audio_codec;
public int audio_channels;
public int audio_sample_rate;
public String audio_preset;
public boolean video_enabled = true;
public String video_codec;
public boolean video_copyinkf;
public Fraction video_frame_rate;
public int video_width;
public int video_height;
public String video_size;
public String video_movflags;
public Integer video_frames;
public String video_pixel_format;
public boolean subtitle_enabled = true;
public String subtitle_preset;
public String preset;
public String presetFilename;
public final List extra_args = new ArrayList<>();
public FFmpegBuilder.Strict strict = FFmpegBuilder.Strict.NORMAL;
public long targetSize = 0; // in bytes
public long pass_padding_bitrate = 1024; // in bits per second
public boolean throwWarnings = true; // TODO Either delete this, or apply it consistently
protected AbstractFFmpegStreamBuilder() {
this.parent = null;
}
protected AbstractFFmpegStreamBuilder(FFmpegBuilder parent, String filename) {
this.parent = checkNotNull(parent);
this.filename = checkNotEmpty(filename, "filename must not be empty");
}
protected AbstractFFmpegStreamBuilder(FFmpegBuilder parent, URI uri) {
this.parent = checkNotNull(parent);
this.uri = checkValidStream(uri);
}
protected abstract T getThis();
public T useOptions(EncodingOptions opts) {
Mapper.map(opts, this);
return getThis();
}
public T useOptions(MainEncodingOptions opts) {
Mapper.map(opts, this);
return getThis();
}
public T useOptions(AudioEncodingOptions opts) {
Mapper.map(opts, this);
return getThis();
}
public T useOptions(VideoEncodingOptions opts) {
Mapper.map(opts, this);
return getThis();
}
public T disableVideo() {
this.video_enabled = false;
return getThis();
}
public T disableAudio() {
this.audio_enabled = false;
return getThis();
}
public T disableSubtitle() {
this.subtitle_enabled = false;
return getThis();
}
/**
* Sets a file to use containing presets.
*
* Uses `-fpre`.
*
* @param presetFilename the preset by filename
* @return this
*/
public T setPresetFilename(String presetFilename) {
this.presetFilename = checkNotEmpty(presetFilename, "file preset must not be empty");
return getThis();
}
/**
* Sets a preset by name (this only works with some codecs).
*
*
Uses `-preset`.
*
* @param preset the preset
* @return this
*/
public T setPreset(String preset) {
this.preset = checkNotEmpty(preset, "preset must not be empty");
return getThis();
}
public T setFilename(String filename) {
this.filename = checkNotEmpty(filename, "filename must not be empty");
return getThis();
}
public String getFilename() {
return filename;
}
public T setUri(URI uri) {
this.uri = checkValidStream(uri);
return getThis();
}
public URI getUri() {
return uri;
}
public T setFormat(String format) {
this.format = checkNotEmpty(format, "format must not be empty");
return getThis();
}
public T setVideoCodec(String codec) {
this.video_enabled = true;
this.video_codec = checkNotEmpty(codec, "codec must not be empty");
return getThis();
}
public T setVideoCopyInkf(boolean copyinkf) {
this.video_enabled = true;
this.video_copyinkf = copyinkf;
return getThis();
}
public T setVideoMovFlags(String movflags) {
this.video_enabled = true;
this.video_movflags = checkNotEmpty(movflags, "movflags must not be empty");
return getThis();
}
/**
* Sets the video's frame rate
*
* @param frame_rate Frames per second
* @return this
* @see com.itshidu.ffmpeg.FFmpeg#FPS_30
* @see com.itshidu.ffmpeg.FFmpeg#FPS_29_97
* @see com.itshidu.ffmpeg.FFmpeg#FPS_24
* @see com.itshidu.ffmpeg.FFmpeg#FPS_23_976
*/
public T setVideoFrameRate(Fraction frame_rate) {
this.video_enabled = true;
this.video_frame_rate = checkNotNull(frame_rate);
return getThis();
}
/**
* Set the video frame rate in terms of frames per interval. For example 24fps would be 24/1,
* however NTSC TV at 23.976fps would be 24000 per 1001.
*
* @param frames The number of frames within the given seconds
* @param per The number of seconds
* @return this
*/
public T setVideoFrameRate(int frames, int per) {
return setVideoFrameRate(Fraction.getFraction(frames, per));
}
public T setVideoFrameRate(double frame_rate) {
return setVideoFrameRate(Fraction.getFraction(frame_rate));
}
/**
* Set the number of video frames to record.
*
* @param frames The number of frames
* @return this
*/
public T setFrames(int frames) {
this.video_enabled = true;
this.video_frames = frames;
return getThis();
}
protected static boolean isValidSize(int widthOrHeight) {
return widthOrHeight > 0 || widthOrHeight == -1;
}
public T setVideoWidth(int width) {
checkArgument(isValidSize(width), "Width must be -1 or greater than zero");
this.video_enabled = true;
this.video_width = width;
return getThis();
}
public T setVideoHeight(int height) {
checkArgument(isValidSize(height), "Height must be -1 or greater than zero");
this.video_enabled = true;
this.video_height = height;
return getThis();
}
public T setVideoResolution(int width, int height) {
checkArgument(
isValidSize(width) && isValidSize(height),
"Both width and height must be -1 or greater than zero");
this.video_enabled = true;
this.video_width = width;
this.video_height = height;
return getThis();
}
/**
* Sets video resolution based on an abbreviation, e.g. "ntsc" for 720x480, or "vga" for 640x480
*
* @see ffmpeg video size
* @param abbreviation The abbreviation size. No validation is done, instead the value is passed
* as is to ffmpeg.
* @return this
*/
public T setVideoResolution(String abbreviation) {
this.video_enabled = true;
this.video_size = checkNotEmpty(abbreviation, "video abbreviation must not be empty");
return getThis();
}
public T setVideoPixelFormat(String format) {
this.video_enabled = true;
this.video_pixel_format = checkNotEmpty(format, "format must not be empty");
return getThis();
}
/**
* Add metadata on output streams. Which keys are possible depends on the used codec.
*
* @param key Metadata key, e.g. "comment"
* @param value Value to set for key
* @return this
*/
public T addMetaTag(String key, String value) {
checkValidKey(key);
checkNotEmpty(value, "value must not be empty");
meta_tags.add("-metadata");
meta_tags.add(key + "=" + value);
return getThis();
}
/**
* 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
* }
*
* @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 T addMetaTag(MetadataSpecifier spec, String key, String value) {
checkValidKey(key);
checkNotEmpty(value, "value must not be empty");
meta_tags.add("-metadata:" + spec.spec());
meta_tags.add(key + "=" + value);
return getThis();
}
public T setAudioCodec(String codec) {
this.audio_enabled = true;
this.audio_codec = checkNotEmpty(codec, "codec must not be empty");
return getThis();
}
/**
* Sets the number of audio channels
*
* @param channels Number of channels
* @return this
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_MONO
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_STEREO
*/
public T setAudioChannels(int channels) {
checkArgument(channels > 0, "channels must be positive");
this.audio_enabled = true;
this.audio_channels = channels;
return getThis();
}
/**
* Sets the Audio sample rate, for example 44_000.
*
* @param sample_rate Samples measured in Hz
* @return this
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_8000
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_11025
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_12000
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_16000
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_22050
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_32000
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_44100
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_48000
* @see com.itshidu.ffmpeg.FFmpeg#AUDIO_SAMPLE_96000
*/
public T setAudioSampleRate(int sample_rate) {
checkArgument(sample_rate > 0, "sample rate must be positive");
this.audio_enabled = true;
this.audio_sample_rate = sample_rate;
return getThis();
}
/**
* Target output file size (in bytes)
*
* @param targetSize The target size in bytes
* @return this
*/
public T setTargetSize(long targetSize) {
checkArgument(targetSize > 0, "target size must be positive");
this.targetSize = targetSize;
return getThis();
}
/**
* Decodes but discards input until the offset.
*
* @param offset The offset
* @param units The units the offset is in
* @return this
*/
public T setStartOffset(long offset, TimeUnit units) {
checkNotNull(units);
this.startOffset = units.toMillis(offset);
return getThis();
}
/**
* Stop writing the output after duration is reached.
*
* @param duration The duration
* @param units The units the duration is in
* @return this
*/
public T setDuration(long duration, TimeUnit units) {
checkNotNull(units);
this.duration = units.toMillis(duration);
return getThis();
}
public T setStrict(FFmpegBuilder.Strict strict) {
this.strict = checkNotNull(strict);
return getThis();
}
/**
* When doing multi-pass we add a little extra padding, to ensure we reach our target
*
* @param bitrate bit rate
* @return this
*/
public T setPassPaddingBitrate(long bitrate) {
checkArgument(bitrate > 0, "bitrate must be positive");
this.pass_padding_bitrate = bitrate;
return getThis();
}
/**
* Sets a audio preset to use.
*
* Uses `-apre`.
*
* @param preset the preset
* @return this
*/
public T setAudioPreset(String preset) {
this.audio_enabled = true;
this.audio_preset = checkNotEmpty(preset, "audio preset must not be empty");
return getThis();
}
/**
* Sets a subtitle preset to use.
*
*
Uses `-spre`.
*
* @param preset the preset
* @return this
*/
public T setSubtitlePreset(String preset) {
this.subtitle_enabled = true;
this.subtitle_preset = checkNotEmpty(preset, "subtitle preset must not be empty");
return getThis();
}
/**
* Add additional output arguments (for flags which aren't currently supported).
*
* @param values The extra arguments
* @return this
*/
public T addExtraArgs(String... values) {
checkArgument(values.length > 0, "one or more values must be supplied");
checkNotEmpty(values[0], "first extra arg may not be empty");
for (String value : values) {
extra_args.add(checkNotNull(value));
}
return getThis();
}
/**
* Finished with this output
*
* @return the parent FFmpegBuilder
*/
public FFmpegBuilder done() {
Preconditions.checkState(parent != null, "Can not call done without parent being set");
return parent;
}
/**
* Returns a representation of this Builder that can be safely serialised.
*
*
NOTE: This method is horribly out of date, and its use should be rethought.
*
* @return A new EncodingOptions capturing this Builder's state
*/
public abstract EncodingOptions buildOptions();
protected List build(int pass) {
Preconditions.checkState(parent != null, "Can not build without parent being set");
return build(parent, pass);
}
/**
* Builds the arguments
*
* @param parent The parent FFmpegBuilder
* @param pass The particular pass. For one-pass this value will be zero, for multi-pass, it will
* be 1 for the first pass, 2 for the second, and so on.
* @return The arguments
*/
protected List build(FFmpegBuilder parent, int pass) {
checkNotNull(parent);
if (pass > 0) {
// TODO Write a test for this:
checkArgument(format != null, "Format must be specified when using two-pass");
}
ImmutableList.Builder args = new ImmutableList.Builder<>();
addGlobalFlags(parent, args);
if (video_enabled) {
addVideoFlags(parent, args);
} else {
args.add("-vn");
}
if (audio_enabled && pass != 1) {
addAudioFlags(args);
} else {
args.add("-an");
}
if (subtitle_enabled) {
if (!Strings.isNullOrEmpty(subtitle_preset)) {
args.add("-spre", subtitle_preset);
}
} else {
args.add("-sn");
}
args.addAll(extra_args);
if (filename != null && uri != null) {
throw new IllegalStateException("Only one of filename and uri can be set");
}
// 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);
}
return args.build();
}
protected void addGlobalFlags(FFmpegBuilder parent, ImmutableList.Builder args) {
if (strict != FFmpegBuilder.Strict.NORMAL) {
args.add("-strict", strict.toString());
}
if (!Strings.isNullOrEmpty(format)) {
args.add("-f", format);
}
if (!Strings.isNullOrEmpty(preset)) {
args.add("-preset", preset);
}
if (!Strings.isNullOrEmpty(presetFilename)) {
args.add("-fpre", presetFilename);
}
if (startOffset != null) {
args.add("-ss", toTimecode(startOffset, TimeUnit.MILLISECONDS));
}
if (duration != null) {
args.add("-t", toTimecode(duration, TimeUnit.MILLISECONDS));
}
args.addAll(meta_tags);
}
protected void addAudioFlags(ImmutableList.Builder args) {
if (!Strings.isNullOrEmpty(audio_codec)) {
args.add("-acodec", audio_codec);
}
if (audio_channels > 0) {
args.add("-ac", String.valueOf(audio_channels));
}
if (audio_sample_rate > 0) {
args.add("-ar", String.valueOf(audio_sample_rate));
}
if (!Strings.isNullOrEmpty(audio_preset)) {
args.add("-apre", audio_preset);
}
}
protected void addVideoFlags(FFmpegBuilder parent, ImmutableList.Builder args) {
if (video_frames != null) {
args.add("-vframes", video_frames.toString());
}
if (!Strings.isNullOrEmpty(video_codec)) {
args.add("-vcodec", video_codec);
}
if (!Strings.isNullOrEmpty(video_pixel_format)) {
args.add("-pix_fmt", video_pixel_format);
}
if (video_copyinkf) {
args.add("-copyinkf");
}
if (!Strings.isNullOrEmpty(video_movflags)) {
args.add("-movflags", video_movflags);
}
if (video_size != null) {
checkArgument(
video_width == 0 && video_height == 0,
"Can not specific width or height, as well as an abbreviatied video size");
args.add("-s", video_size);
} else if (video_width != 0 && video_height != 0) {
args.add("-s", String.format("%dx%d", video_width, video_height));
}
// TODO What if width is set but heigh isn't. We don't seem to do anything
if (video_frame_rate != null) {
args.add("-r", video_frame_rate.toString());
}
}
}