use ffmpeg_common::{CommandBuilder, Duration, StreamSpecifier};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncType {
Audio,
Video,
External,
}
impl SyncType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Audio => "audio",
Self::Video => "video",
Self::External => "ext",
}
}
}
#[derive(Debug, Clone)]
pub struct PlaybackOptions {
no_audio: bool,
no_video: bool,
no_subtitles: bool,
start_position: Option<Duration>,
duration: Option<Duration>,
loop_count: Option<i32>,
volume: Option<u8>,
format: Option<String>,
seek_by_bytes: bool,
seek_interval: Option<f64>,
fast: bool,
genpts: bool,
sync_type: Option<SyncType>,
audio_stream: Option<StreamSpecifier>,
video_stream: Option<StreamSpecifier>,
subtitle_stream: Option<StreamSpecifier>,
autoexit: bool,
exitonkeydown: bool,
exitonmousedown: bool,
video_codec: Option<String>,
audio_codec: Option<String>,
subtitle_codec: Option<String>,
autorotate: bool,
framedrop: bool,
infbuf: bool,
video_filters: Option<String>,
audio_filters: Option<String>,
stats: bool,
filter_threads: Option<u32>,
}
impl Default for PlaybackOptions {
fn default() -> Self {
Self {
no_audio: false,
no_video: false,
no_subtitles: false,
start_position: None,
duration: None,
loop_count: None,
volume: None,
format: None,
seek_by_bytes: false,
seek_interval: None,
fast: false,
genpts: false,
sync_type: None,
audio_stream: None,
video_stream: None,
subtitle_stream: None,
autoexit: false,
exitonkeydown: false,
exitonmousedown: false,
video_codec: None,
audio_codec: None,
subtitle_codec: None,
autorotate: true,
framedrop: true,
infbuf: false,
video_filters: None,
audio_filters: None,
stats: true,
filter_threads: None,
}
}
}
impl PlaybackOptions {
pub fn new() -> Self {
Self::default()
}
pub fn no_audio(mut self, enable: bool) -> Self {
self.no_audio = enable;
self
}
pub fn no_video(mut self, enable: bool) -> Self {
self.no_video = enable;
self
}
pub fn no_subtitles(mut self, enable: bool) -> Self {
self.no_subtitles = enable;
self
}
pub fn seek(mut self, position: Duration) -> Self {
self.start_position = Some(position);
self
}
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
pub fn loop_count(mut self, count: i32) -> Self {
self.loop_count = Some(count);
self
}
pub fn volume(mut self, volume: u8) -> Self {
self.volume = Some(volume.min(100));
self
}
pub fn format(mut self, format: impl Into<String>) -> Self {
self.format = Some(format.into());
self
}
pub fn seek_by_bytes(mut self, enable: bool) -> Self {
self.seek_by_bytes = enable;
self
}
pub fn seek_interval(mut self, seconds: f64) -> Self {
self.seek_interval = Some(seconds);
self
}
pub fn fast(mut self, enable: bool) -> Self {
self.fast = enable;
self
}
pub fn genpts(mut self, enable: bool) -> Self {
self.genpts = enable;
self
}
pub fn sync(mut self, sync_type: SyncType) -> Self {
self.sync_type = Some(sync_type);
self
}
pub fn audio_stream(mut self, spec: StreamSpecifier) -> Self {
self.audio_stream = Some(spec);
self
}
pub fn video_stream(mut self, spec: StreamSpecifier) -> Self {
self.video_stream = Some(spec);
self
}
pub fn subtitle_stream(mut self, spec: StreamSpecifier) -> Self {
self.subtitle_stream = Some(spec);
self
}
pub fn autoexit(mut self, enable: bool) -> Self {
self.autoexit = enable;
self
}
pub fn exitonkeydown(mut self, enable: bool) -> Self {
self.exitonkeydown = enable;
self
}
pub fn exitonmousedown(mut self, enable: bool) -> Self {
self.exitonmousedown = enable;
self
}
pub fn video_codec(mut self, codec: impl Into<String>) -> Self {
self.video_codec = Some(codec.into());
self
}
pub fn audio_codec(mut self, codec: impl Into<String>) -> Self {
self.audio_codec = Some(codec.into());
self
}
pub fn subtitle_codec(mut self, codec: impl Into<String>) -> Self {
self.subtitle_codec = Some(codec.into());
self
}
pub fn autorotate(mut self, enable: bool) -> Self {
self.autorotate = enable;
self
}
pub fn framedrop(mut self, enable: bool) -> Self {
self.framedrop = enable;
self
}
pub fn infbuf(mut self, enable: bool) -> Self {
self.infbuf = enable;
self
}
pub fn video_filter(mut self, filter: impl Into<String>) -> Self {
self.video_filters = Some(filter.into());
self
}
pub fn audio_filter(mut self, filter: impl Into<String>) -> Self {
self.audio_filters = Some(filter.into());
self
}
pub fn stats(mut self, enable: bool) -> Self {
self.stats = enable;
self
}
pub fn filter_threads(mut self, threads: u32) -> Self {
self.filter_threads = Some(threads);
self
}
pub fn build_args(&self) -> Vec<String> {
let mut cmd = CommandBuilder::new();
if self.no_audio {
cmd = cmd.flag("-an");
}
if self.no_video {
cmd = cmd.flag("-vn");
}
if self.no_subtitles {
cmd = cmd.flag("-sn");
}
if let Some(ref pos) = self.start_position {
cmd = cmd.option("-ss", pos.to_ffmpeg_format());
}
if let Some(ref dur) = self.duration {
cmd = cmd.option("-t", dur.to_ffmpeg_format());
}
if let Some(count) = self.loop_count {
cmd = cmd.option("-loop", count);
}
if let Some(vol) = self.volume {
cmd = cmd.option("-volume", vol);
}
if let Some(ref fmt) = self.format {
cmd = cmd.option("-f", fmt);
}
if self.seek_by_bytes {
cmd = cmd.flag("-bytes");
}
if let Some(interval) = self.seek_interval {
cmd = cmd.option("-seek_interval", interval);
}
if self.fast {
cmd = cmd.flag("-fast");
}
if self.genpts {
cmd = cmd.flag("-genpts");
}
if let Some(ref sync) = self.sync_type {
cmd = cmd.option("-sync", sync.as_str());
}
if let Some(ref spec) = self.audio_stream {
cmd = cmd.option("-ast", spec.to_string());
}
if let Some(ref spec) = self.video_stream {
cmd = cmd.option("-vst", spec.to_string());
}
if let Some(ref spec) = self.subtitle_stream {
cmd = cmd.option("-sst", spec.to_string());
}
if self.autoexit {
cmd = cmd.flag("-autoexit");
}
if self.exitonkeydown {
cmd = cmd.flag("-exitonkeydown");
}
if self.exitonmousedown {
cmd = cmd.flag("-exitonmousedown");
}
if let Some(ref codec) = self.video_codec {
cmd = cmd.option("-vcodec", codec);
}
if let Some(ref codec) = self.audio_codec {
cmd = cmd.option("-acodec", codec);
}
if let Some(ref codec) = self.subtitle_codec {
cmd = cmd.option("-scodec", codec);
}
if !self.autorotate {
cmd = cmd.flag("-noautorotate");
}
if !self.framedrop {
cmd = cmd.flag("-noframedrop");
}
if self.infbuf {
cmd = cmd.flag("-infbuf");
}
if let Some(ref filter) = self.video_filters {
cmd = cmd.option("-vf", filter);
}
if let Some(ref filter) = self.audio_filters {
cmd = cmd.option("-af", filter);
}
if !self.stats {
cmd = cmd.flag("-nostats");
}
if let Some(threads) = self.filter_threads {
cmd = cmd.option("-filter_threads", threads);
}
cmd.build()
}
}
pub mod presets {
use super::*;
use ffmpeg_common::StreamType;
pub fn default() -> PlaybackOptions {
PlaybackOptions::default()
}
pub fn audio_only() -> PlaybackOptions {
PlaybackOptions::new()
.no_video(true)
.no_subtitles(true)
}
pub fn video_only() -> PlaybackOptions {
PlaybackOptions::new()
.no_audio(true)
.no_subtitles(true)
}
pub fn low_latency() -> PlaybackOptions {
PlaybackOptions::new()
.fast(true)
.sync(SyncType::External)
.infbuf(true)
.framedrop(true)
}
pub fn preview() -> PlaybackOptions {
PlaybackOptions::new()
.duration(Duration::from_secs(30))
.autoexit(true)
.stats(false)
}
pub fn interactive() -> PlaybackOptions {
PlaybackOptions::new()
.exitonkeydown(false)
.exitonmousedown(false)
.stats(true)
}
pub fn benchmark() -> PlaybackOptions {
PlaybackOptions::new()
.video_codec("rawvideo")
.audio_codec("pcm_s16le")
.fast(true)
.autoexit(true)
}
pub fn with_language(lang: &str) -> PlaybackOptions {
PlaybackOptions::new()
.audio_stream(StreamSpecifier::Metadata {
key: "language".to_string(),
value: Some(lang.to_string()),
})
.subtitle_stream(StreamSpecifier::Metadata {
key: "language".to_string(),
value: Some(lang.to_string()),
})
}
pub fn loop_forever() -> PlaybackOptions {
PlaybackOptions::new()
.loop_count(-1)
}
pub fn muted() -> PlaybackOptions {
PlaybackOptions::new()
.volume(0)
}
pub fn test_pattern() -> PlaybackOptions {
PlaybackOptions::new()
.format("lavfi")
.video_filter("testsrc2=size=1280x720:rate=30")
.audio_filter("sine=frequency=1000:sample_rate=48000")
}
}
#[cfg(test)]
mod tests {
use ffmpeg_common::StreamType;
use super::*;
#[test]
fn test_playback_options() {
let opts = PlaybackOptions::new()
.seek(Duration::from_secs(10))
.duration(Duration::from_secs(30))
.volume(75)
.loop_count(2);
let args = opts.build_args();
assert!(args.contains(&"-ss".to_string()));
assert!(args.contains(&"00:00:10".to_string()));
assert!(args.contains(&"-t".to_string()));
assert!(args.contains(&"00:00:30".to_string()));
assert!(args.contains(&"-volume".to_string()));
assert!(args.contains(&"75".to_string()));
assert!(args.contains(&"-loop".to_string()));
assert!(args.contains(&"2".to_string()));
}
#[test]
fn test_stream_selection() {
let opts = PlaybackOptions::new()
.audio_stream(StreamSpecifier::Index(1))
.video_stream(StreamSpecifier::Type(StreamType::Video))
.subtitle_stream(StreamSpecifier::Type(StreamType::Subtitle));
let args = opts.build_args();
assert!(args.contains(&"-ast".to_string()));
assert!(args.contains(&"1".to_string()));
assert!(args.contains(&"-vst".to_string()));
assert!(args.contains(&"v".to_string()));
assert!(args.contains(&"-sst".to_string()));
assert!(args.contains(&"s".to_string()));
}
#[test]
fn test_codecs() {
let opts = PlaybackOptions::new()
.video_codec("h264")
.audio_codec("aac");
let args = opts.build_args();
assert!(args.contains(&"-vcodec".to_string()));
assert!(args.contains(&"h264".to_string()));
assert!(args.contains(&"-acodec".to_string()));
assert!(args.contains(&"aac".to_string()));
}
#[test]
fn test_presets() {
let audio_only = presets::audio_only();
let args = audio_only.build_args();
assert!(args.contains(&"-vn".to_string()));
let low_latency = presets::low_latency();
let args = low_latency.build_args();
assert!(args.contains(&"-fast".to_string()));
assert!(args.contains(&"-sync".to_string()));
assert!(args.contains(&"ext".to_string()));
let preview = presets::preview();
let args = preview.build_args();
assert!(args.contains(&"-t".to_string()));
assert!(args.contains(&"-autoexit".to_string()));
}
}