imferno-core 0.1.2

SMPTE ST 2067 IMF parser and validator
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
//! Typed validation-code catalogue for SMPTE ST 2067-21 (Application Profile #2E).

use crate::diagnostics::codes::ValidationCode;
use crate::diagnostics::{Category, Severity};

macro_rules! impl_into_string {
    ($t:ty) => {
        impl From<$t> for String {
            fn from(c: $t) -> String {
                <$t as ValidationCode>::code(&c).to_string()
            }
        }
    };
}

// ─────────────────────────────────────────────────────────────────────────────
// ST 2067-21:2020
// ─────────────────────────────────────────────────────────────────────────────

/// Validation codes defined by SMPTE ST 2067-21:2020 (Application Profile #2E).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum St2067_21_2020 {
    /// Application identifier in CPL ExtensionProperties does not match the expected App2E URI.
    AppIdMismatch,
}

impl ValidationCode for St2067_21_2020 {
    fn code(&self) -> &'static str {
        match self {
            Self::AppIdMismatch => "ST2067-21:2020:7.1/AppIdMismatch",
        }
    }
    fn description(&self) -> &'static str {
        match self {
            Self::AppIdMismatch =>
                "Application identifier in CPL ExtensionProperties does not match the expected App2E URI.",
        }
    }
    fn default_severity(&self) -> Severity {
        Severity::Warning
    }
    fn category(&self) -> Category {
        Category::Metadata
    }
}

impl St2067_21_2020 {
    pub const ALL: &'static [Self] = &[Self::AppIdMismatch];
}

impl_into_string!(St2067_21_2020);

// ─────────────────────────────────────────────────────────────────────────────
// ST 2067-21:2023
// ─────────────────────────────────────────────────────────────────────────────

