1use crate::diagnostics::codes::ValidationCode;
11use crate::diagnostics::{Category, Severity};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
17pub enum St377_1_2011 {
18 NotMxf,
20 ParseError,
22 NoEssenceContainers,
24 Op1a,
26 NonHeaderFirstPartition,
29 HeaderPartitionOpen,
32 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#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
129pub enum St2067_2_2016 {
130 OperationalPatternNotOP1A,
132 SoundDescriptorNotWAVEPCM,
134 AudioSampleRateUnsupported,
136 QuantizationBitsNot24,
138 ChannelLabelCountMismatch,
141 MCAChannelIDMissing,
144 SoundFieldGroupLabelCount,
147 AudioNotClipWrapped,
149 RFC5646SpokenLanguageMissing,
151 ChannelAssignmentNotMCA,
153 SoundfieldGroupMissingMCATitle,
155 SoundfieldGroupMissingMCATitleVersion,
157 SoundfieldGroupMissingMCAAudioContentKind,
159 SoundfieldGroupMissingMCAAudioElementKind,
161 TimedTextUCSEncodingNotUTF8,
163 TimedTextNamespaceNotIMSC,
165 TimedTextResourceMIMETypeUnsupported,
168 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 Self::RFC5646SpokenLanguageMissing
250 | Self::SoundfieldGroupMissingMCATitle
251 | Self::SoundfieldGroupMissingMCATitleVersion
252 | Self::SoundfieldGroupMissingMCAAudioContentKind
253 | Self::SoundfieldGroupMissingMCAAudioElementKind => Severity::Warning,
254 _ => Severity::Error,
256 }
257 }
258 fn category(&self) -> Category {
259 match self {
260 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 Self::TimedTextUCSEncodingNotUTF8
275 | Self::TimedTextNamespaceNotIMSC
276 | Self::TimedTextResourceMIMETypeUnsupported => Category::Subtitle,
277 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#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
362pub enum St377_4_2012 {
363 MCALinkIDMissing,
365 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#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
418pub enum ImfernoMxf {
419 OpenFailed,
421 PartitionPackParseFailed,
423 RegXmlConversionFailed,
426 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}