Skip to main content

imferno_core/mxf/
codes.rs

1//! Typed validation-code catalogue for SMPTE ST 377-1 (MXF) plus the
2//! ST 2067-2 / ST 377-4 essence-layer codes emitted by the modules in
3//! `mxf::{essence, audio_mca, timed_text, metadata}`, and the
4//! `IMFERNO:Mxf/*` engine-internal codes.
5//!
6//! Every code that ships in a `ValidationIssue` should resolve through
7//! one of these enums via `ValidationCode::code()` so renames stay
8//! compiler-checked.
9
10use crate::diagnostics::codes::ValidationCode;
11use crate::diagnostics::{Category, Severity};
12
13// ─── ST 377-1:2011 — MXF File Format ─────────────────────────────────────────
14
15/// Validation codes defined by SMPTE ST 377-1:2011 (MXF File Format).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
17pub enum St377_1_2011 {
18    /// File is not a valid MXF container.
19    NotMxf,
20    /// MXF file could not be parsed; may be truncated or corrupt.
21    ParseError,
22    /// MXF file contains no essence containers.
23    NoEssenceContainers,
24    /// Operational pattern is not OP1a as required by IMF.
25    Op1a,
26    /// First partition in the file is not the Header partition
27    /// (ST 377-1 §6.4 requires header-first ordering).
28    NonHeaderFirstPartition,
29    /// Header partition is Open (in-progress) — finished IMF
30    /// deliveries should be ClosedComplete per ST 377-1 §8.3.3.
31    HeaderPartitionOpen,
32    /// Header partition declares zero header metadata
33    /// (HeaderByteCount = 0) — ST 377-1 §8.3.3 requires header
34    /// metadata in the header partition.
35    MissingHeaderMetadata,
36}
37
38impl ValidationCode for St377_1_2011 {
39    fn code(&self) -> &'static str {
40        match self {
41            Self::NotMxf => "ST377-1:2011:5/NotMxf",
42            Self::ParseError => "ST377-1:2011:5/ParseError",
43            Self::NoEssenceContainers => "ST377-1:2011:11/NoEssenceContainers",
44            Self::Op1a => "ST377-1:2011:7/OP1a",
45            Self::NonHeaderFirstPartition => "ST377-1:2011:6.4/NonHeaderFirstPartition",
46            Self::HeaderPartitionOpen => "ST377-1:2011:8.3.3/HeaderPartitionOpen",
47            Self::MissingHeaderMetadata => "ST377-1:2011:8.3.3/MissingHeaderMetadata",
48        }
49    }
50    fn description(&self) -> &'static str {
51        match self {
52            Self::NotMxf => "File is not a valid MXF container.",
53            Self::ParseError => "MXF file could not be parsed; it may be truncated or corrupt.",
54            Self::NoEssenceContainers => "MXF file contains no essence containers.",
55            Self::Op1a => "MXF operational pattern must be OP1a for IMF packages.",
56            Self::NonHeaderFirstPartition => {
57                "The first partition in an MXF file must be the Header partition."
58            }
59            Self::HeaderPartitionOpen => {
60                "The header partition status should be ClosedComplete for finished deliveries."
61            }
62            Self::MissingHeaderMetadata => {
63                "The header partition must carry header metadata (HeaderByteCount > 0)."
64            }
65        }
66    }
67    fn default_severity(&self) -> Severity {
68        match self {
69            Self::NotMxf | Self::ParseError | Self::NoEssenceContainers => Severity::Warning,
70            Self::HeaderPartitionOpen => Severity::Warning,
71            Self::Op1a | Self::NonHeaderFirstPartition | Self::MissingHeaderMetadata => {
72                Severity::Error
73            }
74        }
75    }
76    fn category(&self) -> Category {
77        match self {
78            Self::NotMxf | Self::ParseError => Category::Asset,
79            Self::NoEssenceContainers | Self::Op1a => Category::Encoding,
80            Self::NonHeaderFirstPartition
81            | Self::HeaderPartitionOpen
82            | Self::MissingHeaderMetadata => Category::Container,
83        }
84    }
85    fn example(&self) -> Option<&'static str> {
86        Some(match self {
87            Self::NotMxf =>
88                "An asset declared in the PKL with MIME `application/mxf` whose header doesn't carry the MXF run-in / partition pack key.",
89            Self::ParseError =>
90                "An MXF file that was truncated mid-transfer; the footer partition is missing or the RIP is unreadable.",
91            Self::NoEssenceContainers =>
92                "An MXF file whose header metadata declares no EssenceContainer ULs — the body has no decodable essence.",
93            Self::Op1a =>
94                "An MXF file whose Preface declares OperationalPattern OP-Atom or OP-3a instead of OP-1a.",
95            Self::NonHeaderFirstPartition =>
96                "An authoring tool wrote a Body partition before the Header partition, violating ST 377-1 ordering.",
97            Self::HeaderPartitionOpen =>
98                "A live-streaming export shipped a Header partition with OpenIncomplete status instead of finalising to ClosedComplete.",
99            Self::MissingHeaderMetadata =>
100                "An MXF file whose Header partition advertises HeaderByteCount = 0 — no metadata sets follow the partition pack.",
101        })
102    }
103}
104
105impl St377_1_2011 {
106    pub const ALL: &'static [Self] = &[
107        Self::NotMxf,
108        Self::ParseError,
109        Self::NoEssenceContainers,
110        Self::Op1a,
111        Self::NonHeaderFirstPartition,
112        Self::HeaderPartitionOpen,
113        Self::MissingHeaderMetadata,
114    ];
115}
116
117impl From<St377_1_2011> for String {
118    fn from(c: St377_1_2011) -> String {
119        c.code().to_string()
120    }
121}
122
123// ─── ST 2067-2:2016 — Essence-layer (§5.2 / §5.3 / §5.4) ─────────────────────
124
125/// SMPTE ST 2067-2:2016 essence-layer codes (§5.2 OP1a, §5.3 audio
126/// MCA, §5.4 timed text). Cited from the 2016 edition because that's
127/// where the MCA work was stabilised; later editions inherit.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
129pub enum St2067_2_2016 {
130    /// §5.2 — operational pattern UL is not OP1a (ST 378:2004).
131    OperationalPatternNotOP1A,
132    /// §5.3.4.1 — audio essence descriptor is not WAVEPCMDescriptor.
133    SoundDescriptorNotWAVEPCM,
134    /// §5.3.2.2 — AudioSampleRate must be 48 000 or 96 000 Hz.
135    AudioSampleRateUnsupported,
136    /// §5.3.2.3 — QuantizationBits must be 24.
137    QuantizationBitsNot24,
138    /// §5.3.6.2 — number of AudioChannelLabelSubDescriptors must
139    /// equal ChannelCount.
140    ChannelLabelCountMismatch,
141    /// §5.3.6.2 — every channel ID 1..ChannelCount must have an
142    /// AudioChannelLabelSubDescriptor.
143    MCAChannelIDMissing,
144    /// §5.3.6.3 — exactly one SoundfieldGroupLabelSubDescriptor per
145    /// WAVEPCMDescriptor.
146    SoundFieldGroupLabelCount,
147    /// §5.3.3 / ST 382 §10 — audio essence must be Wave Clip-Wrapped.
148    AudioNotClipWrapped,
149    /// §5.3 — RFC-5646 spoken language tag recommended (Warning).
150    RFC5646SpokenLanguageMissing,
151    /// §5.3.4.2 — ChannelAssignment must be a SMPTE 428-12 MCA UL.
152    ChannelAssignmentNotMCA,
153    /// §5.3.6.5 — SoundfieldGroup MCATitle field missing (Warning).
154    SoundfieldGroupMissingMCATitle,
155    /// §5.3.6.5 — SoundfieldGroup MCATitleVersion missing (Warning).
156    SoundfieldGroupMissingMCATitleVersion,
157    /// §5.3.6.5 — SoundfieldGroup MCAAudioContentKind missing (Warning).
158    SoundfieldGroupMissingMCAAudioContentKind,
159    /// §5.3.6.5 — SoundfieldGroup MCAAudioElementKind missing (Warning).
160    SoundfieldGroupMissingMCAAudioElementKind,
161    /// §5.4 — timed text UCSEncoding must be UTF-8.
162    TimedTextUCSEncodingNotUTF8,
163    /// §5.4 — timed text NamespaceURI must be an IMSC1 profile.
164    TimedTextNamespaceNotIMSC,
165    /// §5.4.5/6 — TimeTextResourceSubDescriptor.MIMEType must be
166    /// image/png or application/x-font-opentype.
167    TimedTextResourceMIMETypeUnsupported,
168    /// §5.4 / ST 429-5 §7 — timed-text ContainerFormat UL byte 15
169    /// (Mapping Kind) must be 0x13 for IMSC.
170    TimedTextMappingKindNot0x13,
171}
172
173impl ValidationCode for St2067_2_2016 {
174    fn code(&self) -> &'static str {
175        match self {
176            Self::OperationalPatternNotOP1A => "ST2067-2:2016:5.2/OperationalPatternNotOP1A",
177            Self::SoundDescriptorNotWAVEPCM => "ST2067-2:2016:5.3.4.1/SoundDescriptorNotWAVEPCM",
178            Self::AudioSampleRateUnsupported => "ST2067-2:2016:5.3.2.2/AudioSampleRateUnsupported",
179            Self::QuantizationBitsNot24 => "ST2067-2:2016:5.3.2.3/QuantizationBitsNot24",
180            Self::ChannelLabelCountMismatch => "ST2067-2:2016:5.3.6.2/ChannelLabelCountMismatch",
181            Self::MCAChannelIDMissing => "ST2067-2:2016:5.3.6.2/MCAChannelIDMissing",
182            Self::SoundFieldGroupLabelCount => "ST2067-2:2016:5.3.6.3/SoundFieldGroupLabelCount",
183            Self::AudioNotClipWrapped => "ST2067-2:2016:5.3.3/AudioNotClipWrapped",
184            Self::RFC5646SpokenLanguageMissing => "ST2067-2:2016:5.3/RFC5646SpokenLanguageMissing",
185            Self::ChannelAssignmentNotMCA => "ST2067-2:2016:5.3.4.2/ChannelAssignmentNotMCA",
186            Self::SoundfieldGroupMissingMCATitle => {
187                "ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCATitle"
188            }
189            Self::SoundfieldGroupMissingMCATitleVersion => {
190                "ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCATitleVersion"
191            }
192            Self::SoundfieldGroupMissingMCAAudioContentKind => {
193                "ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCAAudioContentKind"
194            }
195            Self::SoundfieldGroupMissingMCAAudioElementKind => {
196                "ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCAAudioElementKind"
197            }
198            Self::TimedTextUCSEncodingNotUTF8 => "ST2067-2:2016:5.4/TimedTextUCSEncodingNotUTF8",
199            Self::TimedTextNamespaceNotIMSC => "ST2067-2:2016:5.4/TimedTextNamespaceNotIMSC",
200            Self::TimedTextResourceMIMETypeUnsupported => {
201                "ST2067-2:2016:5.4.5/TimedTextResourceMIMETypeUnsupported"
202            }
203            Self::TimedTextMappingKindNot0x13 => "ST2067-2:2016:5.4/TimedTextMappingKindNot0x13",
204        }
205    }
206    fn description(&self) -> &'static str {
207        match self {
208            Self::OperationalPatternNotOP1A =>
209                "MXF essence Operational Pattern UL is not OP1a (ST 378:2004).",
210            Self::SoundDescriptorNotWAVEPCM =>
211                "Audio essence descriptor must be WAVEPCMDescriptor.",
212            Self::AudioSampleRateUnsupported =>
213                "AudioSampleRate must be 48000 Hz or 96000 Hz.",
214            Self::QuantizationBitsNot24 => "QuantizationBits must be 24.",
215            Self::ChannelLabelCountMismatch =>
216                "Number of AudioChannelLabelSubDescriptors must equal ChannelCount.",
217            Self::MCAChannelIDMissing =>
218                "Every channel index 1..ChannelCount must have an AudioChannelLabelSubDescriptor.",
219            Self::SoundFieldGroupLabelCount =>
220                "Exactly one SoundfieldGroupLabelSubDescriptor is required per WAVEPCMDescriptor.",
221            Self::AudioNotClipWrapped =>
222                "Audio essence must be Wave Clip-Wrapped (ST 382 §10).",
223            Self::RFC5646SpokenLanguageMissing =>
224                "Audio descriptor should carry an RFC-5646 spoken language tag.",
225            Self::ChannelAssignmentNotMCA =>
226                "ChannelAssignment UL must be a SMPTE 428-12 MCA channel-layout UL.",
227            Self::SoundfieldGroupMissingMCATitle =>
228                "SoundfieldGroupLabelSubDescriptor is missing MCATitle.",
229            Self::SoundfieldGroupMissingMCATitleVersion =>
230                "SoundfieldGroupLabelSubDescriptor is missing MCATitleVersion.",
231            Self::SoundfieldGroupMissingMCAAudioContentKind =>
232                "SoundfieldGroupLabelSubDescriptor is missing MCAAudioContentKind.",
233            Self::SoundfieldGroupMissingMCAAudioElementKind =>
234                "SoundfieldGroupLabelSubDescriptor is missing MCAAudioElementKind.",
235            Self::TimedTextUCSEncodingNotUTF8 =>
236                "TimedTextDescriptor UCSEncoding must be UTF-8.",
237            Self::TimedTextNamespaceNotIMSC =>
238                "TimedTextDescriptor NamespaceURI must be one of the IMSC1 profile namespaces.",
239            Self::TimedTextResourceMIMETypeUnsupported =>
240                "TimeTextResourceSubDescriptor MIMEType must be image/png or application/x-font-opentype.",
241            Self::TimedTextMappingKindNot0x13 =>
242                "Timed-text ContainerFormat UL Mapping Kind byte must be 0x13 (IMSC).",
243        }
244    }
245    fn default_severity(&self) -> Severity {
246        match self {
247            // Recommendation-level rules emitted as Warning so operators
248            // can `Off` them when their pipeline doesn't require the field.
249            Self::RFC5646SpokenLanguageMissing
250            | Self::SoundfieldGroupMissingMCATitle
251            | Self::SoundfieldGroupMissingMCATitleVersion
252            | Self::SoundfieldGroupMissingMCAAudioContentKind
253            | Self::SoundfieldGroupMissingMCAAudioElementKind => Severity::Warning,
254            // Everything else is a "SHALL" rule — Error severity.
255            _ => Severity::Error,
256        }
257    }
258    fn category(&self) -> Category {
259        match self {
260            // Audio essence layer.
261            Self::SoundDescriptorNotWAVEPCM
262            | Self::AudioSampleRateUnsupported
263            | Self::QuantizationBitsNot24
264            | Self::ChannelLabelCountMismatch
265            | Self::MCAChannelIDMissing
266            | Self::SoundFieldGroupLabelCount
267            | Self::RFC5646SpokenLanguageMissing
268            | Self::ChannelAssignmentNotMCA
269            | Self::SoundfieldGroupMissingMCATitle
270            | Self::SoundfieldGroupMissingMCATitleVersion
271            | Self::SoundfieldGroupMissingMCAAudioContentKind
272            | Self::SoundfieldGroupMissingMCAAudioElementKind => Category::Audio,
273            // Timed-text essence layer.
274            Self::TimedTextUCSEncodingNotUTF8
275            | Self::TimedTextNamespaceNotIMSC
276            | Self::TimedTextResourceMIMETypeUnsupported => Category::Subtitle,
277            // Container-layer rules (operational pattern, wrapping,
278            // mapping kind — properties of the MXF wrapper, not the
279            // essence inside).
280            Self::OperationalPatternNotOP1A
281            | Self::AudioNotClipWrapped
282            | Self::TimedTextMappingKindNot0x13 => Category::Container,
283        }
284    }
285    fn example(&self) -> Option<&'static str> {
286        Some(match self {
287            Self::OperationalPatternNotOP1A =>
288                "Header partition pack OP UL bytes 13..14 = 02 03 (OP2a) instead of OP1a (01 01)",
289            Self::SoundDescriptorNotWAVEPCM =>
290                "Audio essence descriptor is AES3PCMDescriptor instead of WAVEPCMDescriptor",
291            Self::AudioSampleRateUnsupported =>
292                "<AudioSampleRate>44100/1</AudioSampleRate>  <!-- only 48000 or 96000 allowed -->",
293            Self::QuantizationBitsNot24 =>
294                "<QuantizationBits>16</QuantizationBits>",
295            Self::ChannelLabelCountMismatch =>
296                "<ChannelCount>6</ChannelCount> but only 2 AudioChannelLabelSubDescriptor entries present",
297            Self::MCAChannelIDMissing =>
298                "ChannelCount=6 but no AudioChannelLabelSubDescriptor with MCAChannelID=4",
299            Self::SoundFieldGroupLabelCount =>
300                "WAVEPCMDescriptor with 2 SoundfieldGroupLabelSubDescriptor children (must be exactly 1)",
301            Self::AudioNotClipWrapped =>
302                "ContainerFormat UL byte 14 = 0x02 (frame-wrapped) instead of 0x06 (clip-wrapped)",
303            Self::RFC5646SpokenLanguageMissing =>
304                "No RFC5646SpokenLanguage tag on any AudioChannelLabel/SoundfieldGroupLabel sub-descriptor",
305            Self::ChannelAssignmentNotMCA =>
306                "ChannelAssignment UL outside the SMPTE 428-12 MCA label range (bytes 9..16)",
307            Self::SoundfieldGroupMissingMCATitle =>
308                "SoundfieldGroupLabelSubDescriptor with no <MCATitle> item",
309            Self::SoundfieldGroupMissingMCATitleVersion =>
310                "SoundfieldGroupLabelSubDescriptor with no <MCATitleVersion> item",
311            Self::SoundfieldGroupMissingMCAAudioContentKind =>
312                "SoundfieldGroupLabelSubDescriptor with no <MCAAudioContentKind> item",
313            Self::SoundfieldGroupMissingMCAAudioElementKind =>
314                "SoundfieldGroupLabelSubDescriptor with no <MCAAudioElementKind> item",
315            Self::TimedTextUCSEncodingNotUTF8 =>
316                "<UCSEncoding>ISO-8859-1</UCSEncoding>",
317            Self::TimedTextNamespaceNotIMSC =>
318                "<NamespaceURI>http://www.w3.org/ns/ttml</NamespaceURI>  <!-- not an IMSC1 profile URI -->",
319            Self::TimedTextResourceMIMETypeUnsupported =>
320                "<TimeTextResourceSubDescriptor><MIMEType>application/json</MIMEType></TimeTextResourceSubDescriptor>",
321            Self::TimedTextMappingKindNot0x13 =>
322                "Timed-text ContainerFormat UL byte 15 (Mapping Kind) = 0x12 instead of 0x13 (IMSC)",
323        })
324    }
325}
326
327impl St2067_2_2016 {
328    pub const ALL: &'static [Self] = &[
329        Self::OperationalPatternNotOP1A,
330        Self::SoundDescriptorNotWAVEPCM,
331        Self::AudioSampleRateUnsupported,
332        Self::QuantizationBitsNot24,
333        Self::ChannelLabelCountMismatch,
334        Self::MCAChannelIDMissing,
335        Self::SoundFieldGroupLabelCount,
336        Self::AudioNotClipWrapped,
337        Self::RFC5646SpokenLanguageMissing,
338        Self::ChannelAssignmentNotMCA,
339        Self::SoundfieldGroupMissingMCATitle,
340        Self::SoundfieldGroupMissingMCATitleVersion,
341        Self::SoundfieldGroupMissingMCAAudioContentKind,
342        Self::SoundfieldGroupMissingMCAAudioElementKind,
343        Self::TimedTextUCSEncodingNotUTF8,
344        Self::TimedTextNamespaceNotIMSC,
345        Self::TimedTextResourceMIMETypeUnsupported,
346        Self::TimedTextMappingKindNot0x13,
347    ];
348}
349
350impl From<St2067_2_2016> for String {
351    fn from(c: St2067_2_2016) -> String {
352        c.code().to_string()
353    }
354}
355
356// ─── ST 377-4:2012 — MCA sub-descriptor linkage ──────────────────────────────
357
358/// Validation codes for SMPTE ST 377-4:2012 §6.3.2 MCA sub-descriptor
359/// linkage rules — the MCALinkID / SoundfieldGroupLinkID correlation
360/// that ties channel labels to their soundfield group.
361#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
362pub enum St377_4_2012 {
363    /// Required MCALinkID is missing on an MCA sub-descriptor.
364    MCALinkIDMissing,
365    /// AudioChannelLabelSubDescriptor's SoundfieldGroupLinkID does
366    /// not equal its enclosing SoundfieldGroup's MCALinkID.
367    SoundfieldGroupLinkIDMismatch,
368}
369
370impl ValidationCode for St377_4_2012 {
371    fn code(&self) -> &'static str {
372        match self {
373            Self::MCALinkIDMissing => "ST377-4:2012:6.3.2/MCALinkIDMissing",
374            Self::SoundfieldGroupLinkIDMismatch => {
375                "ST377-4:2012:6.3.2/SoundfieldGroupLinkIDMismatch"
376            }
377        }
378    }
379    fn description(&self) -> &'static str {
380        match self {
381            Self::MCALinkIDMissing => "Every MCA sub-descriptor must carry an MCALinkID.",
382            Self::SoundfieldGroupLinkIDMismatch =>
383                "AudioChannelLabelSubDescriptor SoundfieldGroupLinkID must equal the SoundfieldGroup's MCALinkID.",
384        }
385    }
386    fn default_severity(&self) -> Severity {
387        Severity::Error
388    }
389    fn category(&self) -> Category {
390        Category::Audio
391    }
392    fn example(&self) -> Option<&'static str> {
393        Some(match self {
394            Self::MCALinkIDMissing =>
395                "<AudioChannelLabelSubDescriptor>…</AudioChannelLabelSubDescriptor>  <!-- no <MCALinkID> child -->",
396            Self::SoundfieldGroupLinkIDMismatch =>
397                "<AudioChannelLabelSubDescriptor><SoundfieldGroupLinkID>urn:uuid:abc…</SoundfieldGroupLinkID></AudioChannelLabelSubDescriptor>  <!-- but the parent SoundfieldGroupLabelSubDescriptor MCALinkID = urn:uuid:def… -->",
398        })
399    }
400}
401
402impl St377_4_2012 {
403    pub const ALL: &'static [Self] = &[Self::MCALinkIDMissing, Self::SoundfieldGroupLinkIDMismatch];
404}
405
406impl From<St377_4_2012> for String {
407    fn from(c: St377_4_2012) -> String {
408        c.code().to_string()
409    }
410}
411
412// ─── IMFERNO engine-internal MXF codes ───────────────────────────────────────
413
414/// Engine-internal codes the MXF pipeline emits when it can't reach
415/// the spec layer — open failures, parse failures, conversion errors,
416/// and the informational "essence containers detected" trace.
417#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
418pub enum ImfernoMxf {
419    /// Could not open the MXF file for reading.
420    OpenFailed,
421    /// Failed to parse the MXF header partition pack.
422    PartitionPackParseFailed,
423    /// Failed to convert the MXF header metadata to RegXML for
424    /// downstream essence-rule application.
425    RegXmlConversionFailed,
426    /// Informational: the MXF declares N essence containers in its
427    /// header partition. Emitted on every clean MXF for report context.
428    EssenceContainersDetected,
429}
430
431impl ValidationCode for ImfernoMxf {
432    fn code(&self) -> &'static str {
433        match self {
434            Self::OpenFailed => "IMFERNO:Mxf/OpenFailed",
435            Self::PartitionPackParseFailed => "IMFERNO:Mxf/PartitionPackParseFailed",
436            Self::RegXmlConversionFailed => "IMFERNO:Mxf/RegXmlConversionFailed",
437            Self::EssenceContainersDetected => "IMFERNO:Mxf/EssenceContainersDetected",
438        }
439    }
440    fn description(&self) -> &'static str {
441        match self {
442            Self::OpenFailed => "MXF file could not be opened for reading.",
443            Self::PartitionPackParseFailed => "MXF header partition pack failed to parse.",
444            Self::RegXmlConversionFailed => "MXF header metadata could not be converted to RegXML.",
445            Self::EssenceContainersDetected => {
446                "Informational trace of how many essence containers the MXF declares."
447            }
448        }
449    }
450    fn default_severity(&self) -> Severity {
451        match self {
452            Self::OpenFailed | Self::PartitionPackParseFailed => Severity::Critical,
453            Self::RegXmlConversionFailed => Severity::Warning,
454            Self::EssenceContainersDetected => Severity::Info,
455        }
456    }
457    fn category(&self) -> Category {
458        Category::Container
459    }
460    fn example(&self) -> Option<&'static str> {
461        Some(match self {
462            Self::OpenFailed =>
463                "fs::File::open(\"audio1.mxf\") returned Err (file missing or unreadable)",
464            Self::PartitionPackParseFailed =>
465                "Header partition pack KLV is malformed (e.g. BER length undershoots the minimum 88-byte body)",
466            Self::RegXmlConversionFailed =>
467                "smpte-mxf could not convert header metadata to RegXML (e.g. unknown KLV set in header)",
468            Self::EssenceContainersDetected =>
469                "Header partition pack declares N essence container ULs — info notice surfaces the count for traceability",
470        })
471    }
472}
473
474impl ImfernoMxf {
475    pub const ALL: &'static [Self] = &[
476        Self::OpenFailed,
477        Self::PartitionPackParseFailed,
478        Self::RegXmlConversionFailed,
479        Self::EssenceContainersDetected,
480    ];
481}
482
483impl From<ImfernoMxf> for String {
484    fn from(c: ImfernoMxf) -> String {
485        c.code().to_string()
486    }
487}