Skip to main content

imferno_core/validation/
codes.rs

1//! Typed validation-code catalogue for SMPTE ST 2067-21 (Application Profile #2E).
2
3use crate::diagnostics::codes::ValidationCode;
4use crate::diagnostics::{Category, Severity};
5
6macro_rules! impl_into_string {
7    ($t:ty) => {
8        impl From<$t> for String {
9            fn from(c: $t) -> String {
10                <$t as ValidationCode>::code(&c).to_string()
11            }
12        }
13    };
14}
15
16// ─────────────────────────────────────────────────────────────────────────────
17// ST 2067-21:2020
18// ─────────────────────────────────────────────────────────────────────────────
19
20/// Validation codes defined by SMPTE ST 2067-21:2020 (Application Profile #2E).
21#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
22pub enum St2067_21_2020 {
23    /// Application identifier in CPL ExtensionProperties does not match the expected App2E URI.
24    AppIdMismatch,
25}
26
27impl ValidationCode for St2067_21_2020 {
28    fn code(&self) -> &'static str {
29        match self {
30            Self::AppIdMismatch => "ST2067-21:2020:7.1/AppIdMismatch",
31        }
32    }
33    fn description(&self) -> &'static str {
34        match self {
35            Self::AppIdMismatch =>
36                "Application identifier in CPL ExtensionProperties does not match the expected App2E URI.",
37        }
38    }
39    fn default_severity(&self) -> Severity {
40        Severity::Warning
41    }
42    fn category(&self) -> Category {
43        Category::Metadata
44    }
45    fn example(&self) -> Option<&'static str> {
46        Some(match self {
47            Self::AppIdMismatch =>
48                "CPL ExtensionProperties contains `<ApplicationIdentification>http://www.smpte-ra.org/schemas/2067-20/2013</ApplicationIdentification>` (App #2) instead of the App #2E URI.",
49        })
50    }
51}
52
53impl St2067_21_2020 {
54    pub const ALL: &'static [Self] = &[Self::AppIdMismatch];
55}
56
57impl_into_string!(St2067_21_2020);
58
59// ─────────────────────────────────────────────────────────────────────────────
60// ST 2067-21:2023
61// ─────────────────────────────────────────────────────────────────────────────
62
63/// Validation codes defined by SMPTE ST 2067-21:2023 (Application Profile #2E, UHD/HDR).
64///
65/// Variant naming notes:
66/// - Variants ending in `Unknown` check for unrecognized UL values (wrong value).
67/// - Variants starting with `Required` check for missing mandatory fields.
68/// - `*Missing` variants (e.g. `ColorPrimariesMissing`) check field presence at §6.2.1.
69/// - `*Unknown` variants (e.g. `ColorPrimariesUnknown`) check value validity at §6.2.2/3/4.
70/// - Plain variants in §6.2.1 check value-validity for fields whose presence
71///   is enforced elsewhere (e.g. `FieldDominance`, `FrameLayout`).
72#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
73pub enum St2067_21_2023 {
74    // ── §5.2 Frame rates and resolutions ─────────────────────────────────────
75    /// Frame rate is not in the permitted set for App2E.
76    FrameRate,
77    /// Image resolution is not in the permitted set for App2E.
78    Resolution,
79
80    // ── §5.3 Language tags ────────────────────────────────────────────────────
81    /// Locale language tag is empty.
82    EmptyLanguageTag,
83    /// Locale language tag is not a valid BCP-47 subtag.
84    MalformedLanguageTag,
85    /// Region subtag in a language tag is not valid.
86    RegionCode,
87
88    // ── §6.2 Color system ────────────────────────────────────────────────────
89    /// Color system designator is not in the permitted set.
90    ColorSystem,
91
92    // ── §6.2 Required picture-descriptor fields ───────────────────────────────
93    /// RGBA/CDCI descriptor is missing the required `StoredWidth` field.
94    RequiredStoredWidth,
95    /// RGBA/CDCI descriptor is missing the required `StoredHeight` field.
96    RequiredStoredHeight,
97    /// RGBA/CDCI descriptor is missing the required `SampleRate` field.
98    RequiredSampleRate,
99    /// RGBA/CDCI descriptor is missing the required `FrameLayout` field.
100    RequiredFrameLayout,
101    /// RGBA/CDCI descriptor is missing the required `ColorPrimaries` field.
102    RequiredColorPrimaries,
103    /// RGBA/CDCI descriptor is missing the required `TransferCharacteristic` field.
104    RequiredTransferCharacteristic,
105    /// RGBA/CDCI descriptor is missing the required `PictureCompression` field.
106    RequiredPictureCompression,
107    /// CDCI descriptor is missing the required `ComponentDepth` field.
108    RequiredComponentDepth,
109
110    // ── §6.5 Required audio-descriptor fields ─────────────────────────────────
111    /// WavePCM descriptor is missing the required `ChannelCount` field.
112    RequiredChannelCount,
113    /// WavePCM descriptor is missing the required `QuantizationBits` field.
114    RequiredQuantizationBits,
115
116    // ── §6.2.1 Picture descriptor constraints ────────────────────────────────
117    /// Alpha transparency mode is not permitted in App2E.
118    AlphaTransparency,
119    /// `CodingEquations` field is absent from the picture descriptor (§6.2.1 Table 8).
120    CodingEquationsMissing,
121    /// `ColorPrimaries` field is absent from the picture descriptor (§6.2.1 Table 8).
122    ColorPrimariesMissing,
123    /// `FieldDominance` value is not permitted for the declared `FrameLayout`.
124    FieldDominance,
125    /// `FrameLayout` value is not in the permitted set for App2E.
126    FrameLayout,
127    /// `FrameLayout` declares interlaced content, which is not permitted in App2E.
128    FrameLayoutInterlaced,
129    /// `ImageAlignmentOffset` value is not zero as required.
130    ImageAlignmentOffset,
131    /// `ImageEndOffset` value is not zero as required.
132    ImageEndOffset,
133    /// `ImageStartOffset` value is not zero as required.
134    ImageStartOffset,
135    /// `SampledHeight` does not equal `StoredHeight` as required.
136    SampledHeight,
137    /// `SampledWidth` does not equal `StoredWidth` as required.
138    SampledWidth,
139    /// `SampledXOffset` is not zero as required.
140    SampledXOffset,
141    /// `SampledYOffset` is not zero as required.
142    SampledYOffset,
143    /// `StoredF2Offset` is not zero as required for interlaced content.
144    StoredF2Offset,
145    /// `TransferCharacteristic` field is absent from the picture descriptor (§6.2.1 Table 8).
146    TransferCharacteristicMissing,
147
148    // ── §6.2.2 Transfer characteristic value ────────────────────────────────
149    /// `TransferCharacteristic` UL is present but not a recognized value (§6.2.2).
150    TransferCharacteristicUnknown,
151
152    // ── §6.2.3 Coding equations value ────────────────────────────────────────
153    /// `CodingEquations` UL is present but not a recognized value (§6.2.3).
154    CodingEquationsUnknown,
155
156    // ── §6.2.4 Color primaries value ─────────────────────────────────────────
157    /// `ColorPrimaries` UL is present but not a recognized value (§6.2.4).
158    ColorPrimariesUnknown,
159
160    // ── §6.2.5 JPEG 2000 requirement ─────────────────────────────────────────
161    /// Video essence is not JPEG 2000 encoded as required by App2E.
162    J2KRequired,
163
164    // ── §6.3 RGBA descriptor ─────────────────────────────────────────────────
165    /// `AlphaMaxRef` value is not permitted.
166    AlphaMaxRef,
167    /// `AlphaMinRef` value is not permitted.
168    AlphaMinRef,
169    /// `ComponentMaxRef` value is not in the permitted range.
170    ComponentMaxRef,
171    /// `ComponentMinRef` value is not in the permitted range.
172    ComponentMinRef,
173    /// `Palette` is present; palette images are not permitted in App2E.
174    Palette,
175    /// `PaletteLayout` is present; palette layout is not permitted in App2E.
176    PaletteLayout,
177    /// `ScanningDirection` value is not in the permitted set.
178    ScanningDirection,
179
180    // ── §6.3.2 Component reference values ────────────────────────────────────
181    /// Component max/min reference values are inconsistent with bit depth.
182    ComponentRefValues,
183
184    // ── §6.4 Bit depth and chroma ────────────────────────────────────────────
185    /// `AlphaSampleDepth` value is not permitted.
186    AlphaSampleDepth,
187    /// `ColorSiting` value is not in the permitted set.
188    ColorSiting,
189    /// `ComponentDepth` value is not in the permitted set (8/10/12/16).
190    ComponentDepth,
191    /// `HorizontalSubsampling` value is not in the permitted set.
192    HorizontalSubsampling,
193    /// `PaddingBits` value is not zero as required.
194    PaddingBits,
195    /// `ReversedByteOrder` flag is set; byte reversal is not permitted.
196    ReversedByteOrder,
197    /// `VerticalSubsampling` value is not in the permitted set.
198    VerticalSubsampling,
199
200    // ── §6.4.3 Luma range ────────────────────────────────────────────────────
201    /// `BlackRefLevel` value is inconsistent with bit depth.
202    BlackRefLevel,
203    /// `ColorRange` value is not in the permitted set.
204    ColorRange,
205    /// `WhiteRefLevel` value is inconsistent with bit depth.
206    WhiteRefLevel,
207
208    // ── §6.5 Audio ───────────────────────────────────────────────────────────
209    /// Audio sample rate is not 48 kHz as required.
210    AudioSampleRate,
211    /// `QuantizationBits` value is not in the permitted set (16/24).
212    QuantizationBits,
213
214    // ── §6.5.2 JPEG 2000 sub-descriptor ─────────────────────────────────────
215    /// J2K codestream coding style is not compliant.
216    CodingStyle,
217    /// JPEG 2000 codestream layout (J2C) does not meet App2E requirements.
218    J2CLayout,
219    /// JPEG 2000 extended capabilities are declared but not permitted.
220    J2KExtendedCapabilities,
221    /// JPEG2000SubDescriptor is absent or incomplete.
222    Jpeg2000SubDescriptor,
223
224    // ── §6.2.5 JPEG 2000 resolution profiles ────────────────────────────────
225    /// JPEG 2000 HT (ISO 15444-15) is not permitted by the App2E 2020 edition.
226    J2KHtNotAllowed,
227    /// JPEG 2000 IMF 4K Profile: stored resolution is outside the permitted range.
228    J2K4KResolution,
229    /// JPEG 2000 IMF 2K Profile: stored resolution is outside the permitted range.
230    J2K2KResolution,
231    /// JPEG 2000 Broadcast Contribution Profile: stored resolution is outside the permitted range.
232    J2KBcpResolution,
233
234    // ── §7.1 Application identification ──────────────────────────────────────
235    /// ApplicationIdentification is required for App2E compositions.
236    ApplicationIdentification,
237    /// ContentMaturityRating agency is empty.
238    ContentMaturityRatingAgency,
239    /// ContentMaturityRating agency is not a valid xs:anyURI.
240    ContentMaturityRatingAgencyUri,
241
242    // ── §7.2 Homogeneous image essence ───────────────────────────────────────
243    /// All image essence in a composition shall use the same color system.
244    HomogeneousImageEssence,
245
246    // ── §7.1 Application identification ──────────────────────────────────────
247    /// Application identifier in CPL ExtensionProperties does not match the App2E URI.
248    AppIdMismatch,
249
250    // ── §7.4 Segment duration ─────────────────────────────────────────────────
251    /// Segment duration is not an integer multiple of 5 edit units as required by App2E.
252    SegmentDurationMultiple,
253
254    // ── §7.5 HDR metadata ────────────────────────────────────────────────────
255    /// `MaxCLL` / `MaxFALL` HDR metadata is absent; recommended for HDR content.
256    MaxCLLMaxFALL,
257}
258
259impl ValidationCode for St2067_21_2023 {
260    fn code(&self) -> &'static str {
261        match self {
262            Self::FrameRate => "ST2067-21:2023:5.2/FrameRate",
263            Self::Resolution => "ST2067-21:2023:5.2/Resolution",
264            Self::EmptyLanguageTag => "ST2067-21:2023:5.3/EmptyLanguageTag",
265            Self::MalformedLanguageTag => "ST2067-21:2023:5.3/MalformedLanguageTag",
266            Self::RegionCode => "ST2067-21:2023:5.3/RegionCode",
267            Self::ColorSystem => "ST2067-21:2023:6.2/ColorSystem",
268            Self::RequiredStoredWidth => "ST2067-21:2023:6.2/Required-StoredWidth",
269            Self::RequiredStoredHeight => "ST2067-21:2023:6.2/Required-StoredHeight",
270            Self::RequiredSampleRate => "ST2067-21:2023:6.2/Required-SampleRate",
271            Self::RequiredFrameLayout => "ST2067-21:2023:6.2/Required-FrameLayout",
272            Self::RequiredColorPrimaries => "ST2067-21:2023:6.2/Required-ColorPrimaries",
273            Self::RequiredTransferCharacteristic => {
274                "ST2067-21:2023:6.2/Required-TransferCharacteristic"
275            }
276            Self::RequiredPictureCompression => "ST2067-21:2023:6.2/Required-PictureCompression",
277            Self::RequiredComponentDepth => "ST2067-21:2023:6.2/Required-ComponentDepth",
278            Self::RequiredChannelCount => "ST2067-21:2023:6.5/Required-ChannelCount",
279            Self::RequiredQuantizationBits => "ST2067-21:2023:6.5/Required-QuantizationBits",
280            Self::AlphaTransparency => "ST2067-21:2023:6.2.1/AlphaTransparency",
281            Self::CodingEquationsMissing => "ST2067-21:2023:6.2.1/CodingEquationsMissing",
282            Self::ColorPrimariesMissing => "ST2067-21:2023:6.2.1/ColorPrimariesMissing",
283            Self::FieldDominance => "ST2067-21:2023:6.2.1/FieldDominance",
284            Self::FrameLayout => "ST2067-21:2023:6.2.1/FrameLayout",
285            Self::FrameLayoutInterlaced => "ST2067-21:2023:6.2.1/FrameLayoutInterlaced",
286            Self::ImageAlignmentOffset => "ST2067-21:2023:6.2.1/ImageAlignmentOffset",
287            Self::ImageEndOffset => "ST2067-21:2023:6.2.1/ImageEndOffset",
288            Self::ImageStartOffset => "ST2067-21:2023:6.2.1/ImageStartOffset",
289            Self::SampledHeight => "ST2067-21:2023:6.2.1/SampledHeight",
290            Self::SampledWidth => "ST2067-21:2023:6.2.1/SampledWidth",
291            Self::SampledXOffset => "ST2067-21:2023:6.2.1/SampledXOffset",
292            Self::SampledYOffset => "ST2067-21:2023:6.2.1/SampledYOffset",
293            Self::StoredF2Offset => "ST2067-21:2023:6.2.1/StoredF2Offset",
294            Self::TransferCharacteristicMissing => {
295                "ST2067-21:2023:6.2.1/TransferCharacteristicMissing"
296            }
297            Self::TransferCharacteristicUnknown => {
298                "ST2067-21:2023:6.2.2/TransferCharacteristicUnknown"
299            }
300            Self::CodingEquationsUnknown => "ST2067-21:2023:6.2.3/CodingEquationsUnknown",
301            Self::ColorPrimariesUnknown => "ST2067-21:2023:6.2.4/ColorPrimariesUnknown",
302            Self::J2KRequired => "ST2067-21:2023:6.2.5/J2KRequired",
303            Self::AlphaMaxRef => "ST2067-21:2023:6.3/AlphaMaxRef",
304            Self::AlphaMinRef => "ST2067-21:2023:6.3/AlphaMinRef",
305            Self::ComponentMaxRef => "ST2067-21:2023:6.3/ComponentMaxRef",
306            Self::ComponentMinRef => "ST2067-21:2023:6.3/ComponentMinRef",
307            Self::Palette => "ST2067-21:2023:6.3/Palette",
308            Self::PaletteLayout => "ST2067-21:2023:6.3/PaletteLayout",
309            Self::ScanningDirection => "ST2067-21:2023:6.3/ScanningDirection",
310            Self::ComponentRefValues => "ST2067-21:2023:6.3.2/ComponentRefValues",
311            Self::AlphaSampleDepth => "ST2067-21:2023:6.4/AlphaSampleDepth",
312            Self::ColorSiting => "ST2067-21:2023:6.4/ColorSiting",
313            Self::ComponentDepth => "ST2067-21:2023:6.4/ComponentDepth",
314            Self::HorizontalSubsampling => "ST2067-21:2023:6.4/HorizontalSubsampling",
315            Self::PaddingBits => "ST2067-21:2023:6.4/PaddingBits",
316            Self::ReversedByteOrder => "ST2067-21:2023:6.4/ReversedByteOrder",
317            Self::VerticalSubsampling => "ST2067-21:2023:6.4/VerticalSubsampling",
318            Self::BlackRefLevel => "ST2067-21:2023:6.4.3/BlackRefLevel",
319            Self::ColorRange => "ST2067-21:2023:6.4.3/ColorRange",
320            Self::WhiteRefLevel => "ST2067-21:2023:6.4.3/WhiteRefLevel",
321            Self::AudioSampleRate => "ST2067-21:2023:6.5/AudioSampleRate",
322            Self::QuantizationBits => "ST2067-21:2023:6.5/QuantizationBits",
323            Self::CodingStyle => "ST2067-21:2023:6.5.2/CodingStyle",
324            Self::J2CLayout => "ST2067-21:2023:6.5.2/J2CLayout",
325            Self::J2KExtendedCapabilities => "ST2067-21:2023:6.5.2/J2KExtendedCapabilities",
326            Self::Jpeg2000SubDescriptor => "ST2067-21:2023:6.5.2/JPEG2000SubDescriptor",
327            Self::J2KHtNotAllowed => "ST2067-21:2023:6.2.5/J2K-HT-Not-Allowed",
328            Self::J2K4KResolution => "ST2067-21:2023:6.2.5/J2K-4K-Resolution",
329            Self::J2K2KResolution => "ST2067-21:2023:6.2.5/J2K-2K-Resolution",
330            Self::J2KBcpResolution => "ST2067-21:2023:6.2.5/J2K-BCP-Resolution",
331            Self::ApplicationIdentification => "ST2067-21:2023:7.1/ApplicationIdentification",
332            Self::ContentMaturityRatingAgency => "ST2067-21:2023:7.1/ContentMaturityRating-Agency",
333            Self::ContentMaturityRatingAgencyUri => {
334                "ST2067-21:2023:7.1/ContentMaturityRating-Agency-URI"
335            }
336            Self::HomogeneousImageEssence => "ST2067-21:2023:7.2/HomogeneousImageEssence",
337            Self::AppIdMismatch => "ST2067-21:2023:7.1/AppIdMismatch",
338            Self::SegmentDurationMultiple => "ST2067-21:2023:7.4/SegmentDurationMultiple",
339            Self::MaxCLLMaxFALL => "ST2067-21:2023:7.5/MaxCLLMaxFALL",
340        }
341    }
342
343    fn description(&self) -> &'static str {
344        match self {
345            Self::FrameRate => "Frame rate is not in the permitted set for App2E.",
346            Self::Resolution => "Image resolution is not in the permitted set for App2E.",
347            Self::EmptyLanguageTag => "Locale language tag is empty.",
348            Self::MalformedLanguageTag => "Locale language tag is not a valid BCP-47 subtag.",
349            Self::RegionCode => "Region subtag in a language tag is not valid.",
350            Self::ColorSystem => "Color system designator is not in the permitted set.",
351            Self::RequiredStoredWidth => "RGBA/CDCI descriptor is missing the required StoredWidth field.",
352            Self::RequiredStoredHeight => "RGBA/CDCI descriptor is missing the required StoredHeight field.",
353            Self::RequiredSampleRate => "RGBA/CDCI descriptor is missing the required SampleRate field.",
354            Self::RequiredFrameLayout => "RGBA/CDCI descriptor is missing the required FrameLayout field.",
355            Self::RequiredColorPrimaries => "RGBA/CDCI descriptor is missing the required ColorPrimaries field.",
356            Self::RequiredTransferCharacteristic => "RGBA/CDCI descriptor is missing the required TransferCharacteristic field.",
357            Self::RequiredPictureCompression => "RGBA/CDCI descriptor is missing the required PictureCompression field.",
358            Self::RequiredComponentDepth => "CDCI descriptor is missing the required ComponentDepth field.",
359            Self::RequiredChannelCount => "WavePCM descriptor is missing the required ChannelCount field.",
360            Self::RequiredQuantizationBits => "WavePCM descriptor is missing the required QuantizationBits field.",
361            Self::AlphaTransparency => "Alpha transparency mode is not permitted in App2E.",
362            Self::CodingEquationsMissing => "CodingEquations field is absent from the picture descriptor (Table 8).",
363            Self::ColorPrimariesMissing => "ColorPrimaries field is absent from the picture descriptor (Table 8).",
364            Self::FieldDominance => "FieldDominance value is not permitted for the declared FrameLayout.",
365            Self::FrameLayout => "FrameLayout value is not in the permitted set for App2E.",
366            Self::FrameLayoutInterlaced => "FrameLayout declares interlaced content, which is not permitted in App2E.",
367            Self::ImageAlignmentOffset => "ImageAlignmentOffset must be zero.",
368            Self::ImageEndOffset => "ImageEndOffset must be zero.",
369            Self::ImageStartOffset => "ImageStartOffset must be zero.",
370            Self::SampledHeight => "SampledHeight must equal StoredHeight.",
371            Self::SampledWidth => "SampledWidth must equal StoredWidth.",
372            Self::SampledXOffset => "SampledXOffset must be zero.",
373            Self::SampledYOffset => "SampledYOffset must be zero.",
374            Self::StoredF2Offset => "StoredF2Offset must be zero.",
375            Self::TransferCharacteristicMissing => "TransferCharacteristic field is absent from the picture descriptor (Table 8).",
376            Self::TransferCharacteristicUnknown => "TransferCharacteristic UL is present but not a recognized value.",
377            Self::CodingEquationsUnknown => "CodingEquations UL is present but not a recognized value.",
378            Self::ColorPrimariesUnknown => "ColorPrimaries UL is present but not a recognized value.",
379            Self::J2KRequired => "Video essence is not JPEG 2000 encoded as required by App2E.",
380            Self::AlphaMaxRef => "AlphaMaxRef value is not permitted.",
381            Self::AlphaMinRef => "AlphaMinRef value is not permitted.",
382            Self::ComponentMaxRef => "ComponentMaxRef value is not in the permitted range.",
383            Self::ComponentMinRef => "ComponentMinRef value is not in the permitted range.",
384            Self::Palette => "Palette is present; palette images are not permitted in App2E.",
385            Self::PaletteLayout => "PaletteLayout is present; palette layout is not permitted in App2E.",
386            Self::ScanningDirection => "ScanningDirection value is not in the permitted set.",
387            Self::ComponentRefValues => "Component max/min reference values are inconsistent with bit depth.",
388            Self::AlphaSampleDepth => "AlphaSampleDepth value is not permitted.",
389            Self::ColorSiting => "ColorSiting value is not in the permitted set.",
390            Self::ComponentDepth => "ComponentDepth value is not in the permitted set (8 / 10 / 12 / 16).",
391            Self::HorizontalSubsampling => "HorizontalSubsampling value is not in the permitted set.",
392            Self::PaddingBits => "PaddingBits must be zero.",
393            Self::ReversedByteOrder => "ReversedByteOrder flag is set; byte reversal is not permitted.",
394            Self::VerticalSubsampling => "VerticalSubsampling value is not in the permitted set.",
395            Self::BlackRefLevel => "BlackRefLevel value is inconsistent with ComponentDepth.",
396            Self::ColorRange => "ColorRange value is not in the permitted set.",
397            Self::WhiteRefLevel => "WhiteRefLevel value is inconsistent with ComponentDepth.",
398            Self::AudioSampleRate => "Audio sample rate must be 48 000 Hz.",
399            Self::QuantizationBits => "QuantizationBits must be 16 or 24.",
400            Self::CodingStyle => "JPEG 2000 codestream coding style is not compliant.",
401            Self::J2CLayout => "JPEG 2000 codestream layout does not meet App2E requirements.",
402            Self::J2KExtendedCapabilities => "JPEG 2000 extended capabilities are declared but not permitted.",
403            Self::Jpeg2000SubDescriptor => "JPEG2000SubDescriptor is absent or incomplete.",
404            Self::J2KHtNotAllowed                => "JPEG 2000 HT (ISO 15444-15) is not permitted by App2E 2020.",
405            Self::J2K4KResolution                => "JPEG 2000 IMF 4K Profile: stored resolution is outside the permitted range.",
406            Self::J2K2KResolution                => "JPEG 2000 IMF 2K Profile: stored resolution is outside the permitted range.",
407            Self::J2KBcpResolution               => "JPEG 2000 Broadcast Contribution Profile: stored resolution is outside the permitted range.",
408            Self::ApplicationIdentification      => "ApplicationIdentification is required for App2E compositions.",
409            Self::ContentMaturityRatingAgency    => "ContentMaturityRating Agency is empty.",
410            Self::ContentMaturityRatingAgencyUri => "ContentMaturityRating Agency is not a valid xs:anyURI.",
411            Self::HomogeneousImageEssence        => "All image essence in a composition shall use the same color system.",
412            Self::AppIdMismatch => "Application identifier in CPL ExtensionProperties does not match the expected App2E URI.",
413            Self::SegmentDurationMultiple => "Segment duration must be an integer multiple of 5 edit units.",
414            Self::MaxCLLMaxFALL => "MaxCLL / MaxFALL HDR metadata is absent; recommended for HDR content.",
415        }
416    }
417
418    fn default_severity(&self) -> Severity {
419        match self {
420            Self::MaxCLLMaxFALL => Severity::Info,
421            Self::AppIdMismatch | Self::Jpeg2000SubDescriptor => Severity::Warning,
422            Self::ContentMaturityRatingAgency | Self::ContentMaturityRatingAgencyUri => {
423                Severity::Error
424            }
425            _ => Severity::Error,
426        }
427    }
428
429    fn category(&self) -> Category {
430        match self {
431            Self::EmptyLanguageTag
432            | Self::MalformedLanguageTag
433            | Self::RegionCode
434            | Self::AppIdMismatch
435            | Self::ApplicationIdentification
436            | Self::ContentMaturityRatingAgency
437            | Self::ContentMaturityRatingAgencyUri => Category::Metadata,
438
439            Self::RequiredChannelCount
440            | Self::RequiredQuantizationBits
441            | Self::AudioSampleRate
442            | Self::QuantizationBits => Category::Audio,
443
444            Self::J2KRequired
445            | Self::CodingStyle
446            | Self::J2CLayout
447            | Self::J2KExtendedCapabilities
448            | Self::Jpeg2000SubDescriptor
449            | Self::J2KHtNotAllowed
450            | Self::J2K4KResolution
451            | Self::J2K2KResolution
452            | Self::J2KBcpResolution
453            | Self::RequiredStoredWidth
454            | Self::RequiredStoredHeight
455            | Self::RequiredSampleRate
456            | Self::RequiredFrameLayout
457            | Self::RequiredColorPrimaries
458            | Self::RequiredTransferCharacteristic
459            | Self::RequiredPictureCompression
460            | Self::RequiredComponentDepth => Category::Encoding,
461
462            Self::SegmentDurationMultiple => Category::Timing,
463
464            _ => Category::Video,
465        }
466    }
467    fn example(&self) -> Option<&'static str> {
468        Some(match self {
469            Self::FrameRate =>
470                "<EditRate>30 1</EditRate>  <!-- not in {24,25,30,50,60,120}/{1,1001} -->",
471            Self::Resolution =>
472                "<StoredWidth>1280</StoredWidth><StoredHeight>720</StoredHeight>  <!-- not 1920×1080 / 3840×2160 / 4096×2160 -->",
473            Self::EmptyLanguageTag =>
474                "<Locale><LanguageList><Language></Language></LanguageList></Locale>",
475            Self::MalformedLanguageTag =>
476                "<Language>123-en</Language>  <!-- BCP-47 primary subtag must start with a letter -->",
477            Self::RegionCode =>
478                "<Language>en-XYZ</Language>  <!-- region subtag XYZ not in ISO 3166-1 -->",
479            Self::ColorSystem =>
480                "ColorPrimaries + TransferCharacteristic + CodingEquations triple doesn't map to a permitted App2E ColorN system",
481            Self::RequiredStoredWidth =>
482                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <StoredWidth> child -->",
483            Self::RequiredStoredHeight =>
484                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <StoredHeight> child -->",
485            Self::RequiredSampleRate =>
486                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <SampleRate> child -->",
487            Self::RequiredFrameLayout =>
488                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <FrameLayout> child -->",
489            Self::RequiredColorPrimaries =>
490                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <ColorPrimaries> UL child -->",
491            Self::RequiredTransferCharacteristic =>
492                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <TransferCharacteristic> UL child -->",
493            Self::RequiredPictureCompression =>
494                "<RGBADescriptor>…</RGBADescriptor>  <!-- no <PictureCompression> UL child -->",
495            Self::RequiredComponentDepth =>
496                "<CDCIDescriptor>…</CDCIDescriptor>  <!-- no <ComponentDepth> child -->",
497            Self::RequiredChannelCount =>
498                "<WAVEPCMDescriptor>…</WAVEPCMDescriptor>  <!-- no <ChannelCount> child -->",
499            Self::RequiredQuantizationBits =>
500                "<WAVEPCMDescriptor>…</WAVEPCMDescriptor>  <!-- no <QuantizationBits> child -->",
501            Self::AlphaTransparency =>
502                "<AlphaTransparency>true</AlphaTransparency>  <!-- App2E disallows alpha -->",
503            Self::CodingEquationsMissing =>
504                "<CDCIDescriptor>…</CDCIDescriptor>  <!-- no <CodingEquations> UL child -->",
505            Self::ColorPrimariesMissing =>
506                "<CDCIDescriptor>…</CDCIDescriptor>  <!-- no <ColorPrimaries> UL child -->",
507            Self::FieldDominance =>
508                "<FieldDominance>2</FieldDominance> on <FrameLayout>FullFrame</FrameLayout>",
509            Self::FrameLayout =>
510                "<FrameLayout>MixedFields</FrameLayout>  <!-- not in {FullFrame,SeparateFields,SingleField} -->",
511            Self::FrameLayoutInterlaced =>
512                "<FrameLayout>SeparateFields</FrameLayout>  <!-- App2E forbids interlaced -->",
513            Self::ImageAlignmentOffset =>
514                "<ImageAlignmentOffset>16</ImageAlignmentOffset>  <!-- must be 0 -->",
515            Self::ImageEndOffset =>
516                "<ImageEndOffset>32</ImageEndOffset>  <!-- must be 0 -->",
517            Self::ImageStartOffset =>
518                "<ImageStartOffset>16</ImageStartOffset>  <!-- must be 0 -->",
519            Self::SampledHeight =>
520                "<StoredHeight>1080</StoredHeight><SampledHeight>1056</SampledHeight>",
521            Self::SampledWidth =>
522                "<StoredWidth>1920</StoredWidth><SampledWidth>1872</SampledWidth>",
523            Self::SampledXOffset =>
524                "<SampledXOffset>4</SampledXOffset>  <!-- must be 0 -->",
525            Self::SampledYOffset =>
526                "<SampledYOffset>4</SampledYOffset>  <!-- must be 0 -->",
527            Self::StoredF2Offset =>
528                "<StoredF2Offset>1</StoredF2Offset>  <!-- must be 0 -->",
529            Self::TransferCharacteristicMissing =>
530                "<CDCIDescriptor>…</CDCIDescriptor>  <!-- no <TransferCharacteristic> UL child -->",
531            Self::TransferCharacteristicUnknown =>
532                "<TransferCharacteristic>urn:smpte:ul:060e2b34.04010101.04010101.01010500</TransferCharacteristic>  <!-- UL not in §6.2.2 table -->",
533            Self::CodingEquationsUnknown =>
534                "<CodingEquations>urn:smpte:ul:060e2b34.04010101.04010101.02020500</CodingEquations>  <!-- UL not in §6.2.3 table -->",
535            Self::ColorPrimariesUnknown =>
536                "<ColorPrimaries>urn:smpte:ul:060e2b34.04010101.04010101.03030500</ColorPrimaries>  <!-- UL not in §6.2.4 table -->",
537            Self::J2KRequired =>
538                "<PictureCompression>urn:smpte:ul:060e2b34.04010101.04010202.01020500</PictureCompression>  <!-- not a JPEG-2000 UL -->",
539            Self::AlphaMaxRef =>
540                "<AlphaMaxRef>1024</AlphaMaxRef>  <!-- outside permitted depth-derived range -->",
541            Self::AlphaMinRef =>
542                "<AlphaMinRef>-1</AlphaMinRef>  <!-- outside permitted depth-derived range -->",
543            Self::ComponentMaxRef =>
544                "<ComponentMaxRef>9999</ComponentMaxRef>  <!-- beyond (2^ComponentDepth)-1 -->",
545            Self::ComponentMinRef =>
546                "<ComponentMinRef>-5</ComponentMinRef>  <!-- must be ≥ 0 -->",
547            Self::Palette =>
548                "<Palette>…</Palette>  <!-- palette images forbidden in App2E -->",
549            Self::PaletteLayout =>
550                "<PaletteLayout>…</PaletteLayout>  <!-- forbidden in App2E -->",
551            Self::ScanningDirection =>
552                "<ScanningDirection>5</ScanningDirection>  <!-- not in permitted set -->",
553            Self::ComponentRefValues =>
554                "<ComponentDepth>10</ComponentDepth><ComponentMaxRef>4096</ComponentMaxRef>  <!-- 4096 > (2^10)-1 -->",
555            Self::AlphaSampleDepth =>
556                "<AlphaSampleDepth>7</AlphaSampleDepth>  <!-- not in {8,10,12,16} -->",
557            Self::ColorSiting =>
558                "<ColorSiting>9</ColorSiting>  <!-- not in permitted set -->",
559            Self::ComponentDepth =>
560                "<ComponentDepth>9</ComponentDepth>  <!-- not in {8,10,12,16} -->",
561            Self::HorizontalSubsampling =>
562                "<HorizontalSubsampling>3</HorizontalSubsampling>  <!-- not in {1,2,4} -->",
563            Self::PaddingBits =>
564                "<PaddingBits>4</PaddingBits>  <!-- must be 0 -->",
565            Self::ReversedByteOrder =>
566                "<ReversedByteOrder>true</ReversedByteOrder>  <!-- byte reversal forbidden -->",
567            Self::VerticalSubsampling =>
568                "<VerticalSubsampling>3</VerticalSubsampling>  <!-- not in {1,2} -->",
569            Self::BlackRefLevel =>
570                "<ComponentDepth>10</ComponentDepth><BlackRefLevel>16</BlackRefLevel>  <!-- expected 64 for 10-bit -->",
571            Self::ColorRange =>
572                "<ComponentDepth>10</ComponentDepth><ColorRange>1019</ColorRange>  <!-- not in permitted set -->",
573            Self::WhiteRefLevel =>
574                "<ComponentDepth>10</ComponentDepth><WhiteRefLevel>235</WhiteRefLevel>  <!-- expected 940 for 10-bit -->",
575            Self::AudioSampleRate =>
576                "<AudioSampleRate>44100/1</AudioSampleRate>  <!-- must be 48000/1 -->",
577            Self::QuantizationBits =>
578                "<QuantizationBits>32</QuantizationBits>  <!-- not in {16,24} -->",
579            Self::CodingStyle =>
580                "JPEG2000SubDescriptor CodingStyleDefault Scod byte declares non-compliant entropy coding",
581            Self::J2CLayout =>
582                "JPEG2000SubDescriptor declares >1 layer or unsupported precinct sizes",
583            Self::J2KExtendedCapabilities =>
584                "JPEG2000SubDescriptor <Capabilities><Bit set=\"15\"/></Capabilities>  <!-- HT not permitted -->",
585            Self::Jpeg2000SubDescriptor =>
586                "<PictureDescriptor>…</PictureDescriptor>  <!-- no JPEG2000SubDescriptor child -->",
587            Self::J2KHtNotAllowed =>
588                "JPEG2000SubDescriptor Profile field declares HTJ2K (ISO 15444-15)",
589            Self::J2K4KResolution =>
590                "<StoredWidth>5000</StoredWidth>  <!-- IMF 4K profile caps width at 4096 -->",
591            Self::J2K2KResolution =>
592                "<StoredWidth>2400</StoredWidth>  <!-- IMF 2K profile caps width at 2048 -->",
593            Self::J2KBcpResolution =>
594                "<StoredWidth>3840</StoredWidth>  <!-- Broadcast Contribution Profile caps width at 1920 -->",
595            Self::ApplicationIdentification =>
596                "<CompositionPlaylist>…</CompositionPlaylist>  <!-- ExtensionProperties has no <ApplicationIdentification> -->",
597            Self::ContentMaturityRatingAgency =>
598                "<ContentMaturityRating><Agency></Agency>…</ContentMaturityRating>",
599            Self::ContentMaturityRatingAgencyUri =>
600                "<ContentMaturityRating><Agency>imdb</Agency>…</ContentMaturityRating>  <!-- not a URI -->",
601            Self::HomogeneousImageEssence =>
602                "Composition mixes Color1 (BT.709) and Color3 (BT.2020) resources across segments",
603            Self::AppIdMismatch =>
604                "<ApplicationIdentification>http://example.org/not-app2e</ApplicationIdentification>",
605            Self::SegmentDurationMultiple =>
606                "<Resource><SourceDuration>7</SourceDuration></Resource>  <!-- not a multiple of 5 -->",
607            Self::MaxCLLMaxFALL =>
608                "HDR content (PQ TransferCharacteristic) with no <MaxCLL> / <MaxFALL> in ExtensionProperties",
609        })
610    }
611}
612
613impl St2067_21_2023 {
614    pub const ALL: &'static [Self] = &[
615        Self::FrameRate,
616        Self::Resolution,
617        Self::EmptyLanguageTag,
618        Self::MalformedLanguageTag,
619        Self::RegionCode,
620        Self::ColorSystem,
621        Self::RequiredStoredWidth,
622        Self::RequiredStoredHeight,
623        Self::RequiredSampleRate,
624        Self::RequiredFrameLayout,
625        Self::RequiredColorPrimaries,
626        Self::RequiredTransferCharacteristic,
627        Self::RequiredPictureCompression,
628        Self::RequiredComponentDepth,
629        Self::RequiredChannelCount,
630        Self::RequiredQuantizationBits,
631        Self::AlphaTransparency,
632        Self::CodingEquationsMissing,
633        Self::ColorPrimariesMissing,
634        Self::FieldDominance,
635        Self::FrameLayout,
636        Self::FrameLayoutInterlaced,
637        Self::ImageAlignmentOffset,
638        Self::ImageEndOffset,
639        Self::ImageStartOffset,
640        Self::SampledHeight,
641        Self::SampledWidth,
642        Self::SampledXOffset,
643        Self::SampledYOffset,
644        Self::StoredF2Offset,
645        Self::TransferCharacteristicMissing,
646        Self::TransferCharacteristicUnknown,
647        Self::CodingEquationsUnknown,
648        Self::ColorPrimariesUnknown,
649        Self::J2KRequired,
650        Self::AlphaMaxRef,
651        Self::AlphaMinRef,
652        Self::ComponentMaxRef,
653        Self::ComponentMinRef,
654        Self::Palette,
655        Self::PaletteLayout,
656        Self::ScanningDirection,
657        Self::ComponentRefValues,
658        Self::AlphaSampleDepth,
659        Self::ColorSiting,
660        Self::ComponentDepth,
661        Self::HorizontalSubsampling,
662        Self::PaddingBits,
663        Self::ReversedByteOrder,
664        Self::VerticalSubsampling,
665        Self::BlackRefLevel,
666        Self::ColorRange,
667        Self::WhiteRefLevel,
668        Self::AudioSampleRate,
669        Self::QuantizationBits,
670        Self::CodingStyle,
671        Self::J2CLayout,
672        Self::J2KExtendedCapabilities,
673        Self::Jpeg2000SubDescriptor,
674        Self::J2KHtNotAllowed,
675        Self::J2K4KResolution,
676        Self::J2K2KResolution,
677        Self::J2KBcpResolution,
678        Self::ApplicationIdentification,
679        Self::ContentMaturityRatingAgency,
680        Self::ContentMaturityRatingAgencyUri,
681        Self::HomogeneousImageEssence,
682        Self::AppIdMismatch,
683        Self::SegmentDurationMultiple,
684        Self::MaxCLLMaxFALL,
685    ];
686}
687
688impl_into_string!(St2067_21_2023);
689
690// ─────────────────────────────────────────────────────────────────────────────
691// ST 2067-21:2025
692// ─────────────────────────────────────────────────────────────────────────────
693
694/// Validation codes defined by SMPTE ST 2067-21:2025 (App2E, timed-text additions).
695#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
696pub enum St2067_21_2025 {
697    /// Timed text track designated as Forced Narrative (FN) does not comply with §5.6.
698    FNTimedText,
699    /// Timed text track designated as HI-Caption (HIC) does not comply with §5.6.
700    HICTimedText,
701}
702
703impl ValidationCode for St2067_21_2025 {
704    fn code(&self) -> &'static str {
705        match self {
706            Self::FNTimedText => "ST2067-21:2025:5.6/FNTimedText",
707            Self::HICTimedText => "ST2067-21:2025:5.6/HICTimedText",
708        }
709    }
710    fn description(&self) -> &'static str {
711        match self {
712            Self::FNTimedText => {
713                "Timed text track designated as Forced Narrative (FN) does not comply with §5.6."
714            }
715            Self::HICTimedText => {
716                "Timed text track designated as HI-Caption (HIC) does not comply with §5.6."
717            }
718        }
719    }
720    fn default_severity(&self) -> Severity {
721        Severity::Error
722    }
723    fn category(&self) -> Category {
724        Category::Subtitle
725    }
726    fn example(&self) -> Option<&'static str> {
727        Some(match self {
728            Self::FNTimedText =>
729                "<ForcedNarrativeSequence>…</ForcedNarrativeSequence>  <!-- but the referenced essence is not a TimedText track -->",
730            Self::HICTimedText =>
731                "<HearingImpairedCaptionsSequence>…</HearingImpairedCaptionsSequence>  <!-- but the referenced essence is not a TimedText track -->",
732        })
733    }
734}
735
736impl St2067_21_2025 {
737    pub const ALL: &'static [Self] = &[Self::FNTimedText, Self::HICTimedText];
738}
739
740impl_into_string!(St2067_21_2025);