1pub use crate::assetmap::volindex_codes::St429_9_2014;
20
21use crate::diagnostics::codes::ValidationCode;
22use crate::diagnostics::{Category, Severity};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
31pub enum St2067_2_2020 {
32 AssetMap,
34 AssetMapMalformedXml,
36 PklMalformedXml,
38 NoCpls,
40 SizeMismatch,
42 FileNotFound,
44 ChecksumMismatch,
46 UnresolvedUuid,
48 DuplicateUuid,
50 IoError,
52 EssenceDescriptorList,
54 PklUnknownNamespace,
57 AssetMapHasNoPackingList,
61 PklIdNotInAssetMap,
64}
65
66impl ValidationCode for St2067_2_2020 {
67 fn code(&self) -> &'static str {
68 match self {
69 Self::AssetMap => "ST2067-2:2020:7/AssetMap",
70 Self::AssetMapMalformedXml => "ST2067-2:2020:7/MalformedXml",
71 Self::PklMalformedXml => "ST2067-2:2020:9/MalformedXml",
72 Self::NoCpls => "ST2067-2:2020:7/NoCpls",
73 Self::SizeMismatch => "ST2067-2:2020:8.3/SizeMismatch",
74 Self::FileNotFound => "ST2067-2:2020:8.3/FileNotFound",
75 Self::ChecksumMismatch => "ST2067-2:2020:8.3/ChecksumMismatch",
76 Self::UnresolvedUuid => "ST2067-2:2020:7/UnresolvedUuid",
77 Self::DuplicateUuid => "ST2067-2:2020:7/DuplicateUuid",
78 Self::IoError => "IMF:General/IoError",
79 Self::EssenceDescriptorList => "ST2067-2:2020:6.4.2/EssenceDescriptorList",
80 Self::PklUnknownNamespace => "ST2067-2:2020:9/PklUnknownNamespace",
81 Self::AssetMapHasNoPackingList => "ST429-9:2014:6.3/AssetMapHasNoPackingList",
82 Self::PklIdNotInAssetMap => "ST429-9:2014:6.3/PklIdNotInAssetMap",
83 }
84 }
85 fn description(&self) -> &'static str {
86 match self {
87 Self::AssetMap => "AssetMap document is invalid or cannot be parsed.",
88 Self::AssetMapMalformedXml => "The ASSETMAP.xml document is not well-formed XML.",
89 Self::PklMalformedXml => "A Packing List document is not well-formed XML.",
90 Self::NoCpls => "No CPL assets found in the AssetMap.",
91 Self::SizeMismatch => "Declared file size does not match the on-disk size.",
92 Self::FileNotFound => "A referenced asset file is not present at the declared path.",
93 Self::ChecksumMismatch => {
94 "File hash does not match the declared SHA-1/SHA-256 checksum."
95 }
96 Self::UnresolvedUuid => "UUID referenced in the CPL does not resolve to a known asset.",
97 Self::DuplicateUuid => "Two or more assets within the package share the same UUID.",
98 Self::IoError => "An I/O error prevented the asset from being read.",
99 Self::EssenceDescriptorList => {
100 "EssenceDescriptorList element is required per ST 2067-2:2020 §6.4.2."
101 }
102 Self::PklUnknownNamespace => {
103 "PKL document namespace is not one of the published SMPTE PKL namespaces."
104 }
105 Self::AssetMapHasNoPackingList => {
106 "AssetMap declares no asset as a PackingList (ST 429-9 §6.3)."
107 }
108 Self::PklIdNotInAssetMap => {
109 "PKL document Id is not declared as a PackingList asset in the AssetMap."
110 }
111 }
112 }
113 fn default_severity(&self) -> Severity {
114 match self {
115 Self::AssetMap | Self::NoCpls | Self::AssetMapHasNoPackingList => Severity::Critical,
116 _ => Severity::Error,
117 }
118 }
119 fn category(&self) -> Category {
120 match self {
121 Self::AssetMap
122 | Self::AssetMapMalformedXml
123 | Self::PklMalformedXml
124 | Self::NoCpls
125 | Self::EssenceDescriptorList
126 | Self::PklUnknownNamespace
127 | Self::AssetMapHasNoPackingList => Category::Structure,
128 Self::SizeMismatch | Self::FileNotFound | Self::IoError | Self::ChecksumMismatch => {
129 Category::Asset
130 }
131 Self::UnresolvedUuid | Self::DuplicateUuid | Self::PklIdNotInAssetMap => {
132 Category::Reference
133 }
134 }
135 }
136 fn example(&self) -> Option<&'static str> {
137 Some(match self {
138 Self::AssetMap => "ASSETMAP.xml exists but parse_assetmap returns Err (e.g. wrong root element)",
139 Self::AssetMapMalformedXml => "ASSETMAP.xml truncated mid-element: `<AssetMap><Id>urn:uuid:…</Id>`",
140 Self::PklMalformedXml => "PKL.xml with mismatched tag: `<PackingList>…</packinglist>`",
141 Self::NoCpls => "ASSETMAP.xml with all assets flagged `<PackingList>true</PackingList>` and zero CPLs",
142 Self::SizeMismatch => "PKL `<Size>1024</Size>` but the actual file is 2048 bytes on disk",
143 Self::FileNotFound => "AssetMap `<Path>missing-track.mxf</Path>` but no such file exists in the package",
144 Self::ChecksumMismatch => "PKL `<Hash>2jmj7l5rSw0yVb/vlWAYkK/YBwk=</Hash>` (SHA-1 of empty string) on a non-empty file",
145 Self::UnresolvedUuid => "CPL TrackFileId references urn:uuid:aaa… but no PKL/AssetMap asset has that Id",
146 Self::DuplicateUuid => "Two AssetMap `<Asset><Id>urn:uuid:00…01</Id></Asset>` entries with the same Id",
147 Self::IoError => "Underlying filesystem refused the read (permissions, device error, etc.)",
148 Self::EssenceDescriptorList => "CPL with `<SourceEncoding>` references but no top-level `<EssenceDescriptorList>` element",
149 Self::PklUnknownNamespace => "PKL with `xmlns=\"http://example.org/not-a-pkl-namespace\"`",
150 Self::AssetMapHasNoPackingList => "AssetMap whose `<Asset>` entries all omit `<PackingList>true</PackingList>`",
151 Self::PklIdNotInAssetMap => "PKL `<Id>urn:uuid:abc…</Id>` not present in the AssetMap as a packing-list asset",
152 })
153 }
154}
155
156impl St2067_2_2020 {
157 pub const ALL: &'static [Self] = &[
158 Self::AssetMap,
159 Self::AssetMapMalformedXml,
160 Self::PklMalformedXml,
161 Self::NoCpls,
162 Self::SizeMismatch,
163 Self::FileNotFound,
164 Self::ChecksumMismatch,
165 Self::UnresolvedUuid,
166 Self::DuplicateUuid,
167 Self::IoError,
168 Self::EssenceDescriptorList,
169 Self::PklUnknownNamespace,
170 Self::AssetMapHasNoPackingList,
171 Self::PklIdNotInAssetMap,
172 ];
173}
174
175impl From<St2067_2_2020> for String {
176 fn from(c: St2067_2_2020) -> String {
177 c.code().to_string()
178 }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum CoreConstraintsCode {
191 ResourceListEmpty,
192 ContentTitle,
193 TotalRunningTimeFormat,
194 SegmentList,
195 Segment,
196 EditRate,
197 IssueDate,
198 IssueDateFormat,
199 CompositionTimecodeDropFrame,
200 CompositionTimecodeRate,
201 CompositionTimecodeStartAddress,
202 CompositionTimecodeRateZero,
203 CompositionTimecodeStartAddressFormat,
204 CompositionTimecodeRateMismatch,
205 LocaleListNonEmpty,
206 UniqueSegmentId,
207 UniqueEssenceDescriptorId,
208 UniqueResourceId,
209 IntrinsicDuration,
210 EntryPoint,
211 SourceDuration,
212 ResourceDuration,
213 RepeatCount,
214 TrackFileId,
215 VirtualTrackContinuity,
216 VirtualTrackEditRate,
217 TimedTextSampleRate,
218 TimedTextEmptyLanguageTag,
219 TimedTextMalformedLanguageTag,
220 AudioSampleRate,
221 ChannelCount,
222 MCASubDescriptors,
223 SoundfieldGroup,
224 MCATagSymbol,
225 SoundfieldChannelCount,
226 DigitalSignature,
227 DanglingEssenceDescriptor,
228 EssenceDescriptorList,
229}
230
231macro_rules! define_core_constraints_enum {
232 ($name:ident, $prefix:literal) => {
234 define_core_constraints_enum!(@inner $name, $prefix, None);
235 };
236 ($name:ident, $prefix:literal, $previous:literal) => {
239 define_core_constraints_enum!(@inner $name, $prefix, Some($previous));
240 };
241 (@inner $name:ident, $prefix:literal, $previous:expr) => {
242 #[doc = $prefix]
244 #[allow(non_camel_case_types)]
245 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
246 pub enum $name {
247 ResourceListEmpty,
249 ContentTitle,
251 TotalRunningTimeFormat,
253 SegmentList,
255 Segment,
257 EditRate,
259 IssueDate,
261 IssueDateFormat,
263 CompositionTimecodeDropFrame,
265 CompositionTimecodeRate,
267 CompositionTimecodeStartAddress,
269 CompositionTimecodeRateZero,
271 CompositionTimecodeStartAddressFormat,
273 CompositionTimecodeRateMismatch,
275 LocaleListNonEmpty,
277 UniqueSegmentId,
279 UniqueEssenceDescriptorId,
281 UniqueResourceId,
283 IntrinsicDuration,
285 EntryPoint,
287 SourceDuration,
289 ResourceDuration,
291 RepeatCount,
293 TrackFileId,
295 VirtualTrackContinuity,
297 VirtualTrackEditRate,
299 TimedTextSampleRate,
301 TimedTextEmptyLanguageTag,
303 TimedTextMalformedLanguageTag,
305 AudioSampleRate,
307 ChannelCount,
309 MCASubDescriptors,
311 SoundfieldGroup,
313 MCATagSymbol,
315 SoundfieldChannelCount,
317 DigitalSignature,
319 DanglingEssenceDescriptor,
321 EssenceDescriptorList,
323 }
324
325 impl ValidationCode for $name {
326 fn code(&self) -> &'static str {
327 match self {
328 Self::ResourceListEmpty => concat!($prefix, ":XSD/ResourceList-Empty"),
329 Self::ContentTitle => concat!($prefix, ":XSD/ContentTitle"),
330 Self::TotalRunningTimeFormat => concat!($prefix, ":XSD/TotalRunningTime-Format"),
331 Self::SegmentList => concat!($prefix, ":XSD/SegmentList"),
332 Self::Segment => concat!($prefix, ":XSD/Segment"),
333 Self::EditRate => concat!($prefix, ":XSD/CompositionPlaylist/EditRate"),
334 Self::IssueDate => concat!($prefix, ":XSD/CompositionPlaylist/IssueDate"),
335 Self::IssueDateFormat => concat!($prefix, ":XSD/CompositionPlaylist/IssueDate-Format"),
336 Self::CompositionTimecodeDropFrame => concat!($prefix, ":XSD/CompositionTimecode/DropFrame"),
337 Self::CompositionTimecodeRate => concat!($prefix, ":XSD/CompositionTimecode/Rate"),
338 Self::CompositionTimecodeStartAddress => concat!($prefix, ":XSD/CompositionTimecode/StartAddress"),
339 Self::CompositionTimecodeRateZero => concat!($prefix, ":XSD/CompositionTimecode/Rate-Zero"),
340 Self::CompositionTimecodeStartAddressFormat => concat!($prefix, ":XSD/CompositionTimecode/StartAddress-Format"),
341 Self::CompositionTimecodeRateMismatch => concat!($prefix, ":XSD/CompositionTimecode/Rate-Mismatch"),
342 Self::LocaleListNonEmpty => concat!($prefix, ":XSD/LocaleList-NonEmpty"),
343 Self::UniqueSegmentId => concat!($prefix, ":6.1/UniqueSegmentId"),
344 Self::UniqueEssenceDescriptorId => concat!($prefix, ":6.1/UniqueEssenceDescriptorId"),
345 Self::UniqueResourceId => concat!($prefix, ":6.1/UniqueResourceId"),
346 Self::IntrinsicDuration => concat!($prefix, ":6.10/IntrinsicDuration"),
347 Self::EntryPoint => concat!($prefix, ":6.10/EntryPoint"),
348 Self::SourceDuration => concat!($prefix, ":6.10/SourceDuration"),
349 Self::ResourceDuration => concat!($prefix, ":6.10/ResourceDuration"),
350 Self::RepeatCount => concat!($prefix, ":6.10/RepeatCount"),
351 Self::TrackFileId => concat!($prefix, ":6.10/TrackFileId"),
352 Self::VirtualTrackContinuity => concat!($prefix, ":6.9/VirtualTrackContinuity"),
353 Self::VirtualTrackEditRate => concat!($prefix, ":6.9.3/VirtualTrackEditRate"),
354 Self::TimedTextSampleRate => concat!($prefix, ":10/TimedText-SampleRate"),
355 Self::TimedTextEmptyLanguageTag => concat!($prefix, ":10/TimedText-EmptyLanguageTag"),
356 Self::TimedTextMalformedLanguageTag => concat!($prefix, ":10/TimedText-MalformedLanguageTag"),
357 Self::AudioSampleRate => concat!($prefix, ":ST377-4/AudioSampleRate"),
358 Self::ChannelCount => concat!($prefix, ":ST377-4/ChannelCount"),
359 Self::MCASubDescriptors => concat!($prefix, ":ST377-4/MCASubDescriptors"),
360 Self::SoundfieldGroup => concat!($prefix, ":ST377-4/SoundfieldGroup"),
361 Self::MCATagSymbol => concat!($prefix, ":ST377-4/MCATagSymbol"),
362 Self::SoundfieldChannelCount => concat!($prefix, ":ST377-4/SoundfieldChannelCount"),
363 Self::DigitalSignature => concat!($prefix, ":8/DigitalSignature"),
364 Self::DanglingEssenceDescriptor => concat!($prefix, ":6.4.2/DanglingEssenceDescriptor"),
365 Self::EssenceDescriptorList => concat!($prefix, ":6.4.2/EssenceDescriptorList"),
366 }
367 }
368 fn description(&self) -> &'static str {
369 match self {
370 Self::ResourceListEmpty => "A Sequence has an empty ResourceList.",
371 Self::ContentTitle => "ContentTitle shall not be empty.",
372 Self::TotalRunningTimeFormat => "TotalRunningTime does not match required format HH:MM:SS.",
373 Self::SegmentList => "SegmentList shall contain at least one Segment.",
374 Self::Segment => "A Segment contains no sequences.",
375 Self::EditRate => "CPL EditRate is required (XSD schema §88).",
376 Self::IssueDate => "IssueDate shall not be empty.",
377 Self::IssueDateFormat => "IssueDate is not a valid xs:dateTime format.",
378 Self::CompositionTimecodeDropFrame => "CompositionTimecode.TimecodeDropFrame is required when CompositionTimecode is present.",
379 Self::CompositionTimecodeRate => "CompositionTimecode.TimecodeRate is required when CompositionTimecode is present.",
380 Self::CompositionTimecodeStartAddress => "CompositionTimecode.TimecodeStartAddress is required when CompositionTimecode is present.",
381 Self::CompositionTimecodeRateZero => "CompositionTimecode.TimecodeRate shall be a positive integer.",
382 Self::CompositionTimecodeStartAddressFormat => "TimecodeStartAddress does not match SMPTE timecode format HH:MM:SS:FF.",
383 Self::CompositionTimecodeRateMismatch => "CompositionTimecode.TimecodeRate does not match the CPL EditRate.",
384 Self::LocaleListNonEmpty => "LocaleList shall contain at least one Locale.",
385 Self::UniqueSegmentId => "Duplicate Segment Id within the CPL.",
386 Self::UniqueEssenceDescriptorId => "Duplicate EssenceDescriptor Id within the CPL.",
387 Self::UniqueResourceId => "Duplicate Resource Id within the CPL.",
388 Self::IntrinsicDuration => "IntrinsicDuration shall be greater than 0.",
389 Self::EntryPoint => "EntryPoint shall be less than IntrinsicDuration.",
390 Self::SourceDuration => "EntryPoint + SourceDuration exceeds IntrinsicDuration.",
391 Self::ResourceDuration => "SourceDuration shall be a positive integer.",
392 Self::RepeatCount => "RepeatCount shall be a positive integer.",
393 Self::TrackFileId => "A non-marker resource is missing a TrackFileId.",
394 Self::VirtualTrackContinuity => "A virtual track is missing from one or more segments.",
395 Self::VirtualTrackEditRate => "All resources in a virtual track shall have the same edit rate.",
396 Self::TimedTextSampleRate => "DCTimedTextDescriptor SampleRate is missing.",
397 Self::TimedTextEmptyLanguageTag => "Empty language tag in RFC5646LanguageTagList.",
398 Self::TimedTextMalformedLanguageTag => "Language tag does not start with an ASCII letter (RFC 5646 primary subtag).",
399 Self::AudioSampleRate => "WAVEPCMDescriptor has no AudioSampleRate or SampleRate.",
400 Self::ChannelCount => "WAVEPCMDescriptor ChannelCount is zero or missing.",
401 Self::MCASubDescriptors => "WAVEPCMDescriptor has no MCA SubDescriptors.",
402 Self::SoundfieldGroup => "WAVEPCMDescriptor SubDescriptors missing SoundfieldGroupLabelSubDescriptor.",
403 Self::MCATagSymbol => "SoundfieldGroupLabelSubDescriptor is missing MCATagSymbol.",
404 Self::SoundfieldChannelCount => "Soundfield group channel count is inconsistent with WAVEPCMDescriptor.ChannelCount.",
405 Self::DigitalSignature => "Digital signature validation (ST 2067-2 §8) is not currently performed.",
406 Self::DanglingEssenceDescriptor => "EssenceDescriptor present in EssenceDescriptorList but not referenced by any Resource.",
407 Self::EssenceDescriptorList => "EssenceDescriptorList is required per ST 2067-2 §6.4.2.",
408 }
409 }
410 fn default_severity(&self) -> Severity {
411 match self {
412 Self::SegmentList => Severity::Critical,
413 Self::IssueDateFormat
414 | Self::CompositionTimecodeRateMismatch
415 | Self::TimedTextSampleRate
416 | Self::TimedTextEmptyLanguageTag
417 | Self::TimedTextMalformedLanguageTag
418 | Self::AudioSampleRate
419 | Self::ChannelCount
420 | Self::MCASubDescriptors
421 | Self::SoundfieldGroup
422 | Self::MCATagSymbol => Severity::Warning,
423 Self::DigitalSignature => Severity::Info,
424 _ => Severity::Error,
425 }
426 }
427 fn category(&self) -> Category {
428 match self {
429 Self::ContentTitle
430 | Self::IssueDate
431 | Self::IssueDateFormat
432 | Self::CompositionTimecodeRateMismatch => Category::Metadata,
433
434 Self::CompositionTimecodeDropFrame
435 | Self::CompositionTimecodeRate
436 | Self::CompositionTimecodeStartAddress
437 | Self::CompositionTimecodeRateZero
438 | Self::CompositionTimecodeStartAddressFormat
439 | Self::IntrinsicDuration
440 | Self::EntryPoint
441 | Self::SourceDuration
442 | Self::ResourceDuration
443 | Self::RepeatCount
444 | Self::VirtualTrackEditRate => Category::Timing,
445
446 Self::TimedTextSampleRate
447 | Self::TimedTextEmptyLanguageTag
448 | Self::TimedTextMalformedLanguageTag => Category::Subtitle,
449
450 Self::AudioSampleRate
451 | Self::ChannelCount
452 | Self::MCASubDescriptors
453 | Self::SoundfieldGroup
454 | Self::MCATagSymbol
455 | Self::SoundfieldChannelCount => Category::Audio,
456
457 Self::DanglingEssenceDescriptor | Self::TrackFileId => Category::Reference,
458
459 Self::DigitalSignature => Category::Security,
460
461 _ => Category::Structure,
462 }
463 }
464 fn previous_identical_edition(&self) -> Option<&'static str> {
465 $previous
466 }
467 fn example(&self) -> Option<&'static str> {
468 Some(match self {
469 Self::ResourceListEmpty => "<Sequence><ResourceList></ResourceList></Sequence>",
470 Self::ContentTitle => "<ContentTitle></ContentTitle>",
471 Self::TotalRunningTimeFormat => "<TotalRunningTime>1h23m</TotalRunningTime>",
472 Self::SegmentList => "<SegmentList></SegmentList>",
473 Self::Segment => "<Segment><SequenceList></SequenceList></Segment>",
474 Self::EditRate => "<CompositionPlaylist>…</CompositionPlaylist> <!-- no <EditRate> element -->",
475 Self::IssueDate => "<IssueDate></IssueDate>",
476 Self::IssueDateFormat => "<IssueDate>2024/01/01</IssueDate> <!-- not xs:dateTime -->",
477 Self::CompositionTimecodeDropFrame => "<CompositionTimecode><TimecodeRate>24</TimecodeRate>…</CompositionTimecode> <!-- no <TimecodeDropFrame> -->",
478 Self::CompositionTimecodeRate => "<CompositionTimecode><TimecodeDropFrame>false</TimecodeDropFrame>…</CompositionTimecode> <!-- no <TimecodeRate> -->",
479 Self::CompositionTimecodeStartAddress => "<CompositionTimecode><TimecodeRate>24</TimecodeRate><TimecodeDropFrame>false</TimecodeDropFrame></CompositionTimecode> <!-- no <TimecodeStartAddress> -->",
480 Self::CompositionTimecodeRateZero => "<TimecodeRate>0</TimecodeRate>",
481 Self::CompositionTimecodeStartAddressFormat => "<TimecodeStartAddress>1:2:3</TimecodeStartAddress> <!-- expected HH:MM:SS:FF -->",
482 Self::CompositionTimecodeRateMismatch => "CPL <EditRate>24 1</EditRate> but <TimecodeRate>25</TimecodeRate>",
483 Self::LocaleListNonEmpty => "<LocaleList></LocaleList>",
484 Self::UniqueSegmentId => "Two <Segment><Id>urn:uuid:abc…</Id></Segment> with the same Id",
485 Self::UniqueEssenceDescriptorId => "Two <EssenceDescriptor><Id>urn:uuid:abc…</Id></EssenceDescriptor> with the same Id",
486 Self::UniqueResourceId => "Two <Resource><Id>urn:uuid:abc…</Id></Resource> with the same Id",
487 Self::IntrinsicDuration => "<IntrinsicDuration>0</IntrinsicDuration>",
488 Self::EntryPoint => "<IntrinsicDuration>100</IntrinsicDuration><EntryPoint>150</EntryPoint>",
489 Self::SourceDuration => "<IntrinsicDuration>100</IntrinsicDuration><EntryPoint>50</EntryPoint><SourceDuration>80</SourceDuration> <!-- 50+80 > 100 -->",
490 Self::ResourceDuration => "<SourceDuration>-5</SourceDuration>",
491 Self::RepeatCount => "<RepeatCount>0</RepeatCount>",
492 Self::TrackFileId => "<Resource xsi:type=\"TrackFileResourceType\">…</Resource> <!-- no <TrackFileId> child -->",
493 Self::VirtualTrackContinuity => "Two <Segment> entries, MainAudio track present in segment 0 but absent from segment 1",
494 Self::VirtualTrackEditRate => "Track resources mix <EditRate>24 1</EditRate> and <EditRate>25 1</EditRate>",
495 Self::TimedTextSampleRate => "<DCTimedTextDescriptor>…</DCTimedTextDescriptor> <!-- no SampleRate -->",
496 Self::TimedTextEmptyLanguageTag => "<RFC5646LanguageTagList><RFC5646LanguageTag></RFC5646LanguageTag></RFC5646LanguageTagList>",
497 Self::TimedTextMalformedLanguageTag => "<RFC5646LanguageTag>123-en</RFC5646LanguageTag> <!-- primary subtag must start with a letter -->",
498 Self::AudioSampleRate => "<WAVEPCMDescriptor>…</WAVEPCMDescriptor> <!-- no AudioSampleRate -->",
499 Self::ChannelCount => "<WAVEPCMDescriptor><ChannelCount>0</ChannelCount></WAVEPCMDescriptor>",
500 Self::MCASubDescriptors => "<WAVEPCMDescriptor>…</WAVEPCMDescriptor> <!-- no SubDescriptors strong-ref list -->",
501 Self::SoundfieldGroup => "<WAVEPCMDescriptor><SubDescriptors>…</SubDescriptors></WAVEPCMDescriptor> <!-- but no SoundfieldGroupLabelSubDescriptor inside -->",
502 Self::MCATagSymbol => "<SoundfieldGroupLabelSubDescriptor>…</SoundfieldGroupLabelSubDescriptor> <!-- no <MCATagSymbol> -->",
503 Self::SoundfieldChannelCount => "<WAVEPCMDescriptor><ChannelCount>2</ChannelCount>…</WAVEPCMDescriptor> but soundfield group declares 6 channels",
504 Self::DigitalSignature => "CPL declares Signer/Signature but the engine does not yet verify them (info notice)",
505 Self::DanglingEssenceDescriptor => "<EssenceDescriptor><Id>urn:uuid:abc…</Id></EssenceDescriptor> <!-- no Resource references this Id -->",
506 Self::EssenceDescriptorList => "CPL with `<SourceEncoding>` references but no top-level `<EssenceDescriptorList>` element",
507 })
508 }
509 }
510
511 impl $name {
512 pub const ALL: &'static [Self] = &[
513 Self::ResourceListEmpty,
514 Self::ContentTitle,
515 Self::TotalRunningTimeFormat,
516 Self::SegmentList,
517 Self::Segment,
518 Self::EditRate,
519 Self::IssueDate,
520 Self::IssueDateFormat,
521 Self::CompositionTimecodeDropFrame,
522 Self::CompositionTimecodeRate,
523 Self::CompositionTimecodeStartAddress,
524 Self::CompositionTimecodeRateZero,
525 Self::CompositionTimecodeStartAddressFormat,
526 Self::CompositionTimecodeRateMismatch,
527 Self::LocaleListNonEmpty,
528 Self::UniqueSegmentId,
529 Self::UniqueEssenceDescriptorId,
530 Self::UniqueResourceId,
531 Self::IntrinsicDuration,
532 Self::EntryPoint,
533 Self::SourceDuration,
534 Self::ResourceDuration,
535 Self::RepeatCount,
536 Self::TrackFileId,
537 Self::VirtualTrackContinuity,
538 Self::VirtualTrackEditRate,
539 Self::TimedTextSampleRate,
540 Self::TimedTextEmptyLanguageTag,
541 Self::TimedTextMalformedLanguageTag,
542 Self::AudioSampleRate,
543 Self::ChannelCount,
544 Self::MCASubDescriptors,
545 Self::SoundfieldGroup,
546 Self::MCATagSymbol,
547 Self::SoundfieldChannelCount,
548 Self::DigitalSignature,
549 Self::DanglingEssenceDescriptor,
550 Self::EssenceDescriptorList,
551 ];
552
553 pub fn for_code(r: CoreConstraintsCode) -> &'static str {
556 match r {
557 CoreConstraintsCode::ResourceListEmpty => concat!($prefix, ":XSD/ResourceList-Empty"),
558 CoreConstraintsCode::ContentTitle => concat!($prefix, ":XSD/ContentTitle"),
559 CoreConstraintsCode::TotalRunningTimeFormat => concat!($prefix, ":XSD/TotalRunningTime-Format"),
560 CoreConstraintsCode::SegmentList => concat!($prefix, ":XSD/SegmentList"),
561 CoreConstraintsCode::Segment => concat!($prefix, ":XSD/Segment"),
562 CoreConstraintsCode::EditRate => concat!($prefix, ":XSD/CompositionPlaylist/EditRate"),
563 CoreConstraintsCode::IssueDate => concat!($prefix, ":XSD/CompositionPlaylist/IssueDate"),
564 CoreConstraintsCode::IssueDateFormat => concat!($prefix, ":XSD/CompositionPlaylist/IssueDate-Format"),
565 CoreConstraintsCode::CompositionTimecodeDropFrame => concat!($prefix, ":XSD/CompositionTimecode/DropFrame"),
566 CoreConstraintsCode::CompositionTimecodeRate => concat!($prefix, ":XSD/CompositionTimecode/Rate"),
567 CoreConstraintsCode::CompositionTimecodeStartAddress => concat!($prefix, ":XSD/CompositionTimecode/StartAddress"),
568 CoreConstraintsCode::CompositionTimecodeRateZero => concat!($prefix, ":XSD/CompositionTimecode/Rate-Zero"),
569 CoreConstraintsCode::CompositionTimecodeStartAddressFormat => concat!($prefix, ":XSD/CompositionTimecode/StartAddress-Format"),
570 CoreConstraintsCode::CompositionTimecodeRateMismatch => concat!($prefix, ":XSD/CompositionTimecode/Rate-Mismatch"),
571 CoreConstraintsCode::LocaleListNonEmpty => concat!($prefix, ":XSD/LocaleList-NonEmpty"),
572 CoreConstraintsCode::UniqueSegmentId => concat!($prefix, ":6.1/UniqueSegmentId"),
573 CoreConstraintsCode::UniqueEssenceDescriptorId => concat!($prefix, ":6.1/UniqueEssenceDescriptorId"),
574 CoreConstraintsCode::UniqueResourceId => concat!($prefix, ":6.1/UniqueResourceId"),
575 CoreConstraintsCode::IntrinsicDuration => concat!($prefix, ":6.10/IntrinsicDuration"),
576 CoreConstraintsCode::EntryPoint => concat!($prefix, ":6.10/EntryPoint"),
577 CoreConstraintsCode::SourceDuration => concat!($prefix, ":6.10/SourceDuration"),
578 CoreConstraintsCode::ResourceDuration => concat!($prefix, ":6.10/ResourceDuration"),
579 CoreConstraintsCode::RepeatCount => concat!($prefix, ":6.10/RepeatCount"),
580 CoreConstraintsCode::TrackFileId => concat!($prefix, ":6.10/TrackFileId"),
581 CoreConstraintsCode::VirtualTrackContinuity => concat!($prefix, ":6.9/VirtualTrackContinuity"),
582 CoreConstraintsCode::VirtualTrackEditRate => concat!($prefix, ":6.9.3/VirtualTrackEditRate"),
583 CoreConstraintsCode::TimedTextSampleRate => concat!($prefix, ":10/TimedText-SampleRate"),
584 CoreConstraintsCode::TimedTextEmptyLanguageTag => concat!($prefix, ":10/TimedText-EmptyLanguageTag"),
585 CoreConstraintsCode::TimedTextMalformedLanguageTag => concat!($prefix, ":10/TimedText-MalformedLanguageTag"),
586 CoreConstraintsCode::AudioSampleRate => concat!($prefix, ":ST377-4/AudioSampleRate"),
587 CoreConstraintsCode::ChannelCount => concat!($prefix, ":ST377-4/ChannelCount"),
588 CoreConstraintsCode::MCASubDescriptors => concat!($prefix, ":ST377-4/MCASubDescriptors"),
589 CoreConstraintsCode::SoundfieldGroup => concat!($prefix, ":ST377-4/SoundfieldGroup"),
590 CoreConstraintsCode::MCATagSymbol => concat!($prefix, ":ST377-4/MCATagSymbol"),
591 CoreConstraintsCode::SoundfieldChannelCount => concat!($prefix, ":ST377-4/SoundfieldChannelCount"),
592 CoreConstraintsCode::DigitalSignature => concat!($prefix, ":8/DigitalSignature"),
593 CoreConstraintsCode::DanglingEssenceDescriptor => concat!($prefix, ":6.4.2/DanglingEssenceDescriptor"),
594 CoreConstraintsCode::EssenceDescriptorList => concat!($prefix, ":6.4.2/EssenceDescriptorList"),
595 }
596 }
597 }
598
599 impl From<$name> for String {
600 fn from(c: $name) -> String {
601 c.code().to_string()
602 }
603 }
604 };
605}
606
607define_core_constraints_enum!(St2067_2_2013_Core, "ST2067-2:2013");
608define_core_constraints_enum!(St2067_2_2016_Core, "ST2067-2:2016", "ST2067-2:2013");
610define_core_constraints_enum!(St2067_2_2020_Core, "ST2067-2:2020");