use std::path::Path;
use crate::media::ffmpeg::{FFPROBE, ffprobe};
const ENTRIES: &str = "stream=codec_type,width,height:format=duration";
const CSV: &str = "csv=p=0";
const FIELD_SEP: char = ',';
const VIDEO: &str = "video";
const AUDIO: &str = "audio";
#[derive(Debug, PartialEq)]
pub(super) struct Source {
pub(super) size: (u32, u32),
pub(super) sound: bool,
pub(super) seconds: Option<f32>,
}
const PACKETS: &str = "packet=pts_time,duration_time";
const FIRST_PICTURE: &str = "v:0";
fn length_by_packets(path: &Path) -> Option<f32> {
let mut cmd = ffprobe();
cmd.args([
"-select_streams",
FIRST_PICTURE,
"-show_entries",
PACKETS,
"-of",
CSV,
])
.arg(path);
let out = cmd.output().ok()?;
String::from_utf8_lossy(&out.stdout)
.lines()
.filter_map(|l| {
let mut f = l.trim().split(FIELD_SEP);
let at: f32 = f.next()?.parse().ok()?;
Some(at + f.next().and_then(|d| d.parse().ok()).unwrap_or(0.0))
})
.reduce(f32::max)
}
pub(super) fn probe(path: &Path) -> Result<Source, String> {
let mut cmd = ffprobe();
cmd.args(["-show_entries", ENTRIES, "-of", CSV]).arg(path);
let out = cmd
.output()
.map_err(|e| format!("a video needs {FFPROBE}, which can't be run: {e}"))?;
let mut source = parse(&String::from_utf8_lossy(&out.stdout)).ok_or_else(|| {
let err = String::from_utf8_lossy(&out.stderr);
format!("not a video ffmpeg reads: {}", err.trim())
})?;
if source.seconds.is_none() {
source.seconds = length_by_packets(path);
}
Ok(source)
}
fn parse(text: &str) -> Option<Source> {
let mut size = None;
let mut sound = false;
let mut seconds = None;
for line in text.lines() {
if let Ok(s) = line.trim().parse::<f32>() {
seconds = Some(s);
continue;
}
let mut f = line.trim().split(FIELD_SEP);
match f.next() {
Some(VIDEO) if size.is_none() => {
let w = f.next()?.parse().ok()?;
let h = f.next()?.parse().ok()?;
size = Some((w, h));
}
Some(AUDIO) => sound = true,
_ => {}
}
}
size.map(|size| Source {
size,
sound,
seconds,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_picture_and_any_sound_are_found() {
let s = parse("video,161,91\naudio,,\nvideo,8,8\n6.000000\n").unwrap();
assert_eq!(
s,
Source {
size: (161, 91),
sound: true,
seconds: Some(6.0)
}
);
assert_eq!(parse("audio,,\n"), None);
}
}