From c71647db407c071dca20e2434a9e934a4c7c64de Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 19:31:32 +0100 Subject: [PATCH 1/2] feat(core): add movie domain types --- crates/arr-core/src/lib.rs | 269 +++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 9 deletions(-) diff --git a/crates/arr-core/src/lib.rs b/crates/arr-core/src/lib.rs index 328b332..7ec9229 100644 --- a/crates/arr-core/src/lib.rs +++ b/crates/arr-core/src/lib.rs @@ -1,14 +1,265 @@ -//! arr-core — domain types, policy engine, scoring. See DESIGN.md §4 and §5. -//! -//! This crate must never depend on `axum`, `sqlx` or `reqwest`. It holds the -//! logic that is tested constantly and it has to stay fast to compile. +//! Pure domain types shared by the arr crates. + +use std::{collections::BTreeMap, path::PathBuf, time::SystemTime}; + +macro_rules! id_type { + ($name:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct $name(pub i64); + }; +} + +id_type!(RootId); +id_type!(PolicyId); +id_type!(MovieId); +id_type!(MediaFileId); +id_type!(ReleaseId); +id_type!(GrabId); +id_type!(OwnerId); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Audience { + Main, + Kids, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Root { + pub id: RootId, + pub audience: Audience, + pub path: PathBuf, + pub policy_id: PolicyId, +} + +#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Language { + PortuguesePortugal, + PortugueseBrazil, + PortugueseUnverified, + Other(String), +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Resolution { + R2160p, + R1080p, + R720p, + Other(u16), +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Source { + Remux, + BluRay, + WebDl, + WebRip, + Hdtv, + Telesync, + Cam, + Screener, + Other, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HdrFormat { + Sdr, + Hdr10, + Hdr10Plus, + Hlg, + DolbyVision { profile: u8 }, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RequiredAudio { + OriginalLanguage, + AnyOf(Vec), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HdrRules { + pub rejected_dolby_vision_profiles: Vec, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SizeBand { + pub floor_bytes: u64, + pub target_bytes: u64, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Policy { + pub id: PolicyId, + pub name: String, + pub required_audio: RequiredAudio, + pub dub_blacklist: Vec, + pub hdr_rules: HdrRules, + pub size_bands: BTreeMap, + pub resolution_preference: Vec, + pub source_weights: BTreeMap, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct MovieOverrides { + pub only_4k: bool, + pub allow_english_audio: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MovieState { + Missing, + Downloading, + Available, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Movie { + pub id: MovieId, + pub tmdb_id: u64, + pub title: String, + pub year: u16, + pub original_language: Language, + pub root_id: RootId, + pub wanted: bool, + pub overrides: MovieOverrides, + pub state: MovieState, + pub blocked: bool, + pub search_attempts: u32, + pub last_searched_at: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AudioTrack { + pub language: Language, + pub title: Option, + pub handler_name: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SubtitleTrack { + pub language: Language, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProbedMedia { + pub resolution: Resolution, + pub source: Option, + pub hdr: HdrFormat, + pub audio_tracks: Vec, + pub subtitle_tracks: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Rule { + RequiredAudio, + DubBlacklist(Language), + DolbyVisionProfile(u8), + Resolution(Resolution), + Source(Source), + Size, + Other(String), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Verdict { + Eligible, + Waived(Rule), + Rejected(Rule), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct MediaFile { + pub id: MediaFileId, + pub movie_id: MovieId, + pub path: PathBuf, + pub size: u64, + pub probed: ProbedMedia, + pub waiver: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParsedRelease { + pub resolution: Option, + pub source: Option, + pub hdr: Vec, + pub audio_languages: Vec, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Release { + pub id: ReleaseId, + pub indexer_id: i64, + pub guid: String, + pub name: String, + pub size: u64, + pub seeders: u32, + pub publish_date: SystemTime, + pub download_url: String, + pub parsed: ParsedRelease, + pub score: i64, + pub verdict: Verdict, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum GrabState { + Downloading, + Downloaded, + Imported, + Failed, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Grab { + pub id: GrabId, + pub release_id: ReleaseId, + pub movie_id: MovieId, + pub infohash: String, + pub state: GrabState, + pub grabbed_at: SystemTime, + pub imported_at: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct BlacklistEntry { + pub infohash: String, + pub normalised_name: String, + pub reason: String, + pub created_at: SystemTime, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Owner { + pub id: OwnerId, + pub name: String, + pub ntfy_topic: String, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MovieOwner { + pub movie_id: MovieId, + pub owner_id: OwnerId, +} #[cfg(test)] mod tests { - /// Placeholder so the test gate is meaningful on an empty workspace — - /// `cargo nextest` fails a run with zero tests, and relaxing that would - /// let a broken filter go green later. Replaced by real tests in the - /// domain-types and policy-engine issues. + use super::{HdrFormat, Language, Rule, Verdict}; + #[test] - fn workspace_builds_and_tests_run() {} + fn portuguese_variants_are_distinct() { + assert_ne!(Language::PortuguesePortugal, Language::PortugueseBrazil); + assert_ne!(Language::PortugueseBrazil, Language::PortugueseUnverified); + } + + #[test] + fn dolby_vision_profile_is_preserved() { + assert_ne!( + HdrFormat::DolbyVision { profile: 5 }, + HdrFormat::DolbyVision { profile: 8 } + ); + } + + #[test] + fn non_eligible_verdicts_name_the_rule() { + let rule = Rule::DolbyVisionProfile(5); + assert_eq!(Verdict::Rejected(rule.clone()), Verdict::Rejected(rule)); + } } -- 2.47.3 From f764402b632500fbf0d2ad1f27b5bbb3335125a7 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sat, 22 Aug 2026 19:35:52 +0100 Subject: [PATCH 2/2] fix(core): distinguish HDR evidence --- crates/arr-core/src/lib.rs | 41 ++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/arr-core/src/lib.rs b/crates/arr-core/src/lib.rs index 7ec9229..be1badf 100644 --- a/crates/arr-core/src/lib.rs +++ b/crates/arr-core/src/lib.rs @@ -1,4 +1,6 @@ //! Pure domain types shared by the arr crates. +//! +//! This crate must never depend on `axum`, `sqlx` or `reqwest`. use std::{collections::BTreeMap, path::PathBuf, time::SystemTime}; @@ -60,13 +62,27 @@ pub enum Source { Other, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DolbyVisionProfile { + pub profile: u8, + pub compatibility_id: Option, +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum HdrFormat { Sdr, Hdr10, Hdr10Plus, Hlg, - DolbyVision { profile: u8 }, + DolbyVision(DolbyVisionProfile), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum HdrClaim { + Hdr10, + Hdr10Plus, + Hlg, + DolbyVision, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -77,7 +93,7 @@ pub enum RequiredAudio { #[derive(Clone, Debug, Eq, PartialEq)] pub struct HdrRules { - pub rejected_dolby_vision_profiles: Vec, + pub rejected_dolby_vision_profiles: Vec, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -152,7 +168,7 @@ pub struct ProbedMedia { pub enum Rule { RequiredAudio, DubBlacklist(Language), - DolbyVisionProfile(u8), + DolbyVisionProfile(DolbyVisionProfile), Resolution(Resolution), Source(Source), Size, @@ -180,7 +196,7 @@ pub struct MediaFile { pub struct ParsedRelease { pub resolution: Option, pub source: Option, - pub hdr: Vec, + pub hdr: Vec, pub audio_languages: Vec, } @@ -241,7 +257,7 @@ pub struct MovieOwner { #[cfg(test)] mod tests { - use super::{HdrFormat, Language, Rule, Verdict}; + use super::{DolbyVisionProfile, HdrFormat, Language, Rule, Verdict}; #[test] fn portuguese_variants_are_distinct() { @@ -252,14 +268,23 @@ mod tests { #[test] fn dolby_vision_profile_is_preserved() { assert_ne!( - HdrFormat::DolbyVision { profile: 5 }, - HdrFormat::DolbyVision { profile: 8 } + HdrFormat::DolbyVision(DolbyVisionProfile { + profile: 8, + compatibility_id: Some(1), + }), + HdrFormat::DolbyVision(DolbyVisionProfile { + profile: 8, + compatibility_id: Some(2), + }) ); } #[test] fn non_eligible_verdicts_name_the_rule() { - let rule = Rule::DolbyVisionProfile(5); + let rule = Rule::DolbyVisionProfile(DolbyVisionProfile { + profile: 5, + compatibility_id: None, + }); assert_eq!(Verdict::Rejected(rule.clone()), Verdict::Rejected(rule)); } } -- 2.47.3