119 lines
3.9 KiB
Rust
119 lines
3.9 KiB
Rust
use std::env;
|
|
use std::fs;
|
|
use std::os::unix::fs::PermissionsExt;
|
|
use std::path::Path;
|
|
|
|
use temp_env::with_var;
|
|
use tempfile::tempdir;
|
|
|
|
pub(super) fn with_fake_media_tools<T>(
|
|
ffprobe_output: &[u8],
|
|
ffmpeg_video_output: &[u8],
|
|
ffmpeg_audio_output: &[u8],
|
|
test: impl FnOnce(&Path) -> T,
|
|
) -> T {
|
|
let temp_dir = tempdir().expect("tempdir");
|
|
fs::write(temp_dir.path().join("ffprobe.out"), ffprobe_output).expect("write ffprobe");
|
|
fs::write(
|
|
temp_dir.path().join("ffmpeg-video.out"),
|
|
ffmpeg_video_output,
|
|
)
|
|
.expect("write ffmpeg video");
|
|
fs::write(
|
|
temp_dir.path().join("ffmpeg-audio.out"),
|
|
ffmpeg_audio_output,
|
|
)
|
|
.expect("write ffmpeg audio");
|
|
write_executable(
|
|
temp_dir.path(),
|
|
"ffprobe",
|
|
"#!/bin/sh\ncat \"$(dirname \"$0\")/ffprobe.out\"\n",
|
|
);
|
|
write_executable(
|
|
temp_dir.path(),
|
|
"ffmpeg",
|
|
"#!/bin/sh\ncase \" $* \" in\n *\" -map 0:v:0 \"*) cat \"$(dirname \"$0\")/ffmpeg-video.out\" ;;\n *\" -map 0:a:0 \"*) cat \"$(dirname \"$0\")/ffmpeg-audio.out\" ;;\n *) printf 'unexpected ffmpeg args: %s\\n' \"$*\" >&2; exit 64 ;;\nesac\n",
|
|
);
|
|
|
|
let prior_path = env::var("PATH").unwrap_or_default();
|
|
let merged_path = if prior_path.is_empty() {
|
|
temp_dir.path().display().to_string()
|
|
} else {
|
|
format!("{}:{prior_path}", temp_dir.path().display())
|
|
};
|
|
let capture_path = temp_dir.path().join("capture.mkv");
|
|
fs::write(&capture_path, b"fake-capture").expect("write capture");
|
|
with_var("PATH", Some(merged_path.as_str()), || test(&capture_path))
|
|
}
|
|
|
|
pub(super) fn frame_json(timestamps: &[f64]) -> Vec<u8> {
|
|
let frames = timestamps
|
|
.iter()
|
|
.map(|timestamp| {
|
|
serde_json::json!({
|
|
"best_effort_timestamp_time": format!("{timestamp:.3}")
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
serde_json::to_vec(&serde_json::json!({ "frames": frames })).expect("frame json")
|
|
}
|
|
|
|
pub(super) fn click_track_samples(click_times_s: &[f64], total_samples: usize) -> Vec<i16> {
|
|
let mut samples = vec![0i16; total_samples];
|
|
for click_time_s in click_times_s {
|
|
let start = (*click_time_s * 48_000.0).round() as usize;
|
|
for sample in samples.iter_mut().skip(start).take(300) {
|
|
*sample = 18_000;
|
|
}
|
|
}
|
|
samples
|
|
}
|
|
|
|
pub(super) fn thumbnail_video_bytes(brightness_values: &[u8]) -> Vec<u8> {
|
|
const SIDE: usize = 64;
|
|
let mut bytes = Vec::with_capacity(brightness_values.len() * SIDE * SIDE);
|
|
for brightness in brightness_values {
|
|
let mut frame = vec![20u8; SIDE * SIDE];
|
|
for y in SIDE / 4..SIDE - SIDE / 4 {
|
|
for x in SIDE / 4..SIDE - SIDE / 4 {
|
|
frame[y * SIDE + x] = *brightness;
|
|
}
|
|
}
|
|
bytes.extend_from_slice(&frame);
|
|
}
|
|
bytes
|
|
}
|
|
|
|
pub(super) fn thumbnail_rgb_video_bytes(colors: &[(u8, u8, u8)]) -> Vec<u8> {
|
|
const SIDE: usize = 64;
|
|
let mut bytes = Vec::with_capacity(colors.len() * SIDE * SIDE * 3);
|
|
for (r, g, b) in colors {
|
|
let mut frame = vec![0u8; SIDE * SIDE * 3];
|
|
for y in SIDE / 4..SIDE - SIDE / 4 {
|
|
for x in SIDE / 4..SIDE - SIDE / 4 {
|
|
let offset = (y * SIDE + x) * 3;
|
|
frame[offset] = *r;
|
|
frame[offset + 1] = *g;
|
|
frame[offset + 2] = *b;
|
|
}
|
|
}
|
|
bytes.extend_from_slice(&frame);
|
|
}
|
|
bytes
|
|
}
|
|
|
|
pub(super) fn audio_samples_to_bytes(samples: &[i16]) -> Vec<u8> {
|
|
samples
|
|
.iter()
|
|
.flat_map(|sample| sample.to_le_bytes())
|
|
.collect()
|
|
}
|
|
|
|
fn write_executable(dir: &Path, name: &str, contents: &str) {
|
|
let path = dir.join(name);
|
|
fs::write(&path, contents).expect("write script");
|
|
let mut permissions = fs::metadata(&path).expect("script metadata").permissions();
|
|
permissions.set_mode(0o755);
|
|
fs::set_permissions(&path, permissions).expect("script permissions");
|
|
}
|