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}