Files
arr/crates/arr-probe/tests/probe.rs
T
naps62-yolo 1cb7f1b2fa
ci / rust (push) Successful in 1m20s
ci / web (push) Successful in 7s
e2e / e2e (push) Successful in 1m28s
feat(probe): ffprobe wrapper (#59)
2026-08-22 20:52:09 +01:00

244 lines
7.1 KiB
Rust

//! Real `ffprobe` runs against the committed fixture clips (DESIGN.md §12).
//!
//! The clips are generated by `scripts/make-probe-fixtures.sh`. Every
//! assertion here is about output `ffprobe` actually produced, not a recorded
//! JSON document, because the shape of that output is exactly what breaks
//! between ffmpeg releases.
// Same per-target quirk as elsewhere in the workspace: an integration test
// links the library's dependencies without using them directly.
use {serde as _, serde_json as _, thiserror as _, tracing as _};
use std::{path::PathBuf, time::Duration};
use arr_core::{DolbyVisionProfile, HdrFormat, Language, Resolution};
use arr_probe::{Error, Prober, RuntimeMatch};
fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures")
.join(name)
}
#[tokio::test]
async fn a_feature_reports_every_track_it_carries() {
let file = Prober::new()
.probe(fixture("movie-1080p.mkv"))
.await
.expect("the fixture is readable");
assert_eq!(file.media.resolution, Resolution::R1080p);
assert_eq!(file.video_codec, "h264");
assert_eq!(file.media.hdr, HdrFormat::Sdr);
assert!(file.size > 0);
assert!(file.duration >= Duration::from_secs(3));
// A file never claims a source; that stays a claim of the release name.
assert_eq!(file.media.source, None);
let languages: Vec<&Language> = file
.media
.audio_tracks
.iter()
.map(|track| &track.language)
.collect();
assert_eq!(
languages,
vec![
&Language::Other("en".to_owned()),
&Language::PortugueseBrazil,
&Language::PortuguesePortugal,
]
);
let brazilian = &file.media.audio_tracks[1];
assert_eq!(brazilian.title.as_deref(), Some("Português (Brasil)"));
assert_eq!(
brazilian.handler_name.as_deref(),
Some("Portuguese (Brazil)")
);
let subtitles: Vec<&Language> = file
.media
.subtitle_tracks
.iter()
.map(|track| &track.language)
.collect();
assert_eq!(
subtitles,
vec![
&Language::Other("en".to_owned()),
// `por` with nothing else to go on. Not guessed either way.
&Language::PortugueseUnverified,
]
);
}
#[tokio::test]
async fn hdr10_comes_from_the_transfer_function() {
let file = Prober::new()
.probe(fixture("hdr10-2160p.mkv"))
.await
.expect("the fixture is readable");
assert_eq!(file.media.resolution, Resolution::R2160p);
assert_eq!(file.video_codec, "hevc");
assert_eq!(file.media.hdr, HdrFormat::Hdr10);
}
#[tokio::test]
async fn dolby_vision_profile_5_is_reported_as_a_number() {
let file = Prober::new()
.probe(fixture("dv-profile5.mp4"))
.await
.expect("the fixture is readable");
assert_eq!(
file.media.hdr,
HdrFormat::DolbyVision(DolbyVisionProfile {
profile: 5,
compatibility_id: Some(0),
})
);
}
#[tokio::test]
async fn dolby_vision_profile_8_1_keeps_its_compatibility_id() {
let file = Prober::new()
.probe(fixture("dv-profile8-1.mp4"))
.await
.expect("the fixture is readable");
assert_eq!(
file.media.hdr,
HdrFormat::DolbyVision(DolbyVisionProfile {
profile: 8,
compatibility_id: Some(1),
})
);
}
#[tokio::test]
async fn a_text_file_is_not_media() {
let error = Prober::new()
.probe(fixture("notes.nfo"))
.await
.expect_err("a text file is not media");
assert!(matches!(error, Error::NotVideo { .. }), "{error:?}");
}
/// Cover art is a video stream as far as ffprobe is concerned.
#[tokio::test]
async fn an_audio_file_with_artwork_is_not_video() {
let error = Prober::new()
.probe(fixture("audio-with-cover.m4a"))
.await
.expect_err("an attached picture is not a video stream");
assert!(matches!(error, Error::NoVideoStream { .. }), "{error:?}");
}
#[tokio::test]
async fn a_missing_file_is_an_error() {
let error = Prober::new()
.probe(fixture("does-not-exist.mkv"))
.await
.expect_err("nothing to probe");
assert!(matches!(error, Error::Rejected { .. }), "{error:?}");
}
#[tokio::test]
async fn a_missing_ffprobe_is_an_error() {
let error = Prober::new()
.with_binary("ffprobe-that-does-not-exist")
.probe(fixture("movie-1080p.mkv"))
.await
.expect_err("no binary to run");
assert!(matches!(error, Error::Spawn { .. }), "{error:?}");
}
/// The extras reel is the larger file on disk and the shorter one on the
/// clock. Size alone picks it; the runtime check does not.
#[tokio::test]
async fn runtime_beats_size_when_picking_the_feature() {
let selection = Prober::new()
.select_feature(
[
fixture("extras-360p.mkv"),
fixture("movie-1080p.mkv"),
fixture("notes.nfo"),
],
Some(Duration::from_secs(3)),
)
.await
.expect("one of them is a video file");
assert_eq!(selection.feature.path, fixture("movie-1080p.mkv"));
assert_eq!(selection.runtime, RuntimeMatch::Matched);
assert_eq!(selection.others.len(), 1);
assert_eq!(selection.others[0].path, fixture("extras-360p.mkv"));
assert!(selection.others[0].size > selection.feature.size);
}
#[tokio::test]
async fn without_an_expected_runtime_the_largest_file_wins() {
let selection = Prober::new()
.select_feature(
[fixture("movie-1080p.mkv"), fixture("extras-360p.mkv")],
None,
)
.await
.expect("both are video files");
assert_eq!(selection.feature.path, fixture("extras-360p.mkv"));
assert_eq!(selection.runtime, RuntimeMatch::Unchecked);
}
#[tokio::test]
async fn nothing_within_tolerance_still_returns_the_largest_file() {
let expected = Duration::from_hours(2);
let selection = Prober::new()
.select_feature(
[fixture("movie-1080p.mkv"), fixture("extras-360p.mkv")],
Some(expected),
)
.await
.expect("both are video files");
assert_eq!(selection.feature.path, fixture("extras-360p.mkv"));
assert!(
matches!(
selection.runtime,
RuntimeMatch::Mismatched { expected: e, .. } if e == expected
),
"{:?}",
selection.runtime
);
}
/// A prober that cannot run is not the same fact as a torrent without video,
/// and must not be reported as one.
#[tokio::test]
async fn a_broken_prober_fails_the_selection() {
let error = Prober::new()
.with_binary("ffprobe-that-does-not-exist")
.select_feature([fixture("movie-1080p.mkv")], None)
.await
.expect_err("nothing could be probed");
assert!(matches!(error, Error::Spawn { .. }), "{error:?}");
}
#[tokio::test]
async fn a_torrent_with_no_video_file_selects_nothing() {
let error = Prober::new()
.select_feature([fixture("notes.nfo")], None)
.await
.expect_err("no video among the candidates");
assert!(matches!(error, Error::NoCandidates), "{error:?}");
}