use crate::core::context::ffmpeg_context::FfmpegContext;
use crate::core::context::input::Input;
use crate::core::context::output::Output;
use super::ir::{CliIr, DurationKind, Noop};
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct LoweredInput {
pub(crate) url: String,
pub(crate) format: Option<String>,
pub(crate) start_time_us: Option<i64>,
pub(crate) recording_time_us: Option<i64>,
pub(crate) stop_time_us: Option<i64>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct LoweredOutput {
pub(crate) url: String,
pub(crate) format: Option<String>,
pub(crate) start_time_us: Option<i64>,
pub(crate) recording_time_us: Option<i64>,
pub(crate) stop_time_us: Option<i64>,
pub(crate) video_disable: bool,
pub(crate) audio_disable: bool,
pub(crate) video_codec: Option<String>,
pub(crate) audio_codec: Option<String>,
pub(crate) video_bitrate: Option<String>,
pub(crate) audio_bitrate: Option<String>,
pub(crate) video_codec_opts: Vec<(String, String)>,
pub(crate) format_opts: Vec<(String, String)>,
pub(crate) pix_fmt: Option<String>,
pub(crate) audio_sample_rate: Option<i32>,
pub(crate) audio_channels: Option<i32>,
pub(crate) max_video_frames: Option<i64>,
pub(crate) video_filter: Option<String>,
pub(crate) stream_maps: Vec<(String, bool)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct LoweredJob {
pub(crate) input: LoweredInput,
pub(crate) output: LoweredOutput,
pub(crate) noops: Vec<Noop>,
}
pub(crate) fn lower(ir: &CliIr) -> LoweredJob {
let mut input = LoweredInput {
url: ir.input.url.clone(),
format: ir.input.format.clone(),
start_time_us: ir.input.start_time_us,
recording_time_us: None,
stop_time_us: None,
};
match ir.input.duration {
Some(d) if d.kind == DurationKind::Duration => input.recording_time_us = Some(d.us),
Some(d) => input.stop_time_us = Some(d.us),
None => {}
}
let out = &ir.output;
let mut output = LoweredOutput {
url: out.url.clone(),
format: out.format.clone(),
start_time_us: out.start_time_us,
recording_time_us: None,
stop_time_us: None,
video_disable: out.video_disabled,
audio_disable: out.audio_disabled,
video_codec: match (&out.video_codec, out.video_copy) {
(_, true) => Some("copy".to_string()),
(codec, false) => codec.clone(),
},
audio_codec: match (&out.audio_codec, out.audio_copy) {
(_, true) => Some("copy".to_string()),
(codec, false) => codec.clone(),
},
video_bitrate: out.video_bitrate.clone(),
audio_bitrate: out.audio_bitrate.clone(),
video_codec_opts: Vec::new(),
format_opts: Vec::new(),
pix_fmt: out.pix_fmt.clone(),
audio_sample_rate: out.audio_sample_rate,
audio_channels: out.audio_channels,
max_video_frames: out.frames_v,
video_filter: out.video_filter.clone(),
stream_maps: Vec::new(),
};
match out.duration {
Some(d) if d.kind == DurationKind::Duration => output.recording_time_us = Some(d.us),
Some(d) => output.stop_time_us = Some(d.us),
None => {}
}
if let Some(crf) = &out.crf {
output
.video_codec_opts
.push(("crf".to_string(), crf.clone()));
}
if let Some(preset) = &out.preset {
output
.video_codec_opts
.push(("preset".to_string(), preset.clone()));
}
if out.movflags_faststart {
output
.format_opts
.push(("movflags".to_string(), "+faststart".to_string()));
}
for (key, value) in [
("hls_time", &out.hls_time),
("hls_playlist_type", &out.hls_playlist_type),
("hls_list_size", &out.hls_list_size),
("hls_segment_filename", &out.hls_segment_filename),
] {
if let Some(value) = value {
output.format_opts.push((key.to_string(), value.clone()));
}
}
for map in &out.maps {
let copy =
(map_selects(map, 'v') && out.video_copy) || (map_selects(map, 'a') && out.audio_copy);
output.stream_maps.push((map.clone(), copy));
}
LoweredJob {
input,
output,
noops: ir.globals.noops.clone(),
}
}
pub(crate) fn map_selects(map: &str, media: char) -> bool {
map.split(':').nth(1).and_then(|s| s.chars().next()) == Some(media)
}
impl LoweredJob {
pub(crate) fn into_context(self) -> crate::error::Result<FfmpegContext> {
let mut input = Input::from(self.input.url);
input.strict_avoptions = true;
if let Some(format) = self.input.format {
input = input.set_format(format);
}
if let Some(us) = self.input.start_time_us {
input = input.set_start_time_us(us);
}
if let Some(us) = self.input.recording_time_us {
input = input.set_recording_time_us(us);
}
if let Some(us) = self.input.stop_time_us {
input = input.set_stop_time_us(us);
}
let mut output = Output::from(self.output.url);
output.strict_avoptions = true;
output.require_unique_video_source = self.output.video_filter.is_some();
if let Some(format) = self.output.format {
output = output.set_format(format);
}
if let Some(us) = self.output.start_time_us {
output = output.set_start_time_us(us);
}
if let Some(us) = self.output.recording_time_us {
output = output.set_recording_time_us(us);
}
if let Some(us) = self.output.stop_time_us {
output = output.set_stop_time_us(us);
}
if self.output.video_disable {
output = output.disable_video();
}
if self.output.audio_disable {
output = output.disable_audio();
}
if let Some(codec) = self.output.video_codec {
output = output.set_video_codec(codec);
}
if let Some(codec) = self.output.audio_codec {
output = output.set_audio_codec(codec);
}
if let Some(bitrate) = self.output.video_bitrate {
output = output.set_video_bitrate(bitrate);
}
if let Some(bitrate) = self.output.audio_bitrate {
output = output.set_audio_bitrate(bitrate);
}
for (key, value) in self.output.video_codec_opts {
output = output.set_video_codec_opt(key, value);
}
for (key, value) in self.output.format_opts {
output = output.set_format_opt(key, value);
}
if let Some(pix_fmt) = self.output.pix_fmt {
output = output.set_pix_fmt(pix_fmt);
}
if let Some(rate) = self.output.audio_sample_rate {
output = output.set_audio_sample_rate(rate);
}
if let Some(channels) = self.output.audio_channels {
output = output.set_audio_channels(channels);
}
if let Some(frames) = self.output.max_video_frames {
output = output.set_max_video_frames(frames);
}
if let Some(filter) = self.output.video_filter {
output = output.set_video_filter(filter);
}
for (map, copy) in self.output.stream_maps {
output = if copy {
output.add_stream_map_with_copy(map)
} else {
output.add_stream_map(map)
};
}
FfmpegContext::builder().input(input).output(output).build()
}
}