/// Validation codes defined by SMPTE ST 2067-21:2023 (Application Profile #2E, UHD/HDR).
///
/// Variant naming notes:
/// - Variants ending in `Unknown` check for unrecognized UL values (wrong value).
/// - Variants starting with `Required` check for missing mandatory fields.
/// - Plain variants (e.g. `ColorPrimaries`) check field presence at §6.2.1.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum St2067_21_2023 {
    // ── §5.2 Frame rates and resolutions ─────────────────────────────────────
    /// Frame rate is not in the permitted set for App2E.
    FrameRate,
    /// Image resolution is not in the permitted set for App2E.
    Resolution,

    // ── §5.3 Language tags ────────────────────────────────────────────────────
    /// Locale language tag is empty.
    EmptyLanguageTag,
    /// Locale language tag is not a valid BCP-47 subtag.
    MalformedLanguageTag,
    /// Region subtag in a language tag is not valid.
    RegionCode,

    // ── §6.2 Color system ────────────────────────────────────────────────────
    /// Color system designator is not in the permitted set.
    ColorSystem,

    // ── §6.2 Required picture-descriptor fields ───────────────────────────────
    /// RGBA/CDCI descriptor is missing the required `StoredWidth` field.
    RequiredStoredWidth,
    /// RGBA/CDCI descriptor is missing the required `StoredHeight` field.
    RequiredStoredHeight,
    /// RGBA/CDCI descriptor is missing the required `SampleRate` field.
    RequiredSampleRate,
    /// RGBA/CDCI descriptor is missing the required `FrameLayout` field.
    RequiredFrameLayout,
    /// RGBA/CDCI descriptor is missing the required `ColorPrimaries` field.
    RequiredColorPrimaries,
    /// RGBA/CDCI descriptor is missing the required `TransferCharacteristic` field.
    RequiredTransferCharacteristic,
    /// RGBA/CDCI descriptor is missing the required `PictureCompression` field.
    RequiredPictureCompression,
    /// CDCI descriptor is missing the required `ComponentDepth` field.
    RequiredComponentDepth,

    // ── §6.5 Required audio-descriptor fields ─────────────────────────────────
    /// WavePCM descriptor is missing the required `ChannelCount` field.
    RequiredChannelCount,
    /// WavePCM descriptor is missing the required `QuantizationBits` field.
    RequiredQuantizationBits,

    // ── §6.2.1 Picture descriptor constraints ────────────────────────────────
    /// Alpha transparency mode is not permitted in App2E.
    AlphaTransparency,
    /// `CodingEquations` field is absent from the picture descriptor (§6.2.1 Table 8).
    CodingEquations,
    /// `ColorPrimaries` field is absent from the picture descriptor (§6.2.1 Table 8).
    ColorPrimaries,
    /// `FieldDominance` value is not permitted for the declared `FrameLayout`.
    FieldDominance,
    /// `FrameLayout` value is not in the permitted set for App2E.
    FrameLayout,
    /// `FrameLayout` declares interlaced content, which is not permitted in App2E.
    FrameLayoutInterlaced,
    /// `ImageAlignmentOffset` value is not zero as required.
    ImageAlignmentOffset,
    /// `ImageEndOffset` value is not zero as required.
    ImageEndOffset,
    /// `ImageStartOffset` value is not zero as required.
    ImageStartOffset,
    /// `SampledHeight` does not equal `StoredHeight` as required.
    SampledHeight,
    /// `SampledWidth` does not equal `StoredWidth` as required.
    SampledWidth,
    /// `SampledXOffset` is not zero as required.
    SampledXOffset,
    /// `SampledYOffset` is not zero as required.
    SampledYOffset,
    /// `StoredF2Offset` is not zero as required for interlaced content.
    StoredF2Offset,
    /// `TransferCharacteristic` field is absent from the picture descriptor (§6.2.1 Table 8).
    TransferCharacteristic,

    // ── §6.2.2 Transfer characteristic value ────────────────────────────────
    /// `TransferCharacteristic` UL is present but not a recognized value (§6.2.2).
    TransferCharacteristicUnknown,

    // ── §6.2.3 Coding equations value ────────────────────────────────────────
    /// `CodingEquations` UL is present but not a recognized value (§6.2.3).
    CodingEquationsUnknown,

    // ── §6.2.4 Color primaries value ─────────────────────────────────────────
    /// `ColorPrimaries` UL is present but not a recognized value (§6.2.4).
    ColorPrimariesUnknown,

    // ── §6.2.5 JPEG 2000 requirement ─────────────────────────────────────────
    /// Video essence is not JPEG 2000 encoded as required by App2E.
    J2KRequired,

    // ── §6.3 RGBA descriptor ─────────────────────────────────────────────────
    /// `AlphaMaxRef` value is not permitted.
    AlphaMaxRef,
    /// `AlphaMinRef` value is not permitted.
    AlphaMinRef,
    /// `ComponentMaxRef` value is not in the permitted range.
    ComponentMaxRef,
    /// `ComponentMinRef` value is not in the permitted range.
    ComponentMinRef,
    /// `Palette` is present; palette images are not permitted in App2E.
    Palette,
    /// `PaletteLayout` is present; palette layout is not permitted in App2E.
    PaletteLayout,
    /// `ScanningDirection` value is not in the permitted set.
    ScanningDirection,

    // ── §6.3.2 Component reference values ────────────────────────────────────
    /// Component max/min reference values are inconsistent with bit depth.
    ComponentRefValues,

    // ── §6.4 Bit depth and chroma ────────────────────────────────────────────
    /// `AlphaSampleDepth` value is not permitted.
    AlphaSampleDepth,
    /// `ColorSiting` value is not in the permitted set.
    ColorSiting,
    /// `ComponentDepth` value is not in the permitted set (8/10/12/16).
    ComponentDepth,
    /// `HorizontalSubsampling` value is not in the permitted set.
    HorizontalSubsampling,
    /// `PaddingBits` value is not zero as required.
    PaddingBits,
    /// `ReversedByteOrder` flag is set; byte reversal is not permitted.
    ReversedByteOrder,
    /// `VerticalSubsampling` value is not in the permitted set.
    VerticalSubsampling,

    // ── §6.4.3 Luma range ────────────────────────────────────────────────────
    /// `BlackRefLevel` value is inconsistent with bit depth.
    BlackRefLevel,
    /// `ColorRange` value is not in the permitted set.
    ColorRange,
    /// `WhiteRefLevel` value is inconsistent with bit depth.
    WhiteRefLevel,

    // ── §6.5 Audio ───────────────────────────────────────────────────────────
    /// Audio sample rate is not 48 kHz as required.
    AudioSampleRate,
    /// `QuantizationBits` value is not in the permitted set (16/24).
    QuantizationBits,

    // ── §6.5.2 JPEG 2000 sub-descriptor ─────────────────────────────────────
    /// J2K codestream coding style is not compliant.
    CodingStyle,
    /// JPEG 2000 codestream layout (J2C) does not meet App2E requirements.
    J2CLayout,
    /// JPEG 2000 extended capabilities are declared but not permitted.
    J2KExtendedCapabilities,
    /// JPEG2000SubDescriptor is absent or incomplete.
    Jpeg2000SubDescriptor,

    // ── §6.2.5 JPEG 2000 resolution profiles ────────────────────────────────
    /// JPEG 2000 HT (ISO 15444-15) is not permitted by the App2E 2020 edition.
    J2KHtNotAllowed,
    /// JPEG 2000 IMF 4K Profile: stored resolution is outside the permitted range.
    J2K4KResolution,
    /// JPEG 2000 IMF 2K Profile: stored resolution is outside the permitted range.
    J2K2KResolution,
    /// JPEG 2000 Broadcast Contribution Profile: stored resolution is outside the permitted range.
    J2KBcpResolution,

    // ── §7.1 Application identification ──────────────────────────────────────
    /// ApplicationIdentification is required for App2E compositions.
    ApplicationIdentification,
    /// ContentMaturityRating agency is empty.
    ContentMaturityRatingAgency,
    /// ContentMaturityRating agency is not a valid xs:anyURI.
    ContentMaturityRatingAgencyUri,

    // ── §7.2 Homogeneous image essence ───────────────────────────────────────
    /// All image essence in a composition shall use the same color system.
    HomogeneousImageEssence,

    // ── §7.1 Application identification ──────────────────────────────────────
    /// Application identifier in CPL ExtensionProperties does not match the App2E URI.
    AppIdMismatch,

    // ── §7.4 Segment duration ─────────────────────────────────────────────────
    /// Segment duration is not an integer multiple of 5 edit units as required by App2E.
    SegmentDurationMultiple,

    // ── §7.5 HDR metadata ────────────────────────────────────────────────────
    /// `MaxCLL` / `MaxFALL` HDR metadata is absent; recommended for HDR content.
    MaxCLLMaxFALL,
}

