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}
55
56impl ValidationCode for St2067_2_2020 {
57 fn code(&self) -> &'static str {
58 match self {
59 Self::AssetMap => "ST2067-2:2020:7/AssetMap",
60 Self::AssetMapMalformedXml => "ST2067-2:2020:7/MalformedXml",
61 Self::PklMalformedXml => "ST2067-2:2020:9/MalformedXml",
62 Self::NoCpls => "ST2067-2:2020:7/NoCpls",
63 Self::SizeMismatch => "ST2067-2:2020:8.3/SizeMismatch",
64 Self::FileNotFound => "ST2067-2:2020:8.3/FileNotFound",
65 Self::ChecksumMismatch => "ST2067-2:2020:8.3/ChecksumMismatch",
66 Self::UnresolvedUuid => "ST2067-2:2020:7/UnresolvedUuid",
67 Self::DuplicateUuid => "ST2067-2:2020:7/DuplicateUuid",
68 Self::IoError => "IMF:General/IoError",
69 Self::EssenceDescriptorList => "ST2067-2:2020:6.4.2/EssenceDescriptorList",
70 }
71 }
72 fn description(&self) -> &'static str {
73 match self {
74 Self::AssetMap => "AssetMap document is invalid or cannot be parsed.",
75 Self::AssetMapMalformedXml => "The ASSETMAP.xml document is not well-formed XML.",
76 Self::PklMalformedXml => "A Packing List document is not well-formed XML.",
77 Self::NoCpls => "No CPL assets found in the AssetMap.",
78 Self::SizeMismatch => "Declared file size does not match the on-disk size.",
79 Self::FileNotFound => "A referenced asset file is not present at the declared path.",
80 Self::ChecksumMismatch => {
81 "File hash does not match the declared SHA-1/SHA-256 checksum."
82 }
83 Self::UnresolvedUuid => "UUID referenced in the CPL does not resolve to a known asset.",
84 Self::DuplicateUuid => "Two or more assets within the package share the same UUID.",
85 Self::IoError => "An I/O error prevented the asset from being read.",
86 Self::EssenceDescriptorList => {
87 "EssenceDescriptorList element is required per ST 2067-2:2020 §6.4.2."
88 }
89 }
90 }
91 fn default_severity(&self) -> Severity {
92 match self {
93 Self::AssetMap | Self::NoCpls => Severity::Critical,
94 _ => Severity::Error,
95 }
96 }
97 fn category(&self) -> Category {
98 match self {
99 Self::AssetMap
100 | Self::AssetMapMalformedXml
101 | Self::PklMalformedXml
102 | Self::NoCpls
103 | Self::EssenceDescriptorList => Category::Structure,
104 Self::SizeMismatch | Self::FileNotFound | Self::IoError | Self::ChecksumMismatch => {
105 Category::Asset
106 }
107 Self::UnresolvedUuid | Self::DuplicateUuid => Category::Reference,
108 }
109 }
110}
111
112impl St2067_2_2020 {
113 pub const ALL: &'static [Self] = &[
114 Self::AssetMap,
115 Self::AssetMapMalformedXml,
116 Self::PklMalformedXml,
117 Self::NoCpls,
118 Self::SizeMismatch,
119 Self::FileNotFound,
120 Self::ChecksumMismatch,
121 Self::UnresolvedUuid,
122 Self::DuplicateUuid,
123 Self::IoError,
124 Self::EssenceDescriptorList,
125 ];
126}
127
128impl From<St2067_2_2020> for String {
129 fn from(c: St2067_2_2020) -> String {
130 c.code().to_string()
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143pub enum CoreConstraintsCode {
144 ResourceListEmpty,
145 ContentTitle,
146 TotalRunningTimeFormat,
147 SegmentList,
148 Segment,
149 EditRate,
150 IssueDate,
151 IssueDateFormat,
152 CompositionTimecodeDropFrame,
153 CompositionTimecodeRate,
154 CompositionTimecodeStartAddress,
155 CompositionTimecodeRateZero,
156 CompositionTimecodeStartAddressFormat,
157 CompositionTimecodeRateMismatch,
158 LocaleListNonEmpty,
159 UniqueSegmentId,
160 UniqueEssenceDescriptorId,
161 UniqueResourceId,
162 IntrinsicDuration,
163 EntryPoint,
164 SourceDuration,
165 ResourceDuration,
166 RepeatCount,
167 TrackFileId,
168 VirtualTrackContinuity,
169 VirtualTrackEditRate,
170 TimedTextSampleRate,
171 TimedTextEmptyLanguageTag,
172 TimedTextMalformedLanguageTag,
173 AudioSampleRate,
174 ChannelCount,
175 MCASubDescriptors,
176 SoundfieldGroup,
177 MCATagSymbol,
178 SoundfieldChannelCount,
179 DigitalSignature,
180 DanglingEssenceDescriptor,
181 EssenceDescriptorList,
182}
183
184macro_rules! define_core_constraints_enum {
185 ($name:ident, $prefix:literal) => {
186 #[doc = $prefix]
188 #[allow(non_camel_case_types)]
189 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
190 pub enum $name {
191 ResourceListEmpty,
193 ContentTitle,
195 TotalRunningTimeFormat,
197 SegmentList,
199 Segment,
201 EditRate,
203 IssueDate,
205 IssueDateFormat,
207 CompositionTimecodeDropFrame,
209 CompositionTimecodeRate,
211 CompositionTimecodeStartAddress,
213 CompositionTimecodeRateZero,
215 CompositionTimecodeStartAddressFormat,
217 CompositionTimecodeRateMismatch,
219 LocaleListNonEmpty,
221 UniqueSegmentId,
223 UniqueEssenceDescriptorId,
225 UniqueResourceId,
227 IntrinsicDuration,
229 EntryPoint,
231 SourceDuration,
233 ResourceDuration,
235 RepeatCount,
237 TrackFileId,
239 VirtualTrackContinuity,
241 VirtualTrackEditRate,
243 TimedTextSampleRate,
245 TimedTextEmptyLanguageTag,
247 TimedTextMalformedLanguageTag,
249 AudioSampleRate,
251 ChannelCount,
253 MCASubDescriptors,
255 SoundfieldGroup,
257 MCATagSymbol,
259 SoundfieldChannelCount,
261 DigitalSignature,
263 DanglingEssenceDescriptor,
265 EssenceDescriptorList,
267 }
268
269 impl ValidationCode for $name {
270 fn code(&self) -> &'static str {
271 match self {
272 Self::ResourceListEmpty => concat!($prefix, ":XSD/ResourceList-Empty"),
273 Self::ContentTitle => concat!($prefix, ":XSD/ContentTitle"),
274 Self::TotalRunningTimeFormat => concat!($prefix, ":XSD/TotalRunningTime-Format"),
275 Self::SegmentList => concat!($prefix, ":XSD/SegmentList"),
276 Self::Segment => concat!($prefix, ":XSD/Segment"),
277 Self::EditRate => concat!($prefix, ":XSD-88/EditRate"),
278 Self::IssueDate => concat!($prefix, ":XSD-66/IssueDate"),
279 Self::IssueDateFormat => concat!($prefix, ":XSD-66/IssueDate-Format"),
280 Self::CompositionTimecodeDropFrame => concat!($prefix, ":XSD-121-127/CompositionTimecode-DropFrame"),
281 Self::CompositionTimecodeRate => concat!($prefix, ":XSD-121-127/CompositionTimecode-Rate"),
282 Self::CompositionTimecodeStartAddress => concat!($prefix, ":XSD-121-127/CompositionTimecode-StartAddress"),
283 Self::CompositionTimecodeRateZero => concat!($prefix, ":XSD-121-127/CompositionTimecode-Rate-Zero"),
284 Self::CompositionTimecodeStartAddressFormat => concat!($prefix, ":XSD-121-127/CompositionTimecode-StartAddress-Format"),
285 Self::CompositionTimecodeRateMismatch => concat!($prefix, ":XSD-121-127/CompositionTimecode-RateMismatch"),
286 Self::LocaleListNonEmpty => concat!($prefix, ":XSD/LocaleList-NonEmpty"),
287 Self::UniqueSegmentId => concat!($prefix, ":6.1/UniqueSegmentId"),
288 Self::UniqueEssenceDescriptorId => concat!($prefix, ":6.1/UniqueEssenceDescriptorId"),
289 Self::UniqueResourceId => concat!($prefix, ":6.1/UniqueResourceId"),
290 Self::IntrinsicDuration => concat!($prefix, ":6.10/IntrinsicDuration"),
291 Self::EntryPoint => concat!($prefix, ":6.10/EntryPoint"),
292 Self::SourceDuration => concat!($prefix, ":6.10/SourceDuration"),
293 Self::ResourceDuration => concat!($prefix, ":6.10/ResourceDuration"),
294 Self::RepeatCount => concat!($prefix, ":6.10/RepeatCount"),
295 Self::TrackFileId => concat!($prefix, ":6.10/TrackFileId"),
296 Self::VirtualTrackContinuity => concat!($prefix, ":6.9/VirtualTrackContinuity"),
297 Self::VirtualTrackEditRate => concat!($prefix, ":6.9.3/VirtualTrackEditRate"),
298 Self::TimedTextSampleRate => concat!($prefix, ":10/TimedText-SampleRate"),
299 Self::TimedTextEmptyLanguageTag => concat!($prefix, ":10/TimedText-EmptyLanguageTag"),
300 Self::TimedTextMalformedLanguageTag => concat!($prefix, ":10/TimedText-MalformedLanguageTag"),
301 Self::AudioSampleRate => concat!($prefix, ":ST377-4/AudioSampleRate"),
302 Self::ChannelCount => concat!($prefix, ":ST377-4/ChannelCount"),
303 Self::MCASubDescriptors => concat!($prefix, ":ST377-4/MCASubDescriptors"),
304 Self::SoundfieldGroup => concat!($prefix, ":ST377-4/SoundfieldGroup"),
305 Self::MCATagSymbol => concat!($prefix, ":ST377-4/MCATagSymbol"),
306 Self::SoundfieldChannelCount => concat!($prefix, ":ST377-4/SoundfieldChannelCount"),
307 Self::DigitalSignature => concat!($prefix, ":8/DigitalSignature"),
308 Self::DanglingEssenceDescriptor => concat!($prefix, ":6.4.2/DanglingEssenceDescriptor"),
309 Self::EssenceDescriptorList => concat!($prefix, ":6.4.2/EssenceDescriptorList"),
310 }
311 }
312 fn description(&self) -> &'static str {
313 match self {
314 Self::ResourceListEmpty => "A Sequence has an empty ResourceList.",
315 Self::ContentTitle => "ContentTitle shall not be empty.",
316 Self::TotalRunningTimeFormat => "TotalRunningTime does not match required format HH:MM:SS.",
317 Self::SegmentList => "SegmentList shall contain at least one Segment.",
318 Self::Segment => "A Segment contains no sequences.",
319 Self::EditRate => "CPL EditRate is required (XSD schema §88).",
320 Self::IssueDate => "IssueDate shall not be empty.",
321 Self::IssueDateFormat => "IssueDate is not a valid xs:dateTime format.",
322 Self::CompositionTimecodeDropFrame => "CompositionTimecode.TimecodeDropFrame is required when CompositionTimecode is present.",
323 Self::CompositionTimecodeRate => "CompositionTimecode.TimecodeRate is required when CompositionTimecode is present.",
324 Self::CompositionTimecodeStartAddress => "CompositionTimecode.TimecodeStartAddress is required when CompositionTimecode is present.",
325 Self::CompositionTimecodeRateZero => "CompositionTimecode.TimecodeRate shall be a positive integer.",
326 Self::CompositionTimecodeStartAddressFormat => "TimecodeStartAddress does not match SMPTE timecode format HH:MM:SS:FF.",
327 Self::CompositionTimecodeRateMismatch => "CompositionTimecode.TimecodeRate does not match the CPL EditRate.",
328 Self::LocaleListNonEmpty => "LocaleList shall contain at least one Locale.",
329 Self::UniqueSegmentId => "Duplicate Segment Id within the CPL.",
330 Self::UniqueEssenceDescriptorId => "Duplicate EssenceDescriptor Id within the CPL.",
331 Self::UniqueResourceId => "Duplicate Resource Id within the CPL.",
332 Self::IntrinsicDuration => "IntrinsicDuration shall be greater than 0.",
333 Self::EntryPoint => "EntryPoint shall be less than IntrinsicDuration.",
334 Self::SourceDuration => "EntryPoint + SourceDuration exceeds IntrinsicDuration.",
335 Self::ResourceDuration => "SourceDuration shall be a positive integer.",
336 Self::RepeatCount => "RepeatCount shall be a positive integer.",
337 Self::TrackFileId => "A non-marker resource is missing a TrackFileId.",
338 Self::VirtualTrackContinuity => "A virtual track is missing from one or more segments.",
339 Self::VirtualTrackEditRate => "All resources in a virtual track shall have the same edit rate.",
340 Self::TimedTextSampleRate => "DCTimedTextDescriptor SampleRate is missing.",
341 Self::TimedTextEmptyLanguageTag => "Empty language tag in RFC5646LanguageTagList.",
342 Self::TimedTextMalformedLanguageTag => "Language tag does not start with an ASCII letter (RFC 5646 primary subtag).",
343 Self::AudioSampleRate => "WAVEPCMDescriptor has no AudioSampleRate or SampleRate.",
344 Self::ChannelCount => "WAVEPCMDescriptor ChannelCount is zero or missing.",
345 Self::MCASubDescriptors => "WAVEPCMDescriptor has no MCA SubDescriptors.",
346 Self::SoundfieldGroup => "WAVEPCMDescriptor SubDescriptors missing SoundfieldGroupLabelSubDescriptor.",
347 Self::MCATagSymbol => "SoundfieldGroupLabelSubDescriptor is missing MCATagSymbol.",
348 Self::SoundfieldChannelCount => "Soundfield group channel count is inconsistent with WAVEPCMDescriptor.ChannelCount.",
349 Self::DigitalSignature => "Digital signature validation (ST 2067-2 §8) is not currently performed.",
350 Self::DanglingEssenceDescriptor => "EssenceDescriptor present in EssenceDescriptorList but not referenced by any Resource.",
351 Self::EssenceDescriptorList => "EssenceDescriptorList is required per ST 2067-2 §6.4.2.",
352 }
353 }
354 fn default_severity(&self) -> Severity {
355 match self {
356 Self::SegmentList => Severity::Critical,
357 Self::IssueDateFormat
358 | Self::CompositionTimecodeRateMismatch
359 | Self::TimedTextSampleRate
360 | Self::TimedTextEmptyLanguageTag
361 | Self::TimedTextMalformedLanguageTag
362 | Self::AudioSampleRate
363 | Self::ChannelCount
364 | Self::MCASubDescriptors
365 | Self::SoundfieldGroup
366 | Self::MCATagSymbol => Severity::Warning,
367 Self::DigitalSignature => Severity::Info,
368 _ => Severity::Error,
369 }
370 }
371 fn category(&self) -> Category {
372 match self {
373 Self::ContentTitle
374 | Self::IssueDate
375 | Self::IssueDateFormat
376 | Self::CompositionTimecodeRateMismatch => Category::Metadata,
377
378 Self::CompositionTimecodeDropFrame
379 | Self::CompositionTimecodeRate
380 | Self::CompositionTimecodeStartAddress
381 | Self::CompositionTimecodeRateZero
382 | Self::CompositionTimecodeStartAddressFormat
383 | Self::IntrinsicDuration
384 | Self::EntryPoint
385 | Self::SourceDuration
386 | Self::ResourceDuration
387 | Self::RepeatCount
388 | Self::VirtualTrackEditRate => Category::Timing,
389
390 Self::TimedTextSampleRate
391 | Self::TimedTextEmptyLanguageTag
392 | Self::TimedTextMalformedLanguageTag => Category::Subtitle,
393
394 Self::AudioSampleRate
395 | Self::ChannelCount
396 | Self::MCASubDescriptors
397 | Self::SoundfieldGroup
398 | Self::MCATagSymbol
399 | Self::SoundfieldChannelCount => Category::Audio,
400
401 Self::DanglingEssenceDescriptor | Self::TrackFileId => Category::Reference,
402
403 Self::DigitalSignature => Category::Security,
404
405 _ => Category::Structure,
406 }
407 }
408 }
409
410 impl $name {
411 pub const ALL: &'static [Self] = &[
412 Self::ResourceListEmpty,
413 Self::ContentTitle,
414 Self::TotalRunningTimeFormat,
415 Self::SegmentList,
416 Self::Segment,
417 Self::EditRate,
418 Self::IssueDate,
419 Self::IssueDateFormat,
420 Self::CompositionTimecodeDropFrame,
421 Self::CompositionTimecodeRate,
422 Self::CompositionTimecodeStartAddress,
423 Self::CompositionTimecodeRateZero,
424 Self::CompositionTimecodeStartAddressFormat,
425 Self::CompositionTimecodeRateMismatch,
426 Self::LocaleListNonEmpty,
427 Self::UniqueSegmentId,
428 Self::UniqueEssenceDescriptorId,
429 Self::UniqueResourceId,
430 Self::IntrinsicDuration,
431 Self::EntryPoint,
432 Self::SourceDuration,
433 Self::ResourceDuration,
434 Self::RepeatCount,
435 Self::TrackFileId,
436 Self::VirtualTrackContinuity,
437 Self::VirtualTrackEditRate,
438 Self::TimedTextSampleRate,
439 Self::TimedTextEmptyLanguageTag,
440 Self::TimedTextMalformedLanguageTag,
441 Self::AudioSampleRate,
442 Self::ChannelCount,
443 Self::MCASubDescriptors,
444 Self::SoundfieldGroup,
445 Self::MCATagSymbol,
446 Self::SoundfieldChannelCount,
447 Self::DigitalSignature,
448 Self::DanglingEssenceDescriptor,
449 Self::EssenceDescriptorList,
450 ];
451
452 pub fn for_code(r: CoreConstraintsCode) -> &'static str {
455 match r {
456 CoreConstraintsCode::ResourceListEmpty => concat!($prefix, ":XSD/ResourceList-Empty"),
457 CoreConstraintsCode::ContentTitle => concat!($prefix, ":XSD/ContentTitle"),
458 CoreConstraintsCode::TotalRunningTimeFormat => concat!($prefix, ":XSD/TotalRunningTime-Format"),
459 CoreConstraintsCode::SegmentList => concat!($prefix, ":XSD/SegmentList"),
460 CoreConstraintsCode::Segment => concat!($prefix, ":XSD/Segment"),
461 CoreConstraintsCode::EditRate => concat!($prefix, ":XSD-88/EditRate"),
462 CoreConstraintsCode::IssueDate => concat!($prefix, ":XSD-66/IssueDate"),
463 CoreConstraintsCode::IssueDateFormat => concat!($prefix, ":XSD-66/IssueDate-Format"),
464 CoreConstraintsCode::CompositionTimecodeDropFrame => concat!($prefix, ":XSD-121-127/CompositionTimecode-DropFrame"),
465 CoreConstraintsCode::CompositionTimecodeRate => concat!($prefix, ":XSD-121-127/CompositionTimecode-Rate"),
466 CoreConstraintsCode::CompositionTimecodeStartAddress => concat!($prefix, ":XSD-121-127/CompositionTimecode-StartAddress"),
467 CoreConstraintsCode::CompositionTimecodeRateZero => concat!($prefix, ":XSD-121-127/CompositionTimecode-Rate-Zero"),
468 CoreConstraintsCode::CompositionTimecodeStartAddressFormat => concat!($prefix, ":XSD-121-127/CompositionTimecode-StartAddress-Format"),
469 CoreConstraintsCode::CompositionTimecodeRateMismatch => concat!($prefix, ":XSD-121-127/CompositionTimecode-RateMismatch"),
470 CoreConstraintsCode::LocaleListNonEmpty => concat!($prefix, ":XSD/LocaleList-NonEmpty"),
471 CoreConstraintsCode::UniqueSegmentId => concat!($prefix, ":6.1/UniqueSegmentId"),
472 CoreConstraintsCode::UniqueEssenceDescriptorId => concat!($prefix, ":6.1/UniqueEssenceDescriptorId"),
473 CoreConstraintsCode::UniqueResourceId => concat!($prefix, ":6.1/UniqueResourceId"),
474 CoreConstraintsCode::IntrinsicDuration => concat!($prefix, ":6.10/IntrinsicDuration"),
475 CoreConstraintsCode::EntryPoint => concat!($prefix, ":6.10/EntryPoint"),
476 CoreConstraintsCode::SourceDuration => concat!($prefix, ":6.10/SourceDuration"),
477 CoreConstraintsCode::ResourceDuration => concat!($prefix, ":6.10/ResourceDuration"),
478 CoreConstraintsCode::RepeatCount => concat!($prefix, ":6.10/RepeatCount"),
479 CoreConstraintsCode::TrackFileId => concat!($prefix, ":6.10/TrackFileId"),
480 CoreConstraintsCode::VirtualTrackContinuity => concat!($prefix, ":6.9/VirtualTrackContinuity"),
481 CoreConstraintsCode::VirtualTrackEditRate => concat!($prefix, ":6.9.3/VirtualTrackEditRate"),
482 CoreConstraintsCode::TimedTextSampleRate => concat!($prefix, ":10/TimedText-SampleRate"),
483 CoreConstraintsCode::TimedTextEmptyLanguageTag => concat!($prefix, ":10/TimedText-EmptyLanguageTag"),
484 CoreConstraintsCode::TimedTextMalformedLanguageTag => concat!($prefix, ":10/TimedText-MalformedLanguageTag"),
485 CoreConstraintsCode::AudioSampleRate => concat!($prefix, ":ST377-4/AudioSampleRate"),
486 CoreConstraintsCode::ChannelCount => concat!($prefix, ":ST377-4/ChannelCount"),
487 CoreConstraintsCode::MCASubDescriptors => concat!($prefix, ":ST377-4/MCASubDescriptors"),
488 CoreConstraintsCode::SoundfieldGroup => concat!($prefix, ":ST377-4/SoundfieldGroup"),
489 CoreConstraintsCode::MCATagSymbol => concat!($prefix, ":ST377-4/MCATagSymbol"),
490 CoreConstraintsCode::SoundfieldChannelCount => concat!($prefix, ":ST377-4/SoundfieldChannelCount"),
491 CoreConstraintsCode::DigitalSignature => concat!($prefix, ":8/DigitalSignature"),
492 CoreConstraintsCode::DanglingEssenceDescriptor => concat!($prefix, ":6.4.2/DanglingEssenceDescriptor"),
493 CoreConstraintsCode::EssenceDescriptorList => concat!($prefix, ":6.4.2/EssenceDescriptorList"),
494 }
495 }
496 }
497
498 impl From<$name> for String {
499 fn from(c: $name) -> String {
500 c.code().to_string()
501 }
502 }
503 };
504}
505
506define_core_constraints_enum!(St2067_2_2013_Core, "ST2067-2:2013");
507define_core_constraints_enum!(St2067_2_2016_Core, "ST2067-2:2016");
508define_core_constraints_enum!(St2067_2_2020_Core, "ST2067-2:2020");