use ffmpeg_common::{CommandBuilder, Result};
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct FormatOptions {
format: Option<String>,
options: HashMap<String, String>,
flags: Vec<String>,
}
impl FormatOptions {
pub fn new() -> Self {
Self::default()
}
pub fn format(mut self, format: impl Into<String>) -> Self {
self.format = Some(format.into());
self
}
pub fn option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options.insert(key.into(), value.into());
self
}
pub fn flag(mut self, flag: impl Into<String>) -> Self {
self.flags.push(flag.into());
self
}
pub fn build_args(&self) -> Vec<String> {
let mut cmd = CommandBuilder::new();
if let Some(ref format) = self.format {
cmd = cmd.option("-f", format);
}
for (key, value) in &self.options {
cmd = cmd.option(format!("-{}", key), value);
}
if !self.flags.is_empty() {
cmd = cmd.option("-flags", self.flags.join("+"));
}
cmd.build()
}
}
pub mod formats {
use super::*;
pub struct Mp4;
impl Mp4 {
pub fn standard() -> FormatOptions {
FormatOptions::new()
.format("mp4")
.option("movflags", "+faststart")
}
pub fn fragmented() -> FormatOptions {
FormatOptions::new()
.format("mp4")
.option("movflags", "frag_keyframe+empty_moov+default_base_moof")
.option("frag_duration", "1000000")
}
pub fn dash() -> FormatOptions {
FormatOptions::new()
.format("mp4")
.option("movflags", "dash+delay_moov")
.option("use_timeline", "1")
.option("use_template", "1")
}
pub fn progressive() -> FormatOptions {
FormatOptions::new()
.format("mp4")
.option("movflags", "+faststart+separate_moof+disable_chpl")
.option("brand", "mp42")
}
}
pub struct Mkv;
impl Mkv {
pub fn standard() -> FormatOptions {
FormatOptions::new()
.format("matroska")
}
pub fn streaming() -> FormatOptions {
FormatOptions::new()
.format("matroska")
.option("live", "1")
.option("cluster_time_limit", "2000")
}
}
pub struct WebM;
impl WebM {
pub fn standard() -> FormatOptions {
FormatOptions::new()
.format("webm")
}
pub fn dash() -> FormatOptions {
FormatOptions::new()
.format("webm")
.option("dash", "1")
.option("cluster_time_limit", "5000")
.option("cluster_size_limit", "5M")
}
pub fn live() -> FormatOptions {
FormatOptions::new()
.format("webm")
.option("live", "1")
.option("chunk_start_index", "1")
}
}
pub struct Hls;
impl Hls {
pub fn standard() -> FormatOptions {
FormatOptions::new()
.format("hls")
.option("hls_time", "10")
.option("hls_list_size", "0")
.option("hls_segment_type", "mpegts")
}
pub fn live() -> FormatOptions {
FormatOptions::new()
.format("hls")
.option("hls_time", "2")
.option("hls_list_size", "5")
.option("hls_flags", "delete_segments+append_list")
.option("hls_segment_type", "mpegts")
}
pub fn fmp4() -> FormatOptions {
FormatOptions::new()
.format("hls")
.option("hls_segment_type", "fmp4")
.option("hls_fmp4_init_filename", "init.mp4")
.option("hls_time", "10")
}
pub fn event() -> FormatOptions {
FormatOptions::new()
.format("hls")
.option("hls_playlist_type", "event")
.option("hls_time", "10")
.option("hls_list_size", "0")
}
}
pub struct Dash;
impl Dash {
pub fn standard() -> FormatOptions {
FormatOptions::new()
.format("dash")
.option("seg_duration", "4")
.option("use_timeline", "1")
.option("use_template", "1")
}
pub fn live() -> FormatOptions {
FormatOptions::new()
.format("dash")
.option("seg_duration", "2")
.option("use_timeline", "1")
.option("use_template", "1")
.option("streaming", "1")
.option("window_size", "5")
.option("extra_window_size", "2")
}
pub fn low_latency() -> FormatOptions {
FormatOptions::new()
.format("dash")
.option("seg_duration", "1")
.option("ldash", "1")
.option("streaming", "1")
.option("use_timeline", "0")
.option("frag_type", "duration")
.option("frag_duration", "1")
}
}
pub struct Rtmp;
impl Rtmp {
pub fn output() -> FormatOptions {
FormatOptions::new()
.format("flv")
.option("flvflags", "no_duration_filesize")
}
pub fn low_latency() -> FormatOptions {
FormatOptions::new()
.format("flv")
.option("flvflags", "no_duration_filesize+aac_seq_header_detect")
.option("rtmp_buffer", "100")
.option("rtmp_live", "live")
}
}
pub struct ImageSequence;
impl ImageSequence {
pub fn jpeg() -> FormatOptions {
FormatOptions::new()
.format("image2")
.option("update", "1")
}
pub fn png() -> FormatOptions {
FormatOptions::new()
.format("image2")
.option("update", "1")
}
pub fn gif() -> FormatOptions {
FormatOptions::new()
.format("gif")
.option("loop", "0")
}
}
pub struct Audio;
impl Audio {
pub fn mp3() -> FormatOptions {
FormatOptions::new()
.format("mp3")
.option("id3v2_version", "3")
}
pub fn aac() -> FormatOptions {
FormatOptions::new()
.format("adts")
}
pub fn flac() -> FormatOptions {
FormatOptions::new()
.format("flac")
}
pub fn ogg() -> FormatOptions {
FormatOptions::new()
.format("ogg")
.option("page_duration", "1000000")
}
pub fn wav() -> FormatOptions {
FormatOptions::new()
.format("wav")
.option("rf64", "auto")
}
}
pub struct Raw;
impl Raw {
pub fn video() -> FormatOptions {
FormatOptions::new()
.format("rawvideo")
}
pub fn audio_pcm() -> FormatOptions {
FormatOptions::new()
.format("s16le")
}
pub fn h264() -> FormatOptions {
FormatOptions::new()
.format("h264")
}
pub fn h265() -> FormatOptions {
FormatOptions::new()
.format("hevc")
}
}
pub struct Null;
impl Null {
pub fn output() -> FormatOptions {
FormatOptions::new()
.format("null")
}
}
}
pub struct MuxerOptions {
options: HashMap<String, String>,
}
impl MuxerOptions {
pub fn new() -> Self {
Self {
options: HashMap::new(),
}
}
pub fn option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.options.insert(key.into(), value.into());
self
}
pub fn build(self) -> FormatOptions {
let mut format_opts = FormatOptions::new();
for (key, value) in self.options {
format_opts = format_opts.option(key, value);
}
format_opts
}
}
impl Default for MuxerOptions {
fn default() -> Self {
Self::new()
}
}
pub mod muxer_configs {
use super::*;
pub fn fast_seeking() -> MuxerOptions {
MuxerOptions::new()
.option("movflags", "+faststart")
.option("keyint_min", "1")
.option("g", "30")
}
pub fn low_latency() -> MuxerOptions {
MuxerOptions::new()
.option("flush_packets", "1")
.option("flags", "+low_delay")
.option("fflags", "+nobuffer+flush_packets")
}
pub fn archival() -> MuxerOptions {
MuxerOptions::new()
.option("write_id3v2", "1")
.option("write_apetag", "0")
.option("metadata_header_padding", "1024")
}
pub fn web_compatible() -> MuxerOptions {
MuxerOptions::new()
.option("brand", "mp42")
.option("movflags", "+faststart+separate_moof")
.option("min_frag_duration", "1000000")
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::formats::*;
#[test]
fn test_format_options() {
let opts = FormatOptions::new()
.format("mp4")
.option("movflags", "+faststart")
.flag("low_delay");
let args = opts.build_args();
assert!(args.contains(&"-f".to_string()));
assert!(args.contains(&"mp4".to_string()));
assert!(args.contains(&"-movflags".to_string()));
assert!(args.contains(&"+faststart".to_string()));
}
#[test]
fn test_mp4_formats() {
let standard = Mp4::standard();
let args = standard.build_args();
assert!(args.contains(&"mp4".to_string()));
let fragmented = Mp4::fragmented();
let args = fragmented.build_args();
assert!(args.iter().any(|arg| arg.contains("frag_keyframe")));
}
#[test]
fn test_hls_formats() {
let standard = Hls::standard();
let args = standard.build_args();
assert!(args.contains(&"hls".to_string()));
assert!(args.contains(&"-hls_time".to_string()));
let live = Hls::live();
let args = live.build_args();
assert!(args.contains(&"2".to_string()));
}
#[test]
fn test_audio_formats() {
let mp3 = Audio::mp3();
let args = mp3.build_args();
assert!(args.contains(&"mp3".to_string()));
let flac = Audio::flac();
let args = flac.build_args();
assert!(args.contains(&"flac".to_string()));
}
}