simple-ffmpeg-edits 0.1.0

Simple ffmpeg wrapper for trimming, cropping, merging videos and photos
Documentation
use std::{
    path::PathBuf,
    process::{Command, Stdio},
};

use tracing::debug;

use crate::Error;

#[derive(Debug)]
/**
napr. (16, 9), (4, 3)
*/
pub struct AspectRatio(pub u8, pub u8);

#[derive(Debug)]
/**
napr. (16, 9), (4, 3)
*/
pub struct Resolution(pub u16, pub u16);

#[derive(Debug)]
pub struct MediaData {
    pub resolution: Option<Resolution>,
    pub aspect_ratio: Option<AspectRatio>,
    pub duration: Option<f32>,
    pub old_kbit_rate: Option<u32>,
}

pub fn get_metadata(path: &PathBuf) -> Result<MediaData, Error> {
    let args = [
        "-v",
        "error",
        "-select_streams",
        "v:0",
        "-show_entries",
        "stream=width,height,display_aspect_ratio,bit_rate,duration",
        "-of",
        "csv=s=,:p=0",
    ];

    let ffprobe = Command::new("ffprobe")
        .args(args)
        .arg(path)
        .stderr(Stdio::piped())
        .output()?;

    if !ffprobe.status.success() {
        return Err(Error::Sys(
            "Ffprobe failed running. Is it installed?".to_owned(),
        ));
    };

    let text = std::str::from_utf8(&ffprobe.stdout)?;

    let mem = text.split(',').collect::<Vec<_>>();
    debug!("{:?}", mem);

    let width = mem.first().and_then(|v| v.parse::<u16>().ok());
    let height = mem.get(1).and_then(|v| v.parse::<u16>().ok());
    let aspect_ratio = mem.get(2).and_then(|a| {
        a.split_once(':')
            .map(|u| (u.0.parse::<u8>().ok(), u.1.parse::<u8>().ok()))
            .and_then(|u| u.0.zip(u.1))
            .map(|u| AspectRatio(u.0, u.1))
    });

    let old_kbit_rate = mem
        .get(3)
        .and_then(|v| v.parse::<u32>().ok().map(|v| v / 1000));

    let resolution = width.zip(height).map(|s| Resolution(s.0, s.1));

    let duration = match mem.get(4).and_then(|v| v.trim().parse::<f32>().ok()) {
        Some(d) => Some(d),
        None => {
            let metadata = get_attribute_from_meta("duration", path).unwrap_or("".to_owned());
            let res = metadata.parse::<f32>().ok();
            //see if metadatat had seconds directly
            if let Some(d) = res {
                Some(d)
            } else {
                //  try to convert 00:00:00:00 to 0.000s
                if !metadata.contains(":") {
                    None
                } else {
                    let mut res = 0.;
                    let mut iter = metadata.split(':').rev();
                    let secs = iter.next().map(|n| n.parse::<f32>().ok()).flatten();
                    let mins = iter
                        .next()
                        .map(|n| n.parse::<f32>().ok().map(|m| m * 60.))
                        .flatten();
                    let hrs = iter
                        .next()
                        .map(|n| n.parse::<f32>().ok().map(|h| h * 3600.))
                        .flatten();
                    let days = iter
                        .next()
                        .map(|n| n.parse::<f32>().ok().map(|d| d * 24. * 3600.))
                        .flatten();
                    if let Some(s) = secs {
                        res = res + s
                    };
                    if let Some(m) = mins {
                        res = res + m
                    };
                    if let Some(h) = hrs {
                        res = res + h
                    };
                    if let Some(d) = days {
                        res = res + d
                    };
                    Some(res)
                }
            }
        }
    };

    // dbg!(&duration);

    Ok(MediaData {
        aspect_ratio,
        duration,
        resolution,
        old_kbit_rate,
    })
}

pub fn get_attribute_from_meta(attr: &str, path: &PathBuf) -> Option<String> {
    let ffprobe = Command::new("ffprobe")
        .args([
            "-v",
            "error",
            "-select_streams",
            "v:0",
            "-show_entries",
            &format!("stream={attr}:stream_args={attr}"),
            "-of",
            "csv=s=,:p=0",
        ])
        .arg(path)
        .stderr(Stdio::piped())
        .output()
        .ok()?;

    if !ffprobe.status.success() {
        return None;
    };

    std::str::from_utf8(&ffprobe.stdout)
        .ok()
        .map(|v| v.to_string())
}