//! Table-driven checks over representative release names. The full fixture //! corpus pulled from real indexers is issue #9; these rows pin the parser's //! behaviour per shape of name. use arr_parse::{ parse, Codec, Edition, EpisodeClaim, HdrMarker, LanguageMarker, NameClaims, Resolution, Source, }; use serde as _; struct Case { name: &'static str, want: NameClaims, } #[allow(clippy::unnecessary_wraps)] fn s(v: &str) -> Option { Some(v.to_string()) } #[allow(clippy::too_many_lines)] fn cases() -> Vec { vec![ Case { name: "Movie.Title.2024.2160p.UHD.BluRay.DV.HDR10.x265.TrueHD-GROUP", want: NameClaims { title: s("Movie Title"), year: Some(2024), resolution: Some(Resolution::P2160), source: Some(Source::BluRay), codec: Some(Codec::X265), hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], group: s("GROUP"), ..NameClaims::default() }, }, Case { // "Web" before the year is title, not a source claim. name: "Charlottes.Web.2006.1080p.BluRay.x264-GRP", want: NameClaims { title: s("Charlottes Web"), year: Some(2006), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), codec: Some(Codec::X264), group: s("GRP"), ..NameClaims::default() }, }, Case { name: "Dune Part Two (2024) [2160p] [WEB-DL] [HDR10+] [YTS.MX]", want: NameClaims { title: s("Dune Part Two"), year: Some(2024), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), hdr: vec![HdrMarker::Hdr10Plus], group: s("YTS.MX"), ..NameClaims::default() }, }, Case { name: "O.Auto.da.Compadecida.2000.NACIONAL.1080p.WEB-DL.x264-BRA", want: NameClaims { title: s("O Auto da Compadecida"), year: Some(2000), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), codec: Some(Codec::X264), languages: vec![LanguageMarker::PtBr], group: s("BRA"), ..NameClaims::default() }, }, Case { // Repeated pt-BR markers dedupe to one claim. name: "Filme.2023.Dublado.PT-BR.1080p.WEBRip.x264", want: NameClaims { title: s("Filme"), year: Some(2023), resolution: Some(Resolution::P1080), source: Some(Source::WebRip), codec: Some(Codec::X264), languages: vec![LanguageMarker::PtBr], ..NameClaims::default() }, }, Case { name: "Filme.Infantil.2023.1080p.WEB-DL.Dual.Áudio.x264", want: NameClaims { title: s("Filme Infantil"), year: Some(2023), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), codec: Some(Codec::X264), languages: vec![LanguageMarker::Dual], ..NameClaims::default() }, }, Case { name: "Movie.2024.MULTi.1080p.WEB.x265-GRP", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), codec: Some(Codec::X265), languages: vec![LanguageMarker::Multi], group: s("GRP"), ..NameClaims::default() }, }, Case { // Season/episode tags bound the title; no year needed. name: "Serie.S01E02.720p.HDTV.x264-LOL", want: NameClaims { title: s("Serie"), resolution: Some(Resolution::P720), source: Some(Source::Hdtv), codec: Some(Codec::X264), group: s("LOL"), episode: Some(EpisodeClaim::Episodes { season: 1, episodes: vec![2], }), ..NameClaims::default() }, }, Case { // Remux beats the BluRay marker riding along. name: "Movie.2024.2160p.BluRay.REMUX.HEVC.TrueHD.7.1.Atmos-FGT", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P2160), source: Some(Source::Remux), codec: Some(Codec::X265), group: s("FGT"), ..NameClaims::default() }, }, Case { name: "Movie.2024.Extended.1080p.BluRay.x264", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), codec: Some(Codec::X264), editions: vec![Edition::Extended], ..NameClaims::default() }, }, Case { // Edition between title and year is stripped off the title tail. name: "Movie.Directors.Cut.2024.1080p.BluRay", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), editions: vec![Edition::DirectorsCut], ..NameClaims::default() }, }, Case { // First year-like token is the title, second is the year. name: "2012.2009.1080p.BluRay.x264", want: NameClaims { title: s("2012"), year: Some(2009), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), codec: Some(Codec::X264), ..NameClaims::default() }, }, Case { name: "Blade.Runner.2049.2017.2160p.WEB-DL.DDP5.1.HDR.HEVC-XEBEC", want: NameClaims { title: s("Blade Runner 2049"), year: Some(2017), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), codec: Some(Codec::X265), hdr: vec![HdrMarker::Hdr], group: s("XEBEC"), ..NameClaims::default() }, }, Case { name: "Movie.2024.HDTS.x264-ETRG", want: NameClaims { title: s("Movie"), year: Some(2024), source: Some(Source::Telesync), codec: Some(Codec::X264), group: s("ETRG"), ..NameClaims::default() }, }, Case { name: "Movie.2024.1080p.HMAX.WEB-DL.DD5.1.H.264-GROUP", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), codec: Some(Codec::X264), group: s("GROUP"), ..NameClaims::default() }, }, Case { // Spaced dash before the group. name: "Movie Title 2024 1080p BluRay x265 - VXT", want: NameClaims { title: s("Movie Title"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), codec: Some(Codec::X265), group: s("VXT"), ..NameClaims::default() }, }, Case { // Filename with a container extension. name: "Movie.Title.2024.720p.WEBRip.x264.mkv", want: NameClaims { title: s("Movie Title"), year: Some(2024), resolution: Some(Resolution::P720), source: Some(Source::WebRip), codec: Some(Codec::X264), ..NameClaims::default() }, }, Case { // No markers at all: the whole thing is a title claim. name: "Some Random Words", want: NameClaims { title: s("Some Random Words"), ..NameClaims::default() }, }, Case { // Marker-only garbage parses partially, title absent. name: "1080p", want: NameClaims { resolution: Some(Resolution::P1080), ..NameClaims::default() }, }, Case { name: "", want: NameClaims::default(), }, Case { // Dolby Vision spelled out, dotted. name: "Movie.2024.2160p.WEB-DL.Dolby.Vision.HDR10.HEVC-GRP", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), codec: Some(Codec::X265), hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], group: s("GRP"), ..NameClaims::default() }, }, Case { // Hyphenated title survives when nothing marks a group. name: "Spider-Man.2002.1080p.BluRay.x264-SECTOR7", want: NameClaims { title: s("Spider-Man"), year: Some(2002), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), codec: Some(Codec::X264), group: s("SECTOR7"), ..NameClaims::default() }, }, Case { name: "www.Torrenting.com - Movie.2024.1080p.WEB-DL.x264", want: NameClaims { title: s("Movie"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), codec: Some(Codec::X264), ..NameClaims::default() }, }, Case { // Season pack, no episode. name: "Show.Name.S02.2160p.WEB-DL.DDP5.1.DV.HDR10.H.265-NTb", want: NameClaims { title: s("Show Name"), resolution: Some(Resolution::P2160), source: Some(Source::WebDl), codec: Some(Codec::X265), hdr: vec![HdrMarker::DolbyVision, HdrMarker::Hdr10], group: s("NTb"), episode: Some(EpisodeClaim::Season { season: 2 }), ..NameClaims::default() }, }, Case { // `COMPLETE` before the tag closes the title like any strong // marker, wherever the group puts it. name: "Bluey.COMPLETE.S01.1080p.WEB-DL", want: NameClaims { title: s("Bluey"), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), episode: Some(EpisodeClaim::Season { season: 1 }), ..NameClaims::default() }, }, Case { // The trailing placement already worked; it keeps working. name: "Bluey.S01.COMPLETE.1080p.WEB-DL", want: NameClaims { title: s("Bluey"), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), episode: Some(EpisodeClaim::Season { season: 1 }), ..NameClaims::default() }, }, Case { // A degenerate range is one season's pack. name: "Fallout.S01-S01.1080p.BluRay.x264-GROUP", want: NameClaims { title: s("Fallout"), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), codec: Some(Codec::X264), group: s("GROUP"), episode: Some(EpisodeClaim::Seasons { first: 1, last: 1 }), ..NameClaims::default() }, }, Case { // The spelled-out plural range closes the title too. name: "Fallout.Seasons.1-3.1080p.BluRay-GROUP", want: NameClaims { title: s("Fallout"), resolution: Some(Resolution::P1080), source: Some(Source::BluRay), group: s("GROUP"), episode: Some(EpisodeClaim::Seasons { first: 1, last: 3 }), ..NameClaims::default() }, }, Case { // A bare number after the plural is a single season, like // the singular form. name: "Show.Name.Seasons.1.1080p.WEB-DL-GROUP", want: NameClaims { title: s("Show Name"), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), group: s("GROUP"), episode: Some(EpisodeClaim::Season { season: 1 }), ..NameClaims::default() }, }, Case { // A year-sized number after `Seasons` is a year, not a count. name: "The.Four.Seasons.2024.1080p.WEB-DL", want: NameClaims { title: s("The Four Seasons"), year: Some(2024), resolution: Some(Resolution::P1080), source: Some(Source::WebDl), ..NameClaims::default() }, }, Case { name: "Movie.2019.4K.HDR.DV.2160p.BDRemux.Ita.Eng.x265-NAHOM", want: NameClaims { title: s("Movie"), year: Some(2019), resolution: Some(Resolution::P2160), source: Some(Source::Remux), codec: Some(Codec::X265), hdr: vec![HdrMarker::Hdr, HdrMarker::DolbyVision], languages: vec![LanguageMarker::Italian, LanguageMarker::English], group: s("NAHOM"), ..NameClaims::default() }, }, ] } #[test] fn corpus_rows_parse_as_expected() { 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(), failures.join("\n") ); } #[test] fn garbage_never_panics() { let inputs = [ "", " ", "....", "----", "[[[]]]", "()()()", "ção áéíóú 日本語 🎬🎬🎬", "-.-.-.-[]()s01e999999999999999999999", "Movie.2024.1080p-", "a", &"x.".repeat(5000), ]; for input in inputs { let _ = parse(input); } } #[test] fn episode_tags_separate_packs_from_single_episodes() { let single = parse("Show.S01E02.1080p.WEB-DL.x264-GRP") .episode .expect("S01E02 is an episode tag"); assert_eq!( single, EpisodeClaim::Episodes { season: 1, episodes: vec![2] } ); assert!(!single.is_season_pack()); assert!(!single.is_multi_episode()); assert!(single.covers(1, 2)); assert!(!single.covers(1, 3)); assert!(single.covers_season(1)); let multi = parse("Show.S01E02-E04.1080p.WEB-DL.x264-GRP") .episode .expect("S01E02-E04 is an episode tag"); assert!(multi.is_multi_episode()); assert!(!multi.is_season_pack()); assert!(multi.covers(1, 3)); assert!(!multi.covers(1, 5)); let pack = parse("Show.S01.1080p.WEB-DL.x264-GRP") .episode .expect("S01 is a season tag"); assert!(pack.is_season_pack()); assert!( pack.covers(1, 7), "a season pack covers every episode in it" ); assert!(!pack.covers(2, 7)); let seasons = parse("Show.S01-S03.1080p.WEB-DL.x264-GRP") .episode .expect("S01-S03 is a season range"); assert!(seasons.is_season_pack()); assert!(seasons.covers_season(2)); assert!(!seasons.covers_season(4)); let daily = parse("Show.2024.05.13.1080p.WEB.h264-GRP") .episode .expect("a dotted air date is a daily tag"); assert!(daily.covers_date(2024, 5, 13)); assert!(!daily.covers_date(2024, 5, 14)); assert!(!daily.covers(1, 1), "a daily episode has no season number"); } #[test] fn non_tv_names_claim_no_episode() { for name in [ "Dune.Part.Two.2024.2160p.WEB-DL.H.265-FLUX", "Movie.1999.1080p.BluRay.x264-GRP", "Show.2024.13.45.1080p.WEB-DL.x264-GRP", "Blade.Runner.2049.2017.2160p.UHD.BluRay.x265-TERMiNAL", ] { assert_eq!(parse(name).episode, None, "{name}"); } } #[test] fn normalisation_is_separator_and_case_blind() { let key = arr_parse::normalise("Dune.Part.Two.2024.2160p.WEB-DL.DDP5.1.Atmos"); assert_eq!(key, "dune.part.two.2024.2160p.web.dl.ddp5.1.atmos"); assert_eq!( arr_parse::normalise("dune part two [2024] 2160p WEB DL DDP5 1 atmos "), key, "the same release under different separators is the same blacklist key" ); assert_eq!(arr_parse::normalise("!!!"), ""); }