From 6bfdcf039eb4bcfb9de01d993d8ffbd57267c3ee Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 20:02:38 +0100 Subject: [PATCH 1/2] test(parse): add release name fixture corpus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 85 table-driven rows covering every resolution and source tier from DESIGN.md §5.5, DV-tagged and DV-untagged 4K, multi-audio releases, the pt-BR markers from §5.2, and malformed names that must parse partially. Prowlarr was unreachable with credentials available to this session, so names follow the same scene-release grammar real indexer results use rather than being pulled live. Closes #9 --- crates/arr-parse/tests/corpus.rs | 623 +++++++++++++++++++++++++++++++ 1 file changed, 623 insertions(+) create mode 100644 crates/arr-parse/tests/corpus.rs diff --git a/crates/arr-parse/tests/corpus.rs b/crates/arr-parse/tests/corpus.rs new file mode 100644 index 0000000..2081953 --- /dev/null +++ b/crates/arr-parse/tests/corpus.rs @@ -0,0 +1,623 @@ +//! Fixture corpus for `arr-parse` (issue #9): a broad set of release names +//! with expected parses, serving as the parser's regression suite. +//! +//! The issue asks for names pulled from a live Prowlarr search. Prowlarr +//! was not reachable with credentials available to this session (see the +//! environment notes in the repo's `CLAUDE.md`), so this corpus instead +//! follows the same scene-release naming grammar real indexer results use, +//! built to deliberately cover every resolution and source tier, DV-tagged +//! and DV-untagged 4K, multi-audio releases, the pt-BR markers from +//! `DESIGN.md` §5.2, and malformed names that must parse partially. + +use arr_parse::{parse, Codec, HdrMarker, LanguageMarker, NameClaims, Resolution, Source}; +use serde as _; + +struct Case { + name: String, + want: NameClaims, +} + +#[allow(clippy::unnecessary_wraps)] +fn s(v: &str) -> Option { + Some(v.to_string()) +} + +const RESOLUTIONS: [(&str, Resolution); 5] = [ + ("480p", Resolution::P480), + ("576p", Resolution::P576), + ("720p", Resolution::P720), + ("1080p", Resolution::P1080), + ("2160p", Resolution::P2160), +]; + +/// Source tag as it would appear in a real release name, alongside the +/// tier it claims. `BluRay.REMUX` carries both a `BluRay` and a `REMUX` +/// token, matching real remux releases (`DESIGN.md` §5.5) and exercising +/// the "Remux wins when both appear" rule. +const SOURCES: [(&str, Source); 10] = [ + ("HDCAM", Source::Cam), + ("HDTS", Source::Telesync), + ("HDTC", Source::Telecine), + ("DVDSCR", Source::Screener), + ("DVDRip", Source::Dvd), + ("HDTV", Source::Hdtv), + ("WEBRip", Source::WebRip), + ("WEB-DL", Source::WebDl), + ("BluRay", Source::BluRay), + ("BluRay.REMUX", Source::Remux), +]; + +const MATRIX_TITLES: [&str; 10] = [ + "Iron Coast", + "Silent Harbor", + "Crimson Valley", + "Northern Static", + "Glass Horizon", + "Ashfall", + "Long Signal", + "Hollow Point", + "Midnight Freight", + "Salt and Rust", +]; + +/// Every resolution × every source tier (`DESIGN.md` §5.5): 50 rows. +fn resolution_source_matrix() -> Vec { + let mut out = Vec::with_capacity(RESOLUTIONS.len() * SOURCES.len()); + for (ri, &(res_tok, res)) in RESOLUTIONS.iter().enumerate() { + let (codec_tok, codec) = if res == Resolution::P2160 { + ("x265", Codec::X265) + } else { + ("x264", Codec::X264) + }; + for (si, &(src_tok, src)) in SOURCES.iter().enumerate() { + let title = MATRIX_TITLES[si]; + let year = 2015 + u16::try_from((ri * SOURCES.len() + si) % 10).unwrap_or(0); + let group = format!("GRP{ri}{si}"); + let name = format!( + "{}.{year}.{res_tok}.{src_tok}.{codec_tok}-{group}", + title.replace(' ', ".") + ); + out.push(Case { + name, + want: NameClaims { + title: s(title), + year: Some(year), + resolution: Some(res), + source: Some(src), + codec: Some(codec), + group: s(&group), + ..NameClaims::default() + }, + }); + } + } + out +} + +/// DV-tagged and DV-untagged 4K releases (`DESIGN.md` §5.3): every HDR +/// marker shape, including the two-word `Dolby Vision` spelling and a +/// plain 4K release that claims no HDR format at all. +#[allow(clippy::too_many_lines)] +fn dv_hdr_cases() -> Vec { + vec![ + Case { + name: "Aurora.Falling.2024.2160p.WEB-DL.DV.x265-GRP1".into(), + want: NameClaims { + title: s("Aurora Falling"), + year: Some(2024), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision], + group: s("GRP1"), + ..NameClaims::default() + }, + }, + Case { + name: "Undertow.2023.2160p.BluRay.DV.HDR10.x265-GRP2".into(), + want: NameClaims { + title: s("Undertow"), + year: Some(2023), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], + group: s("GRP2"), + ..NameClaims::default() + }, + }, + Case { + name: "The.Last.Ferry.2022.2160p.WEB-DL.DV.HDR10+.x265-GRP3".into(), + want: NameClaims { + title: s("The Last Ferry"), + year: Some(2022), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10Plus], + group: s("GRP3"), + ..NameClaims::default() + }, + }, + Case { + name: "Skyline.Drift.2021.2160p.BluRay.HDR10.x265-GRP4".into(), + want: NameClaims { + title: s("Skyline Drift"), + year: Some(2021), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hdr10], + group: s("GRP4"), + ..NameClaims::default() + }, + }, + Case { + name: "Coastal.Static.2020.2160p.WEB-DL.HDR.x265-GRP5".into(), + want: NameClaims { + title: s("Coastal Static"), + year: Some(2020), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hdr], + group: s("GRP5"), + ..NameClaims::default() + }, + }, + Case { + // DV-untagged 4K: no HDR marker at all, and none should appear. + name: "Paper.Moonlight.2023.2160p.WEB-DL.x265-GRP6".into(), + want: NameClaims { + title: s("Paper Moonlight"), + year: Some(2023), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + group: s("GRP6"), + ..NameClaims::default() + }, + }, + Case { + name: "Harbor.Lights.2022.2160p.WEB-DL.HLG.x265-GRP7".into(), + want: NameClaims { + title: s("Harbor Lights"), + year: Some(2022), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hlg], + group: s("GRP7"), + ..NameClaims::default() + }, + }, + Case { + name: "Night.Frequency.2021.2160p.WEB-DL.SDR.x265-GRP8".into(), + want: NameClaims { + title: s("Night Frequency"), + year: Some(2021), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Sdr], + group: s("GRP8"), + ..NameClaims::default() + }, + }, + Case { + // `DOVI` alternate spelling. + name: "Glass.Horizon.2024.2160p.BluRay.DOVI.HDR10.x265-GRP9".into(), + want: NameClaims { + title: s("Glass Horizon"), + year: Some(2024), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], + group: s("GRP9"), + ..NameClaims::default() + }, + }, + Case { + // Order of appearance is preserved, not a fixed HDR priority. + name: "Midnight.Freight.2023.2160p.WEB-DL.HDR10.Dolby.Vision.x265-GRP10".into(), + want: NameClaims { + title: s("Midnight Freight"), + year: Some(2023), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hdr10, HdrMarker::DolbyVision], + group: s("GRP10"), + ..NameClaims::default() + }, + }, + ] +} + +/// Multi-audio releases: `MULTi`, `DUAL`, the two-word spellings, and a +/// mix with explicit language markers. Confirms audio-codec tokens like +/// `DTS-HD` never leak into the language claims. +#[allow(clippy::too_many_lines)] +fn multi_audio_cases() -> Vec { + vec![ + Case { + name: "River.North.2023.1080p.BluRay.MULTi.x264-MA1".into(), + want: NameClaims { + title: s("River North"), + year: Some(2023), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Multi], + group: s("MA1"), + ..NameClaims::default() + }, + }, + Case { + name: "Northbound.2022.1080p.BluRay.Multi.Audio.x264-MA2".into(), + want: NameClaims { + title: s("Northbound"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Multi], + group: s("MA2"), + ..NameClaims::default() + }, + }, + Case { + name: "Salt.and.Rust.2021.1080p.WEB-DL.DUAL.x264-MA3".into(), + want: NameClaims { + title: s("Salt and Rust"), + year: Some(2021), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Dual], + group: s("MA3"), + ..NameClaims::default() + }, + }, + Case { + name: "Hollow.Point.2022.1080p.WEB-DL.Dual.Audio.x264-MA4".into(), + want: NameClaims { + title: s("Hollow Point"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Dual], + group: s("MA4"), + ..NameClaims::default() + }, + }, + Case { + name: "Ashfall.2023.1080p.WEB-DL.MULTi.English.French.German.x264-MA5".into(), + want: NameClaims { + title: s("Ashfall"), + year: Some(2023), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![ + LanguageMarker::Multi, + LanguageMarker::English, + LanguageMarker::French, + LanguageMarker::German, + ], + group: s("MA5"), + ..NameClaims::default() + }, + }, + Case { + name: "Silent.Harbor.2022.1080p.WEB-DL.MULTi.PT-BR.English.x264-MA6".into(), + want: NameClaims { + title: s("Silent Harbor"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![ + LanguageMarker::Multi, + LanguageMarker::PtBr, + LanguageMarker::English, + ], + group: s("MA6"), + ..NameClaims::default() + }, + }, + Case { + name: "Meia.Noite.2021.1080p.WEB-DL.Dual.Áudio.x264-MA7".into(), + want: NameClaims { + title: s("Meia Noite"), + year: Some(2021), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Dual], + group: s("MA7"), + ..NameClaims::default() + }, + }, + Case { + // `DTS-HD` is an audio-codec claim, not surfaced; must not + // leak into `languages` or anywhere else. + name: "Crimson.Valley.2023.1080p.BluRay.DTS-HD.MULTi.x264-MA8".into(), + want: NameClaims { + title: s("Crimson Valley"), + year: Some(2023), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Multi], + group: s("MA8"), + ..NameClaims::default() + }, + }, + ] +} + +/// The pt-BR / pt-PT markers from `DESIGN.md` §5.2. This crate only +/// reports what the name claims; resolving pt-BR vs. pt-PT ambiguity is +/// the policy engine's job, not this one's. +#[allow(clippy::too_many_lines)] +fn pt_br_cases() -> Vec { + vec![ + Case { + name: "Casa.Vazia.2021.1080p.WEB-DL.DUBLADO.x264-BR3".into(), + want: NameClaims { + title: s("Casa Vazia"), + year: Some(2021), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::PtBr], + group: s("BR3"), + ..NameClaims::default() + }, + }, + Case { + name: "Casa.Vazia.2021.1080p.WEB-DL.NACIONAL.x264-BR4".into(), + want: NameClaims { + title: s("Casa Vazia"), + year: Some(2021), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::PtBr], + group: s("BR4"), + ..NameClaims::default() + }, + }, + Case { + name: "Casa.Vazia.2021.1080p.WEB-DL.PT-BR.x264-BR5".into(), + want: NameClaims { + title: s("Casa Vazia"), + year: Some(2021), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::PtBr], + group: s("BR5"), + ..NameClaims::default() + }, + }, + Case { + name: "Lisboa.Ao.Vivo.2022.1080p.WEB-DL.PT-PT.x264-PT1".into(), + want: NameClaims { + title: s("Lisboa Ao Vivo"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::PtPt], + group: s("PT1"), + ..NameClaims::default() + }, + }, + Case { + name: "Lisboa.Ao.Vivo.2022.1080p.WEB-DL.PORTUGUESE.x264-PT2".into(), + want: NameClaims { + title: s("Lisboa Ao Vivo"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Portuguese], + group: s("PT2"), + ..NameClaims::default() + }, + }, + Case { + // Bare `POR` — flavour unknown, reported as generic Portuguese. + name: "Lisboa.Ao.Vivo.2022.1080p.WEB-DL.POR.x264-PT3".into(), + want: NameClaims { + title: s("Lisboa Ao Vivo"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Portuguese], + group: s("PT3"), + ..NameClaims::default() + }, + }, + Case { + // `LEGENDADO` claims pt-BR subtitles, not audio — must not + // become a PtBr claim (`markers.rs`). + name: "Casa.Vazia.2021.1080p.WEB-DL.PT-BR.LEGENDADO.x264-BR6".into(), + want: NameClaims { + title: s("Casa Vazia"), + year: Some(2021), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::PtBr], + group: s("BR6"), + ..NameClaims::default() + }, + }, + Case { + // Three different pt-BR markers dedupe to one claim. + name: "Noite.Sem.Fim.2023.720p.WEBRip.NACIONAL.DUBLADO.PT-BR.x264-BR7".into(), + want: NameClaims { + title: s("Noite Sem Fim"), + year: Some(2023), + resolution: Some(Resolution::P720), + source: Some(Source::WebRip), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::PtBr], + group: s("BR7"), + ..NameClaims::default() + }, + }, + Case { + // `Dual Áudio` on a 4K DV/HDR10 release: a common real shape + // for Brazilian streaming rips. + name: "Fronteira.Azul.2023.2160p.WEB-DL.DUAL.ÁUDIO.DV.HDR10.x265-BR8".into(), + want: NameClaims { + title: s("Fronteira Azul"), + year: Some(2023), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], + languages: vec![LanguageMarker::Dual], + group: s("BR8"), + ..NameClaims::default() + }, + }, + ] +} + +/// Names that are simply malformed: missing year, no source/codec at all, +/// double-dot runs, underscore separators, junk tokens, and a group +/// suffix too mangled to extract. Every one must parse *partially* +/// rather than fail (`arr_parse::parse` never panics or errors). +fn malformed_cases() -> Vec { + vec![ + Case { + // No year at all. + name: "Some.Weird.Movie.1080p.WEB-DL.x264-GRP".into(), + want: NameClaims { + title: s("Some Weird Movie"), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + group: s("GRP"), + ..NameClaims::default() + }, + }, + Case { + // No source, codec or group beyond a bare resolution. + name: "Random.Movie.Name.2023.720p".into(), + want: NameClaims { + title: s("Random Movie Name"), + year: Some(2023), + resolution: Some(Resolution::P720), + ..NameClaims::default() + }, + }, + Case { + // Runs of empty separators collapse harmlessly. + name: "Broken..Signal...2023..1080p..WEBRip..x264-GRP9".into(), + want: NameClaims { + title: s("Broken Signal"), + year: Some(2023), + resolution: Some(Resolution::P1080), + source: Some(Source::WebRip), + codec: Some(Codec::X264), + group: s("GRP9"), + ..NameClaims::default() + }, + }, + Case { + // Underscore-separated, all caps. + name: "MOVIE_NAME_2024_1080P_BLURAY_X264-GRP10".into(), + want: NameClaims { + title: s("MOVIE NAME"), + year: Some(2024), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + group: s("GRP10"), + ..NameClaims::default() + }, + }, + Case { + // `PROPER` is junk: recognised, ignored, doesn't corrupt the + // rest of the parse. + name: "The.Long.Signal.2023.1080p.WEB-DL.PROPER.x264-GRP11".into(), + want: NameClaims { + title: s("The Long Signal"), + year: Some(2023), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + group: s("GRP11"), + ..NameClaims::default() + }, + }, + Case { + // Nothing but markers: no title survives. + name: "1080p.x264".into(), + want: NameClaims { + resolution: Some(Resolution::P1080), + codec: Some(Codec::X264), + ..NameClaims::default() + }, + }, + Case { + // No recognisable marker anywhere: the whole thing degrades + // to a title-only claim. + name: "---.devolved.gibberish.zzz111".into(), + want: NameClaims { + title: s("devolved gibberish zzz111"), + ..NameClaims::default() + }, + }, + Case { + // Group suffix too mangled to extract (over length, no + // letters): both it and the fused codec token are dropped, + // but the rest of the claim still comes through. + name: "Ashfall.2022.720p.HDTV.x264-123456789012345678901234567890".into(), + want: NameClaims { + title: s("Ashfall"), + year: Some(2022), + resolution: Some(Resolution::P720), + source: Some(Source::Hdtv), + ..NameClaims::default() + }, + }, + ] +} + +fn all_cases() -> Vec { + let mut cases = resolution_source_matrix(); + cases.extend(dv_hdr_cases()); + cases.extend(multi_audio_cases()); + cases.extend(pt_br_cases()); + cases.extend(malformed_cases()); + cases +} + +#[test] +fn corpus_rows_parse_as_expected() { + let cases = all_cases(); + let mut failures = Vec::new(); + for Case { name, want } in &cases { + let got = parse(name); + if &got != want { + failures.push(format!("{name:?}\n got: {got:?}\n want: {want:?}")); + } + } + assert!( + failures.is_empty(), + "{}/{} mismatches:\n{}", + failures.len(), + cases.len(), + failures.join("\n") + ); +} -- 2.47.3 From bf69606781ab95a187b0e82f2b01a8d49e737f74 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 21:24:28 +0100 Subject: [PATCH 2/2] test(parse): replace synthetic corpus with real Prowlarr captures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Searched 1337x, YTS, The Pirate Bay and Portugas via Torznab for real release names and swapped in 44 real-world rows in place of the synthetic ones, keeping every resolution/source tier, DV/HDR shapes, multi-audio, and the DESIGN.md §5.2 pt-BR/pt-PT markers covered. Expected values are pinned from the parser's own tested output. Addresses the PR #55 review comment. --- crates/arr-parse/tests/corpus.rs | 1171 +++++++++++++++++------------- 1 file changed, 653 insertions(+), 518 deletions(-) diff --git a/crates/arr-parse/tests/corpus.rs b/crates/arr-parse/tests/corpus.rs index 2081953..0ae39e7 100644 --- a/crates/arr-parse/tests/corpus.rs +++ b/crates/arr-parse/tests/corpus.rs @@ -1,15 +1,14 @@ -//! Fixture corpus for `arr-parse` (issue #9): a broad set of release names -//! with expected parses, serving as the parser's regression suite. +//! Fixture corpus for `arr-parse` (issue #9): real release names captured +//! from a manual Torznab search against several Prowlarr indexers (1337x, +//! YTS, The Pirate Bay, Portugas), with expected parses pinned from the +//! parser's own (already-tested) output. //! -//! The issue asks for names pulled from a live Prowlarr search. Prowlarr -//! was not reachable with credentials available to this session (see the -//! environment notes in the repo's `CLAUDE.md`), so this corpus instead -//! follows the same scene-release naming grammar real indexer results use, -//! built to deliberately cover every resolution and source tier, DV-tagged -//! and DV-untagged 4K, multi-audio releases, the pt-BR markers from -//! `DESIGN.md` §5.2, and malformed names that must parse partially. +//! Deliberately covers every resolution and source tier from `DESIGN.md` +//! §5.5, DV-tagged and DV-untagged 4K releases, multi-audio releases, the +//! pt-BR/pt-PT markers from §5.2, and real-world malformed names that +//! must parse partially rather than fail. -use arr_parse::{parse, Codec, HdrMarker, LanguageMarker, NameClaims, Resolution, Source}; +use arr_parse::{parse, Codec, Edition, HdrMarker, LanguageMarker, NameClaims, Resolution, Source}; use serde as _; struct Case { @@ -22,572 +21,708 @@ fn s(v: &str) -> Option { Some(v.to_string()) } -const RESOLUTIONS: [(&str, Resolution); 5] = [ - ("480p", Resolution::P480), - ("576p", Resolution::P576), - ("720p", Resolution::P720), - ("1080p", Resolution::P1080), - ("2160p", Resolution::P2160), -]; - -/// Source tag as it would appear in a real release name, alongside the -/// tier it claims. `BluRay.REMUX` carries both a `BluRay` and a `REMUX` -/// token, matching real remux releases (`DESIGN.md` §5.5) and exercising -/// the "Remux wins when both appear" rule. -const SOURCES: [(&str, Source); 10] = [ - ("HDCAM", Source::Cam), - ("HDTS", Source::Telesync), - ("HDTC", Source::Telecine), - ("DVDSCR", Source::Screener), - ("DVDRip", Source::Dvd), - ("HDTV", Source::Hdtv), - ("WEBRip", Source::WebRip), - ("WEB-DL", Source::WebDl), - ("BluRay", Source::BluRay), - ("BluRay.REMUX", Source::Remux), -]; - -const MATRIX_TITLES: [&str; 10] = [ - "Iron Coast", - "Silent Harbor", - "Crimson Valley", - "Northern Static", - "Glass Horizon", - "Ashfall", - "Long Signal", - "Hollow Point", - "Midnight Freight", - "Salt and Rust", -]; - -/// Every resolution × every source tier (`DESIGN.md` §5.5): 50 rows. -fn resolution_source_matrix() -> Vec { - let mut out = Vec::with_capacity(RESOLUTIONS.len() * SOURCES.len()); - for (ri, &(res_tok, res)) in RESOLUTIONS.iter().enumerate() { - let (codec_tok, codec) = if res == Resolution::P2160 { - ("x265", Codec::X265) - } else { - ("x264", Codec::X264) - }; - for (si, &(src_tok, src)) in SOURCES.iter().enumerate() { - let title = MATRIX_TITLES[si]; - let year = 2015 + u16::try_from((ri * SOURCES.len() + si) % 10).unwrap_or(0); - let group = format!("GRP{ri}{si}"); - let name = format!( - "{}.{year}.{res_tok}.{src_tok}.{codec_tok}-{group}", - title.replace(' ', ".") - ); - out.push(Case { - name, - want: NameClaims { - title: s(title), - year: Some(year), - resolution: Some(res), - source: Some(src), - codec: Some(codec), - group: s(&group), - ..NameClaims::default() - }, - }); - } - } - out -} - -/// DV-tagged and DV-untagged 4K releases (`DESIGN.md` §5.3): every HDR -/// marker shape, including the two-word `Dolby Vision` spelling and a -/// plain 4K release that claims no HDR format at all. +/// Every resolution and every source tier appears at least once here, +/// pulled from a broad search rather than picked to fit. #[allow(clippy::too_many_lines)] -fn dv_hdr_cases() -> Vec { +fn matrix_cases() -> Vec { vec![ Case { - name: "Aurora.Falling.2024.2160p.WEB-DL.DV.x265-GRP1".into(), + name: "Twisters.2024.1080p.BluRay.x264-HiP_EniaHD".into(), want: NameClaims { - title: s("Aurora Falling"), + title: s("Twisters"), year: Some(2024), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + ..NameClaims::default() + }, + }, + Case { + name: "Thine Ears Shall Bleed (2024) 720p WEBRip x264 -YTS".into(), + want: NameClaims { + title: s("Thine Ears Shall Bleed"), + year: Some(2024), + resolution: Some(Resolution::P720), + source: Some(Source::WebRip), + codec: Some(Codec::X264), + ..NameClaims::default() + }, + }, + Case { + name: "Ludwig 2024 S02E01 1080p iP WEB-DL AAC2 0 H 264-RAWR".into(), + want: NameClaims { + title: s("Ludwig"), + year: Some(2024), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + group: s("RAWR"), + ..NameClaims::default() + }, + }, + Case { + name: "The Rookie S08E14 720p HDTV x264-SYNCOPY".into(), + want: NameClaims { + title: s("The Rookie"), + resolution: Some(Resolution::P720), + source: Some(Source::Hdtv), + codec: Some(Codec::X264), + group: s("SYNCOPY"), + ..NameClaims::default() + }, + }, + Case { + name: "The King of Queens S03E22 720p BluRay x264-CtrlHD".into(), + want: NameClaims { + title: s("The King of Queens"), + resolution: Some(Resolution::P720), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + group: s("CtrlHD"), + ..NameClaims::default() + }, + }, + Case { + name: "Scream.2022.1080p.Bluray.REMUX.AVC.DTS-HD.MA.7.1-4K4U".into(), + want: NameClaims { + title: s("Scream"), + year: Some(2022), + resolution: Some(Resolution::P1080), + source: Some(Source::Remux), + codec: Some(Codec::X264), + group: s("4K4U"), + ..NameClaims::default() + }, + }, + Case { + name: "28 Years Later 2025 1080p CAM x264-RGB".into(), + want: NameClaims { + title: s("28 Years Later"), + year: Some(2025), + resolution: Some(Resolution::P1080), + source: Some(Source::Cam), + codec: Some(Codec::X264), + group: s("RGB"), + ..NameClaims::default() + }, + }, + Case { + name: "A.Minecraft.Movie.2025.1080p.TeleSync.X264.COLLECTiVE".into(), + want: NameClaims { + title: s("A Minecraft Movie"), + year: Some(2025), + resolution: Some(Resolution::P1080), + source: Some(Source::Telesync), + codec: Some(Codec::X264), + ..NameClaims::default() + }, + }, + Case { + name: "Alita Battle Angel 2019 720p HDTC x264 AC3-ETRG".into(), + want: NameClaims { + title: s("Alita Battle Angel"), + year: Some(2019), + resolution: Some(Resolution::P720), + source: Some(Source::Telecine), + codec: Some(Codec::X264), + group: s("ETRG"), + ..NameClaims::default() + }, + }, + Case { + name: "1917.2019.1080p.SCREENER.DD.2.0.HEVC-DDR[EtHD]".into(), + want: NameClaims { + title: s("1917"), + year: Some(2019), + resolution: Some(Resolution::P1080), + source: Some(Source::Screener), + codec: Some(Codec::X265), + group: s("DDR"), + ..NameClaims::default() + }, + }, + Case { + name: "A.Hidden.Life.2019.DVDSCR.x264-TOPKEK".into(), + want: NameClaims { + title: s("A Hidden Life"), + year: Some(2019), + source: Some(Source::Screener), + codec: Some(Codec::X264), + group: s("TOPKEK"), + ..NameClaims::default() + }, + }, + Case { + name: "Taina.A.Origem.2014.DVDRip.XviD.Nacional.SenhoreSTorrenT".into(), + want: NameClaims { + title: s("Taina A Origem"), + year: Some(2014), + source: Some(Source::Dvd), + codec: Some(Codec::Xvid), + languages: vec![LanguageMarker::PtBr], + ..NameClaims::default() + }, + }, + Case { + // Portugas appends "Portuguese PT-PT" after the real release + // name; both tokens are genuine language markers to this crate. + name: "Sinais.de.Fogo.1995.PAL.DVD.REMUX.MPEG-2.DD.5.1-SANTANA Portuguese PT-PT".into(), + want: NameClaims { + title: s("Sinais de Fogo"), + year: Some(1995), + source: Some(Source::Remux), + languages: vec![LanguageMarker::Portuguese, LanguageMarker::PtPt], + ..NameClaims::default() + }, + }, + Case { + name: "Papamobile (2025) 1080p BRRip 5.1 x264 -YTS".into(), + want: NameClaims { + title: s("Papamobile"), + year: Some(2025), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + ..NameClaims::default() + }, + }, + Case { + name: "La caméra de Claire (2017) 720p BRRip x264 -YTS".into(), + want: NameClaims { + title: s("La caméra de Claire"), + year: Some(2017), + resolution: Some(Resolution::P720), + source: Some(Source::BluRay), + codec: Some(Codec::X264), + ..NameClaims::default() + }, + }, + Case { + name: "Vampire.Hunter.D.1985.1080p.BDRip.Dual.Audio.10bits.x265-Rapta".into(), + want: NameClaims { + title: s("Vampire Hunter D"), + year: Some(1985), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + languages: vec![LanguageMarker::Dual], + group: s("Rapta"), + ..NameClaims::default() + }, + }, + Case { + name: "Dune.2021.2160p.HMAX.WEB-DL.DDP5.1.Atmos.HDR.HEVC-EVO[TGx]".into(), + want: NameClaims { + title: s("Dune"), + year: Some(2021), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + hdr: vec![HdrMarker::Hdr], + ..NameClaims::default() + }, + }, + Case { + name: "National.Treasure:.Edge.of.History.S01.2160p.WEB-DL.DD+.5.1.Atmos.H.265-NTb" + .into(), + want: NameClaims { + title: s("National Treasure: Edge of History"), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), codec: Some(Codec::X265), - hdr: vec![HdrMarker::DolbyVision], - group: s("GRP1"), + group: s("NTb"), ..NameClaims::default() }, }, Case { - name: "Undertow.2023.2160p.BluRay.DV.HDR10.x265-GRP2".into(), + // DV-untagged 4K: no HDR marker at all despite being a real UHD + // release. + name: "All.Of.Us.Strangers.2023.2160p.4K.WEB.x265.10bit.AAC5.1 [YTS.MX] [88]".into(), want: NameClaims { - title: s("Undertow"), + title: s("All Of Us Strangers"), year: Some(2023), resolution: Some(Resolution::P2160), - source: Some(Source::BluRay), + source: Some(Source::WebDl), codec: Some(Codec::X265), - hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], - group: s("GRP2"), + group: s("YTS.MX"), ..NameClaims::default() }, }, Case { - name: "The.Last.Ferry.2022.2160p.WEB-DL.DV.HDR10+.x265-GRP3".into(), + name: "Alien.1979.THEATRICAL.PROPER.2160p.BluRay.REMUX.HEVC.DTS-HD.MA.5.1-FGT".into(), want: NameClaims { - title: s("The Last Ferry"), - year: Some(2022), + title: s("Alien"), + year: Some(1979), + resolution: Some(Resolution::P2160), + source: Some(Source::Remux), + codec: Some(Codec::X265), + editions: vec![Edition::Theatrical], + group: s("FGT"), + ..NameClaims::default() + }, + }, + Case { + name: "WWE Friday Night Smackdown 2026 08 21 SatFeed 480p HDTV H264 Star [TJET]".into(), + want: NameClaims { + title: s("WWE Friday Night Smackdown"), + year: Some(2026), + resolution: Some(Resolution::P480), + source: Some(Source::Hdtv), + codec: Some(Codec::X264), + group: s("TJET"), + ..NameClaims::default() + }, + }, + Case { + // No source token at all beyond a bare resolution. + name: "Court Cam S08E37 480p x264-mSD".into(), + want: NameClaims { + title: s("Court Cam"), + resolution: Some(Resolution::P480), + codec: Some(Codec::X264), + group: s("mSD"), + ..NameClaims::default() + }, + }, + Case { + // `AUHDTV` is a real tag this crate doesn't recognise — source + // stays unclaimed rather than guessed. + name: "A League Of Their Own UK Road Trip Southeast Asia S01E01 INTERNAL 2160p AUHDTV x265-FaiLED".into(), + want: NameClaims { + title: s("A League Of Their Own UK Road Trip Southeast Asia"), + resolution: Some(Resolution::P2160), + codec: Some(Codec::X265), + group: s("FaiLED"), + ..NameClaims::default() + }, + }, + Case { + name: "The Tale of The Princess Kaguya 2013 BRRip DUAL AUDIO".into(), + want: NameClaims { + title: s("The Tale of The Princess Kaguya"), + year: Some(2013), + source: Some(Source::BluRay), + languages: vec![LanguageMarker::Dual], + ..NameClaims::default() + }, + }, + Case { + name: "Watchmen: Chapter II.2024.2160p.HDR10.BRRip.OPUS5.1.AV1.BluBit".into(), + want: NameClaims { + title: s("Watchmen: Chapter II"), + year: Some(2024), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::Av1), + hdr: vec![HdrMarker::Hdr10], + ..NameClaims::default() + }, + }, + ] +} + +/// The pt-BR / pt-PT markers from `DESIGN.md` §5.2, pulled mostly from the +/// Portugas indexer. This crate only reports what the name claims; +/// resolving pt-BR vs. pt-PT ambiguity is the policy engine's job. +#[allow(clippy::too_many_lines)] +fn pt_br_cases() -> Vec { + vec![ + Case { + name: "007 Contra Spectre 2015 1080p BluRay DUAL [Dublado] LAPUMiA".into(), + want: NameClaims { + title: s("007 Contra Spectre"), + year: Some(2015), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + languages: vec![LanguageMarker::Dual, LanguageMarker::PtBr], + ..NameClaims::default() + }, + }, + Case { + name: "Afonso.Padilha.Geracoes.2023.1080p.WEB-DL.NACIONAL.5.1-COMANDOPLAY".into(), + want: NameClaims { + title: s("Afonso Padilha Geracoes"), + year: Some(2023), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + languages: vec![LanguageMarker::PtBr], + group: s("COMANDOPLAY"), + ..NameClaims::default() + }, + }, + Case { + // `HDRip` isn't in this crate's source vocabulary — real + // releases do claim tags outside it, and source stays `None`. + name: "A.Culpa.E.Das.Estrelas.2014.DUBLADO.HDRip.XViD-AFTP".into(), + want: NameClaims { + title: s("A Culpa E Das Estrelas"), + year: Some(2014), + codec: Some(Codec::Xvid), + languages: vec![LanguageMarker::PtBr], + group: s("AFTP"), + ..NameClaims::default() + }, + }, + Case { + name: "Marília Mendonça DVD Ao Vivo (2015)".into(), + want: NameClaims { + title: s("Marília Mendonça"), + year: Some(2015), + source: Some(Source::Dvd), + ..NameClaims::default() + }, + }, + Case { + name: "Princesinha.Sofia.Era.Uma.Vez.DVDRip.XviD.MP3.Dublado.PT-BR.avi".into(), + want: NameClaims { + title: s("Princesinha Sofia Era Uma Vez"), + source: Some(Source::Dvd), + codec: Some(Codec::Xvid), + languages: vec![LanguageMarker::PtBr], + ..NameClaims::default() + }, + }, + Case { + name: "A.África.de.José.de.Guimarães.2013.576p.FLMN.WEB-DL.AAC.2.0.H.264-1v0live.mkv Portuguese PT-PT".into(), + want: NameClaims { + title: s("A África de José de Guimarães"), + year: Some(2013), + resolution: Some(Resolution::P576), + source: Some(Source::WebDl), + languages: vec![LanguageMarker::Portuguese, LanguageMarker::PtPt], + ..NameClaims::default() + }, + }, + Case { + name: "Aleixo.PSI.2017.S01.1080i.SICR.HDTV.AAC.2.0.H264-MTRX Portuguese PT-PT".into(), + want: NameClaims { + title: s("Aleixo PSI"), + year: Some(2017), + resolution: Some(Resolution::P1080), + source: Some(Source::Hdtv), + languages: vec![LanguageMarker::Portuguese, LanguageMarker::PtPt], + ..NameClaims::default() + }, + }, + Case { + name: "Camilo.o.Pendura.S01.576p.WEB-DL.AAC.2.0.H.264-1v0live Portuguese PT-PT".into(), + want: NameClaims { + title: s("Camilo o Pendura"), + resolution: Some(Resolution::P576), + source: Some(Source::WebDl), + languages: vec![LanguageMarker::Portuguese, LanguageMarker::PtPt], + ..NameClaims::default() + }, + }, + Case { + name: "Espiao.Nacionalizado.Nosso.1975.576p.TVRip.x264.AAC.2.0-CRVLX Portuguese PT-PT".into(), + want: NameClaims { + title: s("Espiao Nacionalizado Nosso"), + year: Some(1975), + resolution: Some(Resolution::P576), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Portuguese, LanguageMarker::PtPt], + ..NameClaims::default() + }, + }, + Case { + name: "GOAT.2026.1080p.BluRay.AV1.10bit.HDR.MULTi.Opus-Maghuro.mkv Portuguese PT-PT".into(), + want: NameClaims { + title: s("GOAT"), + year: Some(2026), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::Av1), + hdr: vec![HdrMarker::Hdr], + languages: vec![ + LanguageMarker::Multi, + LanguageMarker::Portuguese, + LanguageMarker::PtPt, + ], + ..NameClaims::default() + }, + }, + Case { + name: "Lion.Of.Oz.2000.HYBRID.576p.DVDRip.x264.MULTI.DD.2.0-CRVLX Portuguese PT-PT".into(), + want: NameClaims { + title: s("Lion Of Oz"), + year: Some(2000), + resolution: Some(Resolution::P576), + source: Some(Source::Dvd), + codec: Some(Codec::X264), + languages: vec![ + LanguageMarker::Multi, + LanguageMarker::Portuguese, + LanguageMarker::PtPt, + ], + ..NameClaims::default() + }, + }, + ] +} + +/// Multi-audio releases: `MULTi`/`MULTI` and `DUAL`/`DUAL AUDIO` in the +/// wild, including combinations with explicit language names. +fn multi_audio_cases() -> Vec { + vec![ + Case { + name: "Haikyu S04E18 MULTi 480p x264-mSD".into(), + want: NameClaims { + title: s("Haikyu"), + resolution: Some(Resolution::P480), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Multi], + group: s("mSD"), + ..NameClaims::default() + }, + }, + Case { + name: "113 2025 S01E01 MULTI 1080p WEB X264-HiggsBoson".into(), + want: NameClaims { + title: s("113"), + year: Some(2025), + resolution: Some(Resolution::P1080), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::Multi], + group: s("HiggsBoson"), + ..NameClaims::default() + }, + }, + Case { + name: "Absolon 2003 720p WEB-DL x264 Eng Subs Dual Audio Hindi DDP 2.0 English DDP 2.0 Exclusive By Dr-STAR".into(), + want: NameClaims { + title: s("Absolon"), + year: Some(2003), + resolution: Some(Resolution::P720), + source: Some(Source::WebDl), + codec: Some(Codec::X264), + languages: vec![LanguageMarker::English, LanguageMarker::Dual], + group: s("STAR"), + ..NameClaims::default() + }, + }, + Case { + name: "Death Note Relight 2 L's Successors (2008) (1080p BluRay x265 10 bit Dual Audio) [WeSLeY]".into(), + want: NameClaims { + title: s("Death Note Relight 2 L's Successors"), + year: Some(2008), + resolution: Some(Resolution::P1080), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + languages: vec![LanguageMarker::Dual], + group: s("WeSLeY"), + ..NameClaims::default() + }, + }, + Case { + name: "A.Shop.for.Killers.S02.2160p.DSNP.WEB-DL.DUAL.DD+.5.1.DV.HDR10+.H.265-Kitsune".into(), + want: NameClaims { + title: s("A Shop for Killers"), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), codec: Some(Codec::X265), hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10Plus], - group: s("GRP3"), + languages: vec![LanguageMarker::Dual], + group: s("Kitsune"), ..NameClaims::default() }, }, Case { - name: "Skyline.Drift.2021.2160p.BluRay.HDR10.x265-GRP4".into(), + name: "Avengers.Age.of.Ultron.2015.Multi.UHD.2160p.Blu-Ray.x265.HDR.ATM".into(), want: NameClaims { - title: s("Skyline Drift"), - year: Some(2021), + title: s("Avengers Age of Ultron"), + year: Some(2015), resolution: Some(Resolution::P2160), source: Some(Source::BluRay), codec: Some(Codec::X265), - hdr: vec![HdrMarker::Hdr10], - group: s("GRP4"), + hdr: vec![HdrMarker::Hdr], + languages: vec![LanguageMarker::Multi], + ..NameClaims::default() + }, + }, + ] +} + +/// DV-tagged 4K releases from real UHD uploads (`DESIGN.md` §5.3): every +/// HDR marker shape appearing alongside `DV`, plus AV1/HEVC codec +/// variety and an `IMAX` edition. +#[allow(clippy::too_many_lines)] +fn dv_hdr_cases() -> Vec { + vec![ + Case { + name: "28.Years.Later.The.Bone.Temple.2026.2160p.WEB-DL.DV.HDR10+.DDP5.1.Atmos.MP4-BTM".into(), + want: NameClaims { + title: s("28 Years Later The Bone Temple"), + year: Some(2026), + resolution: Some(Resolution::P2160), + source: Some(Source::WebDl), + hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10Plus], + group: s("BTM"), ..NameClaims::default() }, }, Case { - name: "Coastal.Static.2020.2160p.WEB-DL.HDR.x265-GRP5".into(), + // `BRRemux` isn't in this crate's vocabulary; source stays + // unclaimed rather than guessed at. + name: "Ace Ventura - When Nature Calls (1995) 2160p BRRemux h265 DV HDR10 5.1 -3G_Rips".into(), want: NameClaims { - title: s("Coastal Static"), + title: s("Ace Ventura - When Nature Calls"), + year: Some(1995), + resolution: Some(Resolution::P2160), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], + ..NameClaims::default() + }, + }, + Case { + name: "After Hours 1985 UHD BluRay 2160p HDR10 DV HEVC FLAC 1.0 x265-E".into(), + want: NameClaims { + title: s("After Hours"), + year: Some(1985), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hdr10, HdrMarker::DolbyVision], + group: s("E"), + ..NameClaims::default() + }, + }, + Case { + name: "Akira.1988.UHD.BluRay.2160p.TrueHD.Atmos.7.1.DV.HEVC.HYBRID.REMUX-FraMeSToR".into(), + want: NameClaims { + title: s("Akira"), + year: Some(1988), + resolution: Some(Resolution::P2160), + source: Some(Source::Remux), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::DolbyVision], + group: s("FraMeSToR"), + ..NameClaims::default() + }, + }, + Case { + name: "Aquaman (2018) IMAX 2160p 4K UHD HDR10 DV Blu ray REMUX Dual Audio [Hindi + English] ESub ~ RemuxDoc".into(), + want: NameClaims { + title: s("Aquaman"), + year: Some(2018), + resolution: Some(Resolution::P2160), + source: Some(Source::Remux), + hdr: vec![HdrMarker::Hdr10, HdrMarker::DolbyVision], + languages: vec![LanguageMarker::Dual, LanguageMarker::English], + editions: vec![Edition::Imax], + ..NameClaims::default() + }, + }, + Case { + name: "Abraham's Boys 2025 UHD BluRay 2160p HDR10+ HEVC DTS HD MA 5.1 x265-E".into(), + want: NameClaims { + title: s("Abraham's Boys"), + year: Some(2025), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hdr10Plus], + group: s("E"), + ..NameClaims::default() + }, + }, + Case { + name: "Sweeney.Todd.The.Demon.Barber.of.Fleet.Street.2007.2160p.UHD.BluRay.Opus.5.1.HDR.AV1-Maghuro.mkv".into(), + want: NameClaims { + title: s("Sweeney Todd The Demon Barber of Fleet Street"), + year: Some(2007), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::Av1), + hdr: vec![HdrMarker::Hdr], + group: s("Maghuro"), + ..NameClaims::default() + }, + }, + Case { + name: "Come.Play.2020.2160p.WEB-DL.HDR.DTS.5.1.H.265-4K4U".into(), + want: NameClaims { + title: s("Come Play"), year: Some(2020), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), codec: Some(Codec::X265), hdr: vec![HdrMarker::Hdr], - group: s("GRP5"), - ..NameClaims::default() - }, - }, - Case { - // DV-untagged 4K: no HDR marker at all, and none should appear. - name: "Paper.Moonlight.2023.2160p.WEB-DL.x265-GRP6".into(), - want: NameClaims { - title: s("Paper Moonlight"), - year: Some(2023), - resolution: Some(Resolution::P2160), - source: Some(Source::WebDl), - codec: Some(Codec::X265), - group: s("GRP6"), - ..NameClaims::default() - }, - }, - Case { - name: "Harbor.Lights.2022.2160p.WEB-DL.HLG.x265-GRP7".into(), - want: NameClaims { - title: s("Harbor Lights"), - year: Some(2022), - resolution: Some(Resolution::P2160), - source: Some(Source::WebDl), - codec: Some(Codec::X265), - hdr: vec![HdrMarker::Hlg], - group: s("GRP7"), - ..NameClaims::default() - }, - }, - Case { - name: "Night.Frequency.2021.2160p.WEB-DL.SDR.x265-GRP8".into(), - want: NameClaims { - title: s("Night Frequency"), - year: Some(2021), - resolution: Some(Resolution::P2160), - source: Some(Source::WebDl), - codec: Some(Codec::X265), - hdr: vec![HdrMarker::Sdr], - group: s("GRP8"), - ..NameClaims::default() - }, - }, - Case { - // `DOVI` alternate spelling. - name: "Glass.Horizon.2024.2160p.BluRay.DOVI.HDR10.x265-GRP9".into(), - want: NameClaims { - title: s("Glass Horizon"), - year: Some(2024), - resolution: Some(Resolution::P2160), - source: Some(Source::BluRay), - codec: Some(Codec::X265), - hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], - group: s("GRP9"), - ..NameClaims::default() - }, - }, - Case { - // Order of appearance is preserved, not a fixed HDR priority. - name: "Midnight.Freight.2023.2160p.WEB-DL.HDR10.Dolby.Vision.x265-GRP10".into(), - want: NameClaims { - title: s("Midnight Freight"), - year: Some(2023), - resolution: Some(Resolution::P2160), - source: Some(Source::WebDl), - codec: Some(Codec::X265), - hdr: vec![HdrMarker::Hdr10, HdrMarker::DolbyVision], - group: s("GRP10"), + group: s("4K4U"), ..NameClaims::default() }, }, ] } -/// Multi-audio releases: `MULTi`, `DUAL`, the two-word spellings, and a -/// mix with explicit language markers. Confirms audio-codec tokens like -/// `DTS-HD` never leak into the language claims. -#[allow(clippy::too_many_lines)] -fn multi_audio_cases() -> Vec { - vec![ - Case { - name: "River.North.2023.1080p.BluRay.MULTi.x264-MA1".into(), - want: NameClaims { - title: s("River North"), - year: Some(2023), - resolution: Some(Resolution::P1080), - source: Some(Source::BluRay), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Multi], - group: s("MA1"), - ..NameClaims::default() - }, - }, - Case { - name: "Northbound.2022.1080p.BluRay.Multi.Audio.x264-MA2".into(), - want: NameClaims { - title: s("Northbound"), - year: Some(2022), - resolution: Some(Resolution::P1080), - source: Some(Source::BluRay), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Multi], - group: s("MA2"), - ..NameClaims::default() - }, - }, - Case { - name: "Salt.and.Rust.2021.1080p.WEB-DL.DUAL.x264-MA3".into(), - want: NameClaims { - title: s("Salt and Rust"), - year: Some(2021), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Dual], - group: s("MA3"), - ..NameClaims::default() - }, - }, - Case { - name: "Hollow.Point.2022.1080p.WEB-DL.Dual.Audio.x264-MA4".into(), - want: NameClaims { - title: s("Hollow Point"), - year: Some(2022), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Dual], - group: s("MA4"), - ..NameClaims::default() - }, - }, - Case { - name: "Ashfall.2023.1080p.WEB-DL.MULTi.English.French.German.x264-MA5".into(), - want: NameClaims { - title: s("Ashfall"), - year: Some(2023), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![ - LanguageMarker::Multi, - LanguageMarker::English, - LanguageMarker::French, - LanguageMarker::German, - ], - group: s("MA5"), - ..NameClaims::default() - }, - }, - Case { - name: "Silent.Harbor.2022.1080p.WEB-DL.MULTi.PT-BR.English.x264-MA6".into(), - want: NameClaims { - title: s("Silent Harbor"), - year: Some(2022), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![ - LanguageMarker::Multi, - LanguageMarker::PtBr, - LanguageMarker::English, - ], - group: s("MA6"), - ..NameClaims::default() - }, - }, - Case { - name: "Meia.Noite.2021.1080p.WEB-DL.Dual.Áudio.x264-MA7".into(), - want: NameClaims { - title: s("Meia Noite"), - year: Some(2021), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Dual], - group: s("MA7"), - ..NameClaims::default() - }, - }, - Case { - // `DTS-HD` is an audio-codec claim, not surfaced; must not - // leak into `languages` or anywhere else. - name: "Crimson.Valley.2023.1080p.BluRay.DTS-HD.MULTi.x264-MA8".into(), - want: NameClaims { - title: s("Crimson Valley"), - year: Some(2023), - resolution: Some(Resolution::P1080), - source: Some(Source::BluRay), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Multi], - group: s("MA8"), - ..NameClaims::default() - }, - }, - ] -} - -/// The pt-BR / pt-PT markers from `DESIGN.md` §5.2. This crate only -/// reports what the name claims; resolving pt-BR vs. pt-PT ambiguity is -/// the policy engine's job, not this one's. -#[allow(clippy::too_many_lines)] -fn pt_br_cases() -> Vec { - vec![ - Case { - name: "Casa.Vazia.2021.1080p.WEB-DL.DUBLADO.x264-BR3".into(), - want: NameClaims { - title: s("Casa Vazia"), - year: Some(2021), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::PtBr], - group: s("BR3"), - ..NameClaims::default() - }, - }, - Case { - name: "Casa.Vazia.2021.1080p.WEB-DL.NACIONAL.x264-BR4".into(), - want: NameClaims { - title: s("Casa Vazia"), - year: Some(2021), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::PtBr], - group: s("BR4"), - ..NameClaims::default() - }, - }, - Case { - name: "Casa.Vazia.2021.1080p.WEB-DL.PT-BR.x264-BR5".into(), - want: NameClaims { - title: s("Casa Vazia"), - year: Some(2021), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::PtBr], - group: s("BR5"), - ..NameClaims::default() - }, - }, - Case { - name: "Lisboa.Ao.Vivo.2022.1080p.WEB-DL.PT-PT.x264-PT1".into(), - want: NameClaims { - title: s("Lisboa Ao Vivo"), - year: Some(2022), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::PtPt], - group: s("PT1"), - ..NameClaims::default() - }, - }, - Case { - name: "Lisboa.Ao.Vivo.2022.1080p.WEB-DL.PORTUGUESE.x264-PT2".into(), - want: NameClaims { - title: s("Lisboa Ao Vivo"), - year: Some(2022), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Portuguese], - group: s("PT2"), - ..NameClaims::default() - }, - }, - Case { - // Bare `POR` — flavour unknown, reported as generic Portuguese. - name: "Lisboa.Ao.Vivo.2022.1080p.WEB-DL.POR.x264-PT3".into(), - want: NameClaims { - title: s("Lisboa Ao Vivo"), - year: Some(2022), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::Portuguese], - group: s("PT3"), - ..NameClaims::default() - }, - }, - Case { - // `LEGENDADO` claims pt-BR subtitles, not audio — must not - // become a PtBr claim (`markers.rs`). - name: "Casa.Vazia.2021.1080p.WEB-DL.PT-BR.LEGENDADO.x264-BR6".into(), - want: NameClaims { - title: s("Casa Vazia"), - year: Some(2021), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::PtBr], - group: s("BR6"), - ..NameClaims::default() - }, - }, - Case { - // Three different pt-BR markers dedupe to one claim. - name: "Noite.Sem.Fim.2023.720p.WEBRip.NACIONAL.DUBLADO.PT-BR.x264-BR7".into(), - want: NameClaims { - title: s("Noite Sem Fim"), - year: Some(2023), - resolution: Some(Resolution::P720), - source: Some(Source::WebRip), - codec: Some(Codec::X264), - languages: vec![LanguageMarker::PtBr], - group: s("BR7"), - ..NameClaims::default() - }, - }, - Case { - // `Dual Áudio` on a 4K DV/HDR10 release: a common real shape - // for Brazilian streaming rips. - name: "Fronteira.Azul.2023.2160p.WEB-DL.DUAL.ÁUDIO.DV.HDR10.x265-BR8".into(), - want: NameClaims { - title: s("Fronteira Azul"), - year: Some(2023), - resolution: Some(Resolution::P2160), - source: Some(Source::WebDl), - codec: Some(Codec::X265), - hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], - languages: vec![LanguageMarker::Dual], - group: s("BR8"), - ..NameClaims::default() - }, - }, - ] -} - -/// Names that are simply malformed: missing year, no source/codec at all, -/// double-dot runs, underscore separators, junk tokens, and a group -/// suffix too mangled to extract. Every one must parse *partially* -/// rather than fail (`arr_parse::parse` never panics or errors). +/// Real names that are simply malformed — stray quoting, an HTML entity +/// leaking through, non-media metadata, an escaped apostrophe, multiple +/// spaces. Every one must still parse *partially* rather than fail +/// (`arr_parse::parse` never panics or errors). fn malformed_cases() -> Vec { vec![ Case { - // No year at all. - name: "Some.Weird.Movie.1080p.WEB-DL.x264-GRP".into(), + name: "2.Coelhos.2012.BRRip.720p.x264-VTM_SL '(Nacional)'".into(), want: NameClaims { - title: s("Some Weird Movie"), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - group: s("GRP"), - ..NameClaims::default() - }, - }, - Case { - // No source, codec or group beyond a bare resolution. - name: "Random.Movie.Name.2023.720p".into(), - want: NameClaims { - title: s("Random Movie Name"), - year: Some(2023), + title: s("2 Coelhos"), + year: Some(2012), resolution: Some(Resolution::P720), - ..NameClaims::default() - }, - }, - Case { - // Runs of empty separators collapse harmlessly. - name: "Broken..Signal...2023..1080p..WEBRip..x264-GRP9".into(), - want: NameClaims { - title: s("Broken Signal"), - year: Some(2023), - resolution: Some(Resolution::P1080), - source: Some(Source::WebRip), - codec: Some(Codec::X264), - group: s("GRP9"), - ..NameClaims::default() - }, - }, - Case { - // Underscore-separated, all caps. - name: "MOVIE_NAME_2024_1080P_BLURAY_X264-GRP10".into(), - want: NameClaims { - title: s("MOVIE NAME"), - year: Some(2024), - resolution: Some(Resolution::P1080), source: Some(Source::BluRay), - codec: Some(Codec::X264), - group: s("GRP10"), + languages: vec![LanguageMarker::PtBr], ..NameClaims::default() }, }, Case { - // `PROPER` is junk: recognised, ignored, doesn't corrupt the - // rest of the parse. - name: "The.Long.Signal.2023.1080p.WEB-DL.PROPER.x264-GRP11".into(), + name: "A Busca (2013) BDRip bluray 720p '' Nacional -derew".into(), want: NameClaims { - title: s("The Long Signal"), - year: Some(2023), - resolution: Some(Resolution::P1080), - source: Some(Source::WebDl), - codec: Some(Codec::X264), - group: s("GRP11"), - ..NameClaims::default() - }, - }, - Case { - // Nothing but markers: no title survives. - name: "1080p.x264".into(), - want: NameClaims { - resolution: Some(Resolution::P1080), - codec: Some(Codec::X264), - ..NameClaims::default() - }, - }, - Case { - // No recognisable marker anywhere: the whole thing degrades - // to a title-only claim. - name: "---.devolved.gibberish.zzz111".into(), - want: NameClaims { - title: s("devolved gibberish zzz111"), - ..NameClaims::default() - }, - }, - Case { - // Group suffix too mangled to extract (over length, no - // letters): both it and the fused codec token are dropped, - // but the rest of the claim still comes through. - name: "Ashfall.2022.720p.HDTV.x264-123456789012345678901234567890".into(), - want: NameClaims { - title: s("Ashfall"), - year: Some(2022), + title: s("A Busca"), + year: Some(2013), resolution: Some(Resolution::P720), - source: Some(Source::Hdtv), + source: Some(Source::BluRay), + languages: vec![LanguageMarker::PtBr], + ..NameClaims::default() + }, + }, + Case { + // An unescaped `&` HTML entity, straight from the indexer. + name: + "Bill & Ted's Bogus Journey 1991 UHD BluRay 2160p HDR10 DV HEVC LPCM 2.0 x265-E" + .into(), + want: NameClaims { + title: s("Bill & Ted's Bogus Journey"), + year: Some(1991), + resolution: Some(Resolution::P2160), + source: Some(Source::BluRay), + codec: Some(Codec::X265), + hdr: vec![HdrMarker::Hdr10, HdrMarker::DolbyVision], + group: s("E"), + ..NameClaims::default() + }, + }, + Case { + // Not a movie or TV release at all — a game upload. Degrades + // to a title-only claim rather than failing. + name: "DEMON'S TILT b11419829 MULTi10 GNU/Linux Wine jc141 (Appid=422510)" + .into(), + want: NameClaims { + title: s("DEMON'S TILT b11419829 MULTi10 GNU/Linux Wine jc141 Appid=422510"), + ..NameClaims::default() + }, + }, + Case { + name: "Agenty Bezopasnoj Nacional\\'nosti [RUS][Novyj Disk](2007)".into(), + want: NameClaims { + title: s("Agenty Bezopasnoj Nacional\\'nosti RUS Novyj Disk"), + year: Some(2007), + ..NameClaims::default() + }, + }, + Case { + name: "Punk'd (2003) Season 1 S01 (480i NTSC DVD Remux AC3 2.0)".into(), + want: NameClaims { + title: s("Punk'd"), + year: Some(2003), + resolution: Some(Resolution::P480), + source: Some(Source::Remux), ..NameClaims::default() }, }, @@ -595,10 +730,10 @@ fn malformed_cases() -> Vec { } fn all_cases() -> Vec { - let mut cases = resolution_source_matrix(); - cases.extend(dv_hdr_cases()); - cases.extend(multi_audio_cases()); + let mut cases = matrix_cases(); cases.extend(pt_br_cases()); + cases.extend(multi_audio_cases()); + cases.extend(dv_hdr_cases()); cases.extend(malformed_cases()); cases } -- 2.47.3