autorip 0.1.1

Composes other programs to automatically rip optical media
Documentation
use std::{
    path::{Path, PathBuf},
    process::Command,
};

use anyhow::{anyhow, bail, Result};

use super::{get_stdout, Preset};

pub(crate) struct File {
    pub(crate) path: PathBuf,
    pub(crate) duration: Option<u32>,
    pub(crate) title_hint: String,
}

impl File {
    pub(crate) fn new(path: impl Into<PathBuf>) -> Result<Self> {
        let path = path.into();
        if !path.is_file() {
            bail!("Path {} is not a file", path.display())
        }
        let duration = File::duration(&path).ok();
        let title_hint = File::title_hint(&path)?;
        Ok(Self {
            path,
            duration,
            title_hint,
        })
    }

    #[allow(clippy::cast_possible_truncation)]
    fn duration(path: &Path) -> Result<u32> {
        let output = Command::new("ffprobe")
            .args(
                "-v error -show_entries format=duration -print_format \
                 default=noprint_wrappers=1:nokey=1"
                    .split_whitespace(),
            )
            .arg(path)
            .output()?;
        Ok(get_stdout(output)?
            .trim()
            .split('.')
            .next()
            .ok_or_else(|| anyhow!("no chunk before period"))?
            .parse::<u32>()?)
    }

    pub(crate) fn rip(&self, preset: &Preset) -> Result<PathBuf> {
        let outfile = &self.title_hint;
        Command::new("HandBrakeCLI")
            .arg("--preset")
            .arg(preset.to_string())
            .arg("--input")
            .arg(&self.path)
            .arg("--output")
            .arg(outfile)
            .output()?;
        Ok(outfile.into())
    }

    fn title_hint(path: impl AsRef<Path>) -> Result<String> {
        if let Some(stem) =
            path.as_ref().file_stem().and_then(|stem| stem.to_str())
        {
            Ok(stem.to_string())
        } else {
            bail!("Unable to get stem from path: {}", path.as_ref().display())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_title() {
        assert_eq!(File::new("/etc/passwd").unwrap().title_hint, "passwd");
        assert!(File::new("/etc/").is_err());
    }
}