impl ValidationCode for St2067_21_2023 {
    fn code(&self) -> &'static str {
        match self {
            Self::FrameRate                      => "ST2067-21:2023:5.2/FrameRate",
            Self::Resolution                     => "ST2067-21:2023:5.2/Resolution",
            Self::EmptyLanguageTag               => "ST2067-21:2023:5.3/EmptyLanguageTag",
            Self::MalformedLanguageTag           => "ST2067-21:2023:5.3/MalformedLanguageTag",
            Self::RegionCode                     => "ST2067-21:2023:5.3/RegionCode",
            Self::ColorSystem                    => "ST2067-21:2023:6.2/ColorSystem",
            Self::RequiredStoredWidth            => "ST2067-21:2023:6.2/Required-StoredWidth",
            Self::RequiredStoredHeight           => "ST2067-21:2023:6.2/Required-StoredHeight",
            Self::RequiredSampleRate             => "ST2067-21:2023:6.2/Required-SampleRate",
            Self::RequiredFrameLayout            => "ST2067-21:2023:6.2/Required-FrameLayout",
            Self::RequiredColorPrimaries         => "ST2067-21:2023:6.2/Required-ColorPrimaries",
            Self::RequiredTransferCharacteristic => "ST2067-21:2023:6.2/Required-TransferCharacteristic",
            Self::RequiredPictureCompression     => "ST2067-21:2023:6.2/Required-PictureCompression",
            Self::RequiredComponentDepth         => "ST2067-21:2023:6.2/Required-ComponentDepth",
            Self::RequiredChannelCount           => "ST2067-21:2023:6.5/Required-ChannelCount",
            Self::RequiredQuantizationBits       => "ST2067-21:2023:6.5/Required-QuantizationBits",
            Self::AlphaTransparency              => "ST2067-21:2023:6.2.1/AlphaTransparency",
            Self::CodingEquations                => "ST2067-21:2023:6.2.1/CodingEquations",
            Self::ColorPrimaries                 => "ST2067-21:2023:6.2.1/ColorPrimaries",
            Self::FieldDominance                 => "ST2067-21:2023:6.2.1/FieldDominance",
            Self::FrameLayout                    => "ST2067-21:2023:6.2.1/FrameLayout",
            Self::FrameLayoutInterlaced          => "ST2067-21:2023:6.2.1/FrameLayoutInterlaced",
            Self::ImageAlignmentOffset           => "ST2067-21:2023:6.2.1/ImageAlignmentOffset",
            Self::ImageEndOffset                 => "ST2067-21:2023:6.2.1/ImageEndOffset",
            Self::ImageStartOffset               => "ST2067-21:2023:6.2.1/ImageStartOffset",
            Self::SampledHeight                  => "ST2067-21:2023:6.2.1/SampledHeight",
            Self::SampledWidth                   => "ST2067-21:2023:6.2.1/SampledWidth",
            Self::SampledXOffset                 => "ST2067-21:2023:6.2.1/SampledXOffset",
            Self::SampledYOffset                 => "ST2067-21:2023:6.2.1/SampledYOffset",
            Self::StoredF2Offset                 => "ST2067-21:2023:6.2.1/StoredF2Offset",
            Self::TransferCharacteristic         => "ST2067-21:2023:6.2.1/TransferCharacteristic",
            Self::TransferCharacteristicUnknown  => "ST2067-21:2023:6.2.2/TransferCharacteristic",
            Self::CodingEquationsUnknown         => "ST2067-21:2023:6.2.3/CodingEquations",
            Self::ColorPrimariesUnknown          => "ST2067-21:2023:6.2.4/ColorPrimaries",
            Self::J2KRequired                    => "ST2067-21:2023:6.2.5/J2KRequired",
            Self::AlphaMaxRef                    => "ST2067-21:2023:6.3/AlphaMaxRef",
            Self::AlphaMinRef                    => "ST2067-21:2023:6.3/AlphaMinRef",
            Self::ComponentMaxRef                => "ST2067-21:2023:6.3/ComponentMaxRef",
            Self::ComponentMinRef                => "ST2067-21:2023:6.3/ComponentMinRef",
            Self::Palette                        => "ST2067-21:2023:6.3/Palette",
            Self::PaletteLayout                  => "ST2067-21:2023:6.3/PaletteLayout",
            Self::ScanningDirection              => "ST2067-21:2023:6.3/ScanningDirection",
            Self::ComponentRefValues             => "ST2067-21:2023:6.3.2/ComponentRefValues",
            Self::AlphaSampleDepth               => "ST2067-21:2023:6.4/AlphaSampleDepth",
            Self::ColorSiting                    => "ST2067-21:2023:6.4/ColorSiting",
            Self::ComponentDepth                 => "ST2067-21:2023:6.4/ComponentDepth",
            Self::HorizontalSubsampling          => "ST2067-21:2023:6.4/HorizontalSubsampling",
            Self::PaddingBits                    => "ST2067-21:2023:6.4/PaddingBits",
            Self::ReversedByteOrder              => "ST2067-21:2023:6.4/ReversedByteOrder",
            Self::VerticalSubsampling            => "ST2067-21:2023:6.4/VerticalSubsampling",
            Self::BlackRefLevel                  => "ST2067-21:2023:6.4.3/BlackRefLevel",
            Self::ColorRange                     => "ST2067-21:2023:6.4.3/ColorRange",
            Self::WhiteRefLevel                  => "ST2067-21:2023:6.4.3/WhiteRefLevel",
            Self::AudioSampleRate                => "ST2067-21:2023:6.5/AudioSampleRate",
            Self::QuantizationBits               => "ST2067-21:2023:6.5/QuantizationBits",
            Self::CodingStyle                    => "ST2067-21:2023:6.5.2/CodingStyle",
            Self::J2CLayout                      => "ST2067-21:2023:6.5.2/J2CLayout",
            Self::J2KExtendedCapabilities        => "ST2067-21:2023:6.5.2/J2KExtendedCapabilities",
            Self::Jpeg2000SubDescriptor          => "ST2067-21:2023:6.5.2/JPEG2000SubDescriptor",
            Self::J2KHtNotAllowed                => "ST2067-21:2023:6.2.5/J2K-HT-Not-Allowed",
            Self::J2K4KResolution                => "ST2067-21:2023:6.2.5/J2K-4K-Resolution",
            Self::J2K2KResolution                => "ST2067-21:2023:6.2.5/J2K-2K-Resolution",
            Self::J2KBcpResolution               => "ST2067-21:2023:6.2.5/J2K-BCP-Resolution",
            Self::ApplicationIdentification      => "ST2067-21:2023:7.1/ApplicationIdentification",
            Self::ContentMaturityRatingAgency    => "ST2067-21:2023:7.1/ContentMaturityRating-Agency",
            Self::ContentMaturityRatingAgencyUri => "ST2067-21:2023:7.1/ContentMaturityRating-Agency-URI",
            Self::HomogeneousImageEssence        => "ST2067-21:2023:7.2/HomogeneousImageEssence",
            Self::AppIdMismatch                  => "ST2067-21:2023:7.1/AppIdMismatch",
            Self::SegmentDurationMultiple        => "ST2067-21:2023:7.4/SegmentDurationMultiple",
            Self::MaxCLLMaxFALL                  => "ST2067-21:2023:7.5/MaxCLLMaxFALL",
        }
    }

    fn description(&self) -> &'static str {
        match self {
            Self::FrameRate => "Frame rate is not in the permitted set for App2E.",
            Self::Resolution => "Image resolution is not in the permitted set for App2E.",
            Self::EmptyLanguageTag => "Locale language tag is empty.",
            Self::MalformedLanguageTag => "Locale language tag is not a valid BCP-47 subtag.",
            Self::RegionCode => "Region subtag in a language tag is not valid.",
            Self::ColorSystem => "Color system designator is not in the permitted set.",
            Self::RequiredStoredWidth => "RGBA/CDCI descriptor is missing the required StoredWidth field.",
            Self::RequiredStoredHeight => "RGBA/CDCI descriptor is missing the required StoredHeight field.",
            Self::RequiredSampleRate => "RGBA/CDCI descriptor is missing the required SampleRate field.",
            Self::RequiredFrameLayout => "RGBA/CDCI descriptor is missing the required FrameLayout field.",
            Self::RequiredColorPrimaries => "RGBA/CDCI descriptor is missing the required ColorPrimaries field.",
            Self::RequiredTransferCharacteristic => "RGBA/CDCI descriptor is missing the required TransferCharacteristic field.",
            Self::RequiredPictureCompression => "RGBA/CDCI descriptor is missing the required PictureCompression field.",
            Self::RequiredComponentDepth => "CDCI descriptor is missing the required ComponentDepth field.",
            Self::RequiredChannelCount => "WavePCM descriptor is missing the required ChannelCount field.",
            Self::RequiredQuantizationBits => "WavePCM descriptor is missing the required QuantizationBits field.",
            Self::AlphaTransparency => "Alpha transparency mode is not permitted in App2E.",
            Self::CodingEquations => "CodingEquations field is absent from the picture descriptor (Table 8).",
            Self::ColorPrimaries => "ColorPrimaries field is absent from the picture descriptor (Table 8).",
            Self::FieldDominance => "FieldDominance value is not permitted for the declared FrameLayout.",
            Self::FrameLayout => "FrameLayout value is not in the permitted set for App2E.",
            Self::FrameLayoutInterlaced => "FrameLayout declares interlaced content, which is not permitted in App2E.",
            Self::ImageAlignmentOffset => "ImageAlignmentOffset must be zero.",
            Self::ImageEndOffset => "ImageEndOffset must be zero.",
            Self::ImageStartOffset => "ImageStartOffset must be zero.",
            Self::SampledHeight => "SampledHeight must equal StoredHeight.",
            Self::SampledWidth => "SampledWidth must equal StoredWidth.",
            Self::SampledXOffset => "SampledXOffset must be zero.",
            Self::SampledYOffset => "SampledYOffset must be zero.",
            Self::StoredF2Offset => "StoredF2Offset must be zero.",
            Self::TransferCharacteristic => "TransferCharacteristic field is absent from the picture descriptor (Table 8).",
            Self::TransferCharacteristicUnknown => "TransferCharacteristic UL is present but not a recognized value.",
            Self::CodingEquationsUnknown => "CodingEquations UL is present but not a recognized value.",
            Self::ColorPrimariesUnknown => "ColorPrimaries UL is present but not a recognized value.",
            Self::J2KRequired => "Video essence is not JPEG 2000 encoded as required by App2E.",
            Self::AlphaMaxRef => "AlphaMaxRef value is not permitted.",
            Self::AlphaMinRef => "AlphaMinRef value is not permitted.",
            Self::ComponentMaxRef => "ComponentMaxRef value is not in the permitted range.",
            Self::ComponentMinRef => "ComponentMinRef value is not in the permitted range.",
            Self::Palette => "Palette is present; palette images are not permitted in App2E.",
            Self::PaletteLayout => "PaletteLayout is present; palette layout is not permitted in App2E.",
            Self::ScanningDirection => "ScanningDirection value is not in the permitted set.",
            Self::ComponentRefValues => "Component max/min reference values are inconsistent with bit depth.",
            Self::AlphaSampleDepth => "AlphaSampleDepth value is not permitted.",
            Self::ColorSiting => "ColorSiting value is not in the permitted set.",
            Self::ComponentDepth => "ComponentDepth value is not in the permitted set (8 / 10 / 12 / 16).",
            Self::HorizontalSubsampling => "HorizontalSubsampling value is not in the permitted set.",
            Self::PaddingBits => "PaddingBits must be zero.",
            Self::ReversedByteOrder => "ReversedByteOrder flag is set; byte reversal is not permitted.",
            Self::VerticalSubsampling => "VerticalSubsampling value is not in the permitted set.",
            Self::BlackRefLevel => "BlackRefLevel value is inconsistent with ComponentDepth.",
            Self::ColorRange => "ColorRange value is not in the permitted set.",
            Self::WhiteRefLevel => "WhiteRefLevel value is inconsistent with ComponentDepth.",
            Self::AudioSampleRate => "Audio sample rate must be 48 000 Hz.",
            Self::QuantizationBits => "QuantizationBits must be 16 or 24.",
            Self::CodingStyle => "JPEG 2000 codestream coding style is not compliant.",
            Self::J2CLayout => "JPEG 2000 codestream layout does not meet App2E requirements.",
            Self::J2KExtendedCapabilities => "JPEG 2000 extended capabilities are declared but not permitted.",
            Self::Jpeg2000SubDescriptor => "JPEG2000SubDescriptor is absent or incomplete.",
            Self::J2KHtNotAllowed                => "JPEG 2000 HT (ISO 15444-15) is not permitted by App2E 2020.",
            Self::J2K4KResolution                => "JPEG 2000 IMF 4K Profile: stored resolution is outside the permitted range.",
            Self::J2K2KResolution                => "JPEG 2000 IMF 2K Profile: stored resolution is outside the permitted range.",
            Self::J2KBcpResolution               => "JPEG 2000 Broadcast Contribution Profile: stored resolution is outside the permitted range.",
            Self::ApplicationIdentification      => "ApplicationIdentification is required for App2E compositions.",
            Self::ContentMaturityRatingAgency    => "ContentMaturityRating Agency is empty.",
            Self::ContentMaturityRatingAgencyUri => "ContentMaturityRating Agency is not a valid xs:anyURI.",
            Self::HomogeneousImageEssence        => "All image essence in a composition shall use the same color system.",
            Self::AppIdMismatch => "Application identifier in CPL ExtensionProperties does not match the expected App2E URI.",
            Self::SegmentDurationMultiple => "Segment duration must be an integer multiple of 5 edit units.",
            Self::MaxCLLMaxFALL => "MaxCLL / MaxFALL HDR metadata is absent; recommended for HDR content.",
        }
    }

    fn default_severity(&self) -> Severity {
        match self {
            Self::MaxCLLMaxFALL => Severity::Info,
            Self::AppIdMismatch | Self::Jpeg2000SubDescriptor => Severity::Warning,
            Self::ContentMaturityRatingAgency | Self::ContentMaturityRatingAgencyUri => {
                Severity::Error
            }
            _ => Severity::Error,
        }
    }

    fn category(&self) -> Category {
        match self {
            Self::EmptyLanguageTag
            | Self::MalformedLanguageTag
            | Self::RegionCode
            | Self::AppIdMismatch
            | Self::ApplicationIdentification
            | Self::ContentMaturityRatingAgency
            | Self::ContentMaturityRatingAgencyUri => Category::Metadata,

            Self::RequiredChannelCount
            | Self::RequiredQuantizationBits
            | Self::AudioSampleRate
            | Self::QuantizationBits => Category::Audio,

            Self::J2KRequired
            | Self::CodingStyle
            | Self::J2CLayout
            | Self::J2KExtendedCapabilities
            | Self::Jpeg2000SubDescriptor
            | Self::J2KHtNotAllowed
            | Self::J2K4KResolution
            | Self::J2K2KResolution
            | Self::J2KBcpResolution
            | Self::RequiredStoredWidth
            | Self::RequiredStoredHeight
            | Self::RequiredSampleRate
            | Self::RequiredFrameLayout
            | Self::RequiredColorPrimaries
            | Self::RequiredTransferCharacteristic
            | Self::RequiredPictureCompression
            | Self::RequiredComponentDepth => Category::Encoding,

            Self::SegmentDurationMultiple => Category::Timing,

            _ => Category::Video,
        }
    }
}

