Skip to main content

avatarr_parser/quality/
mod.rs

1// Ported from Sonarr v4.0.17.2952 (97e85a90).
2// Sources:
3//   src/NzbDrone.Core/Qualities/Quality.cs
4//   src/NzbDrone.Core/Qualities/QualityModel.cs
5//   src/NzbDrone.Core/Qualities/QualitySource.cs
6//   src/NzbDrone.Core/Qualities/QualityDetectionSource.cs
7
8pub mod finder;
9pub mod parser;
10
11pub use parser::{parse_quality, parse_quality_name};
12
13/// Quality identity: a stable integer ID per release class.
14///
15/// IDs match Sonarr's `Quality.cs` static initializers verbatim. ID 11 is
16/// intentionally skipped. It was reserved for `HDTV-480p`, which Sonarr
17/// commented out and never shipped. Bluray576p lands at ID 22 because it was
18/// added after the 2160p suite.
19#[repr(i32)]
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
21pub enum Quality {
22    #[default]
23    Unknown = 0,
24    Sdtv = 1,
25    Dvd = 2,
26    Webdl1080p = 3,
27    Hdtv720p = 4,
28    Webdl720p = 5,
29    Bluray720p = 6,
30    Bluray1080p = 7,
31    Webdl480p = 8,
32    Hdtv1080p = 9,
33    RawHd = 10,
34    Webrip480p = 12,
35    Bluray480p = 13,
36    Webrip720p = 14,
37    Webrip1080p = 15,
38    Hdtv2160p = 16,
39    Webrip2160p = 17,
40    Webdl2160p = 18,
41    Bluray2160p = 19,
42    Bluray1080pRemux = 20,
43    Bluray2160pRemux = 21,
44    Bluray576p = 22,
45}
46
47/// Source-class metadata attached to each `Quality` (Television, Web, etc.).
48///
49/// Mirrors `QualitySource.cs` (8 variants). `Dvd` and `BlurayRaw` are spelled
50/// `DVD` and `BlurayRaw` in the C# enum; we lowercase per Rust convention.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
52pub enum QualitySource {
53    #[default]
54    Unknown,
55    Television,
56    TelevisionRaw,
57    Web,
58    WebRip,
59    Dvd,
60    Bluray,
61    BlurayRaw,
62}
63
64/// Vertical-resolution buckets a release falls into.
65///
66/// Sonarr stores resolution as a raw `int` on each `Quality` instance; the
67/// Rust port models it as a typed enum so the parser can return well-formed
68/// values without a magic-number lookup. `pixels()` returns the numeric height
69/// for any consumer that needs to compare against a raw integer (e.g. minimum
70/// quality thresholds).
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
72pub enum Resolution {
73    #[default]
74    Unknown,
75    R360p,
76    R480p,
77    R540p,
78    R576p,
79    R720p,
80    R1080p,
81    R2160p,
82}
83
84impl Resolution {
85    /// Vertical pixel count for this resolution; `0` for `Unknown`.
86    pub fn pixels(self) -> u32 {
87        match self {
88            Resolution::R360p => 360,
89            Resolution::R480p => 480,
90            Resolution::R540p => 540,
91            Resolution::R576p => 576,
92            Resolution::R720p => 720,
93            Resolution::R1080p => 1080,
94            Resolution::R2160p => 2160,
95            Resolution::Unknown => 0,
96        }
97    }
98}
99
100/// Where in a release a particular field (source, resolution, revision) was
101/// detected.
102///
103/// Mirrors `QualityDetectionSource.cs` (4 variants: `Unknown`, `Name`,
104/// `Extension`, `MediaInfo`). `MediaInfo` is set when the field came from
105/// MediaInfo probing rather than the release name; the parser layer only
106/// emits `Name` / `Extension` / `Unknown`, but the variant exists so consumers
107/// can model the full Sonarr surface.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
109pub enum QualityDetectionSource {
110    #[default]
111    Unknown,
112    Name,
113    Extension,
114    MediaInfo,
115}
116
117/// Repack / proper revision metadata.
118///
119/// `version` defaults to `1` (the original release); `2+` indicates a
120/// PROPER, REPACK, or explicit `vN` marker. `real` is a count of `REAL` tags
121/// (Sonarr stores it as `int`, not `bool`, because the modifier cascade
122/// records every uppercase REAL match via `RealRegex.Matches(name).Count`).
123/// `is_repack` flags REPACK; the version bump from REPACK is kept on
124/// `version` separately, and the two coexist for the comparison ordering in
125/// `CompareTo`.
126///
127/// `Default` is implemented manually because we need a non-zero `version`.
128///
129/// Verified at T6 (Sonarr v4.0.17.2952, `Qualities/Revision.cs:19-21` and
130/// `Parser/QualityParser.cs:702-708`): `Real` is `int`, set from
131/// `realRegexResult.Count`. The plan-spec drafted `bool`, but the multi-real
132/// case (`Show.REAL.REAL.1080p`) would lose information at the cast boundary.
133/// Ported as `u32` to mirror the C# signed-int counter; values are always
134/// non-negative because `Matches.Count` is non-negative.
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
136pub struct Revision {
137    pub version: u32,
138    pub real: u32,
139    pub is_repack: bool,
140}
141
142impl Default for Revision {
143    fn default() -> Self {
144        Self {
145            version: 1,
146            real: 0,
147            is_repack: false,
148        }
149    }
150}
151
152/// A parsed `Quality` plus its `Revision` and per-field detection-source
153/// trail.
154///
155/// Mirrors `QualityModel.cs`: `Quality` + `Revision` are the user-visible
156/// payload; the three `*DetectionSource` fields record provenance for the
157/// Quality, Resolution, and Revision components respectively. C# carries
158/// these as `[JsonIgnore]` properties (they never serialise), so the Rust
159/// port keeps them as plain fields without serde wiring.
160///
161/// `Default` derives because every constituent field is `Default`; the
162/// resulting model is `Quality::Unknown` + version-1 revision + all
163/// detection sources unknown.
164// TODO(m51): disambiguate from `crates/core::domain::embedded::QualityModel`
165// (Radarr-shape, flat fields). Both will co-exist when m51 wires the parser
166// into the user-visible path; resolve via rename or `From<>` converter.
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
168pub struct QualityModel {
169    pub quality: Quality,
170    pub revision: Revision,
171    pub source_detection_source: QualityDetectionSource,
172    pub resolution_detection_source: QualityDetectionSource,
173    pub revision_detection_source: QualityDetectionSource,
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn quality_constants_have_expected_ids() {
182        assert_eq!(Quality::Unknown as i32, 0);
183        assert_eq!(Quality::Sdtv as i32, 1);
184        assert_eq!(Quality::Webdl1080p as i32, 3);
185        assert_eq!(Quality::Bluray2160pRemux as i32, 21);
186        assert_eq!(Quality::Bluray576p as i32, 22);
187    }
188
189    #[test]
190    fn quality_model_default_is_unknown() {
191        let model = QualityModel::default();
192        assert_eq!(model.quality, Quality::Unknown);
193        assert_eq!(model.revision.version, 1);
194        assert!(!model.revision.is_repack);
195        assert_eq!(model.revision.real, 0);
196    }
197}