wl-screenrec 0.2.0

High performance screen/audio recorder for wlroots
use std::{
    env::{consts::EXE_SUFFIX, current_exe, temp_dir},
    path::{Path, PathBuf},
    process::Command,
    thread::sleep,
    time::{Duration, Instant},
};

use nix::{
    sys::signal::{
        Signal::{SIGINT, SIGUSR1},
        kill,
    },
    unistd::Pid,
};
use serde_json::Value;

fn wl_screenrec() -> PathBuf {
    let mut cur = current_exe().unwrap();
    cur.pop();
    cur.pop();
    cur.push(format!("wl-screenrec{}", EXE_SUFFIX));
    cur
}

#[test]
fn history_clip_length() {
    let filename = temp_dir().join("ahcl.mp4");

    let mut cmd = Command::new(dbg!(wl_screenrec()))
        .arg("--no-damage")
        .arg("--capture-backend=wlr-screencopy")
        .arg("--audio")
        .arg("--gop-size=5")
        .arg("--history=2")
        .arg("-f")
        .arg(&filename)
        .spawn()
        .unwrap();

    sleep(Duration::from_secs(10));

    let pid = Pid::from_raw(cmd.id() as i32);
    kill(pid, SIGUSR1).unwrap();

    sleep(Duration::from_secs(6));

    kill(pid, SIGINT).unwrap();

    let wait_start = Instant::now();
    cmd.wait().unwrap();
    assert!(wait_start.elapsed() < Duration::from_secs(1));

    let dur = file_duration(&filename);
    println!("dur={dur:?}");

    // duration *should* be ~8 (2 seconds of history + 6 seconds after USER1)
    assert!(dur > Duration::from_secs(8), "{:?} < 8s", dur);
    assert!(dur < Duration::from_secs_f64(8.5), "{:?} > 8.5s", dur);
}

#[test]
fn scale() {
    let filename = temp_dir().join("scale.mp4");

    let mut cmd = Command::new(dbg!(wl_screenrec()))
        .arg("--no-damage")
        .arg("--capture-backend=wlr-screencopy")
        .arg("--encode-resolution=128x128")
        .arg("-g=0,0 256x256")
        .arg("-f")
        .arg(&filename)
        .spawn()
        .unwrap();

    sleep(Duration::from_secs(5));
    let pid = Pid::from_raw(cmd.id() as i32);
    kill(pid, SIGINT).unwrap();

    let wait_start = Instant::now();
    cmd.wait().unwrap();
    assert!(wait_start.elapsed() < Duration::from_secs(1));

    assert_eq!(file_resolution(&filename), (128, 128));
}

#[test]
fn basic() {
    let filename = temp_dir().join("basic.mp4");

    let mut cmd = Command::new(dbg!(wl_screenrec()))
        .arg("--no-damage")
        .arg("--capture-backend=wlr-screencopy")
        .arg("-f")
        .arg(&filename)
        .spawn()
        .unwrap();
    let pid = Pid::from_raw(cmd.id() as i32);

    sleep(Duration::from_secs(3));

    kill(pid, SIGINT).unwrap();

    let wait_start = Instant::now();
    cmd.wait().unwrap();
    assert!(wait_start.elapsed() < Duration::from_secs(1));

    let dur = file_duration(&filename);

    assert!(dur > Duration::from_secs_f64(2.5), "{:?} < 2.5s", dur);
    assert!(dur < Duration::from_secs_f64(3.5), "{:?} > 3.5s", dur);
}

#[cfg(feature = "experimental-vulkan")]
#[test]
fn basic_vulkan() {
    let filename = temp_dir().join("basic_vulkan.mp4");

    let mut cmd = Command::new(dbg!(wl_screenrec()))
        .arg("--no-damage")
        .arg("--experimental-vulkan")
        .arg("--capture-backend=wlr-screencopy")
        .arg("-f")
        .arg(&filename)
        .spawn()
        .unwrap();
    let pid = Pid::from_raw(cmd.id() as i32);

    sleep(Duration::from_secs(3));

    kill(pid, SIGINT).unwrap();

    let wait_start = Instant::now();
    cmd.wait().unwrap();
    assert!(wait_start.elapsed() < Duration::from_secs(1));

    let dur = file_duration(&filename);

    assert!(dur > Duration::from_secs_f64(2.5), "{:?} < 2.5s", dur);
    assert!(dur < Duration::from_secs_f64(3.5), "{:?} > 3.5s", dur);
}

fn file_metadata(filename: &Path) -> Value {
    serde_json::from_str(
        &String::from_utf8(
            Command::new("ffprobe")
                .arg("-show_format")
                .arg("-show_streams")
                .arg("-print_format")
                .arg("json")
                .arg(&filename)
                .output()
                .unwrap()
                .stdout,
        )
        .unwrap(),
    )
    .unwrap()
}

fn file_duration(filename: &Path) -> Duration {
    let json = file_metadata(filename);

    let dur: f64 = json
        .pointer("/format/duration")
        .unwrap()
        .as_str()
        .unwrap()
        .parse()
        .unwrap();
    Duration::from_secs_f64(dur)
}

fn file_resolution(filename: &Path) -> (i64, i64) {
    let json = file_metadata(filename);

    (
        json.pointer("/streams/0/width")
            .unwrap()
            .as_number()
            .unwrap()
            .as_i64()
            .unwrap(),
        json.pointer("/streams/0/height")
            .unwrap()
            .as_number()
            .unwrap()
            .as_i64()
            .unwrap(),
    )
}