impl St2067_21_2023 {
    pub const ALL: &'static [Self] = &[
        Self::FrameRate,
        Self::Resolution,
        Self::EmptyLanguageTag,
        Self::MalformedLanguageTag,
        Self::RegionCode,
        Self::ColorSystem,
        Self::RequiredStoredWidth,
        Self::RequiredStoredHeight,
        Self::RequiredSampleRate,
        Self::RequiredFrameLayout,
        Self::RequiredColorPrimaries,
        Self::RequiredTransferCharacteristic,
        Self::RequiredPictureCompression,
        Self::RequiredComponentDepth,
        Self::RequiredChannelCount,
        Self::RequiredQuantizationBits,
        Self::AlphaTransparency,
        Self::CodingEquations,
        Self::ColorPrimaries,
        Self::FieldDominance,
        Self::FrameLayout,
        Self::FrameLayoutInterlaced,
        Self::ImageAlignmentOffset,
        Self::ImageEndOffset,
        Self::ImageStartOffset,
        Self::SampledHeight,
        Self::SampledWidth,
        Self::SampledXOffset,
        Self::SampledYOffset,
        Self::StoredF2Offset,
        Self::TransferCharacteristic,
        Self::TransferCharacteristicUnknown,
        Self::CodingEquationsUnknown,
        Self::ColorPrimariesUnknown,
        Self::J2KRequired,
        Self::AlphaMaxRef,
        Self::AlphaMinRef,
        Self::ComponentMaxRef,
        Self::ComponentMinRef,
        Self::Palette,
        Self::PaletteLayout,
        Self::ScanningDirection,
        Self::ComponentRefValues,
        Self::AlphaSampleDepth,
        Self::ColorSiting,
        Self::ComponentDepth,
        Self::HorizontalSubsampling,
        Self::PaddingBits,
        Self::ReversedByteOrder,
        Self::VerticalSubsampling,
        Self::BlackRefLevel,
        Self::ColorRange,
        Self::WhiteRefLevel,
        Self::AudioSampleRate,
        Self::QuantizationBits,
        Self::CodingStyle,
        Self::J2CLayout,
        Self::J2KExtendedCapabilities,
        Self::Jpeg2000SubDescriptor,
        Self::J2KHtNotAllowed,
        Self::J2K4KResolution,
        Self::J2K2KResolution,
        Self::J2KBcpResolution,
        Self::ApplicationIdentification,
        Self::ContentMaturityRatingAgency,
        Self::ContentMaturityRatingAgencyUri,
        Self::HomogeneousImageEssence,
        Self::AppIdMismatch,
        Self::SegmentDurationMultiple,
        Self::MaxCLLMaxFALL,
    ];
}

