//! 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 hlg_comes_from_the_transfer_function() { let file = Prober::new() .probe(fixture("hlg-720p.mkv")) .await .expect("the fixture is readable"); assert_eq!(file.video_codec, "hevc"); assert_eq!(file.media.hdr, HdrFormat::Hlg); } #[tokio::test] async fn ten_bit_hevc_without_hdr_transfer_is_sdr() { let file = Prober::new() .probe(fixture("sdr-10bit-hevc-720p.mkv")) .await .expect("the fixture is readable"); assert_eq!(file.video_codec, "hevc"); assert_eq!(file.media.hdr, HdrFormat::Sdr); } #[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:?}"); }