direct_play_nice 0.2.0-beta.1

CLI program that converts video files to direct-play-compatible formats.
Documentation
#![cfg(feature = "ffmpeg-cli-tests")]

mod common;

use assert_cmd::prelude::*;
use common::{ensure_ffmpeg_present, gen_problem_input};
use predicates::str;
use std::error::Error;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::{Mutex, OnceLock};
use tempfile::TempDir;

fn cli_lock() -> std::sync::MutexGuard<'static, ()> {
    static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
    LOCK.get_or_init(|| Mutex::new(()))
        .lock()
        .unwrap_or_else(|poisoned| poisoned.into_inner())
}

fn make_input(tmp: &TempDir) -> Result<PathBuf, Box<dyn Error>> {
    let (source, _) = gen_problem_input(tmp);
    Ok(source)
}

fn skip_subtitles_and_use_reasonable_audio(cmd: &mut Command) {
    cmd.arg("--sub-mode")
        .arg("skip")
        .arg("--audio-quality")
        .arg("192k");
}

fn run_cli(input: &Path, output: &Path, extra_args: &[&str]) -> Result<(), Box<dyn Error>> {
    let _guard = cli_lock();
    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("direct_play_nice"));
    cmd.arg("-s").arg("chromecast_1st_gen");
    skip_subtitles_and_use_reasonable_audio(&mut cmd);
    cmd.arg(input).arg(output);
    for arg in extra_args {
        cmd.arg(arg);
    }
    cmd.assert().success().stdout(str::is_empty());
    Ok(())
}

#[test]
fn delete_source_defaults_to_false() -> Result<(), Box<dyn Error>> {
    ensure_ffmpeg_present();
    let tmp = TempDir::new()?;
    let input = make_input(&tmp)?;
    let output = tmp.path().join("out.mp4");

    run_cli(&input, &output, &[])?;

    assert!(input.exists(), "input file should remain by default");
    Ok(())
}

#[test]
fn delete_source_config_true_overridden_by_cli_false() -> Result<(), Box<dyn Error>> {
    ensure_ffmpeg_present();
    let tmp = TempDir::new()?;
    let config_path = tmp.path().join("config.toml");
    fs::write(&config_path, "delete_source = true\n")?;

    let input = make_input(&tmp)?;
    let output = tmp.path().join("out.mp4");

    let _guard = cli_lock();
    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("direct_play_nice"));
    cmd.arg("--config-file")
        .arg(&config_path)
        .arg("-s")
        .arg("chromecast_1st_gen");
    skip_subtitles_and_use_reasonable_audio(&mut cmd);
    cmd.arg(&input).arg(&output).arg("--delete-source=false");
    cmd.assert().success().stdout(str::is_empty());

    assert!(
        input.exists(),
        "CLI should override config delete_source=true"
    );
    Ok(())
}

#[test]
fn delete_source_flag_deletes_input() -> Result<(), Box<dyn Error>> {
    ensure_ffmpeg_present();
    let tmp = TempDir::new()?;
    let input = make_input(&tmp)?;
    let output = tmp.path().join("out.mp4");

    run_cli(&input, &output, &["--delete-source"])?;

    assert!(!input.exists(), "--delete-source should remove input file");
    Ok(())
}

#[test]
fn delete_source_config_true_respected_without_cli_override() -> Result<(), Box<dyn Error>> {
    ensure_ffmpeg_present();
    let tmp = TempDir::new()?;
    let config_path = tmp.path().join("config.toml");
    fs::write(&config_path, "delete_source = true\n")?;

    let input = make_input(&tmp)?;
    let output = tmp.path().join("out.mp4");

    let _guard = cli_lock();
    let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("direct_play_nice"));
    cmd.arg("--config-file")
        .arg(&config_path)
        .arg("-s")
        .arg("chromecast_1st_gen");
    skip_subtitles_and_use_reasonable_audio(&mut cmd);
    cmd.arg(&input).arg(&output);
    cmd.assert().success().stdout(str::is_empty());

    assert!(
        !input.exists(),
        "config delete_source=true should remove input when not overridden"
    );
    Ok(())
}