impl_into_string!(St2067_21_2023);

// ─────────────────────────────────────────────────────────────────────────────
// ST 2067-21:2025
// ─────────────────────────────────────────────────────────────────────────────

/// Validation codes defined by SMPTE ST 2067-21:2025 (App2E, timed-text additions).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum St2067_21_2025 {
    /// Timed text track designated as Forced Narrative (FN) does not comply with §5.6.
    FNTimedText,
    /// Timed text track designated as HI-Caption (HIC) does not comply with §5.6.
    HICTimedText,
}

impl ValidationCode for St2067_21_2025 {
    fn code(&self) -> &'static str {
        match self {
            Self::FNTimedText  => "ST2067-21:2025:5.6/FNTimedText",
            Self::HICTimedText => "ST2067-21:2025:5.6/HICTimedText",
        }
    }
    fn description(&self) -> &'static str {
        match self {
            Self::FNTimedText =>
                "Timed text track designated as Forced Narrative (FN) does not comply with §5.6.",
            Self::HICTimedText =>
                "Timed text track designated as HI-Caption (HIC) does not comply with §5.6.",
        }
    }
    fn default_severity(&self) -> Severity {
        Severity::Error
    }
    fn category(&self) -> Category {
        Category::Subtitle
    }
}

impl St2067_21_2025 {
    pub const ALL: &'static [Self] = &[Self::FNTimedText, Self::HICTimedText];
}

impl_into_string!(St2067_21_2025);