use crate::diagnostics::codes::ValidationCode;
use crate::diagnostics::{Category, Severity};
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
pub enum St377_1_2011 {
NotMxf,
ParseError,
NoEssenceContainers,
Op1a,
NonHeaderFirstPartition,
HeaderPartitionOpen,
MissingHeaderMetadata,
}
impl ValidationCode for St377_1_2011 {
fn code(&self) -> &'static str {
match self {
Self::NotMxf => "ST377-1:2011:5/NotMxf",
Self::ParseError => "ST377-1:2011:5/ParseError",
Self::NoEssenceContainers => "ST377-1:2011:11/NoEssenceContainers",
Self::Op1a => "ST377-1:2011:7/OP1a",
Self::NonHeaderFirstPartition => "ST377-1:2011:6.4/NonHeaderFirstPartition",
Self::HeaderPartitionOpen => "ST377-1:2011:8.3.3/HeaderPartitionOpen",
Self::MissingHeaderMetadata => "ST377-1:2011:8.3.3/MissingHeaderMetadata",
}
}
fn description(&self) -> &'static str {
match self {
Self::NotMxf => "File is not a valid MXF container.",
Self::ParseError => "MXF file could not be parsed; it may be truncated or corrupt.",
Self::NoEssenceContainers => "MXF file contains no essence containers.",
Self::Op1a => "MXF operational pattern must be OP1a for IMF packages.",
Self::NonHeaderFirstPartition => {
"The first partition in an MXF file must be the Header partition."
}
Self::HeaderPartitionOpen => {
"The header partition status should be ClosedComplete for finished deliveries."
}
Self::MissingHeaderMetadata => {
"The header partition must carry header metadata (HeaderByteCount > 0)."
}
}
}
fn default_severity(&self) -> Severity {
match self {
Self::NotMxf | Self::ParseError | Self::NoEssenceContainers => Severity::Warning,
Self::HeaderPartitionOpen => Severity::Warning,
Self::Op1a | Self::NonHeaderFirstPartition | Self::MissingHeaderMetadata => {
Severity::Error
}
}
}
fn category(&self) -> Category {
match self {
Self::NotMxf | Self::ParseError => Category::Asset,
Self::NoEssenceContainers | Self::Op1a => Category::Encoding,
Self::NonHeaderFirstPartition
| Self::HeaderPartitionOpen
| Self::MissingHeaderMetadata => Category::Container,
}
}
fn example(&self) -> Option<&'static str> {
Some(match self {
Self::NotMxf =>
"An asset declared in the PKL with MIME `application/mxf` whose header doesn't carry the MXF run-in / partition pack key.",
Self::ParseError =>
"An MXF file that was truncated mid-transfer; the footer partition is missing or the RIP is unreadable.",
Self::NoEssenceContainers =>
"An MXF file whose header metadata declares no EssenceContainer ULs — the body has no decodable essence.",
Self::Op1a =>
"An MXF file whose Preface declares OperationalPattern OP-Atom or OP-3a instead of OP-1a.",
Self::NonHeaderFirstPartition =>
"An authoring tool wrote a Body partition before the Header partition, violating ST 377-1 ordering.",
Self::HeaderPartitionOpen =>
"A live-streaming export shipped a Header partition with OpenIncomplete status instead of finalising to ClosedComplete.",
Self::MissingHeaderMetadata =>
"An MXF file whose Header partition advertises HeaderByteCount = 0 — no metadata sets follow the partition pack.",
})
}
}
impl St377_1_2011 {
pub const ALL: &'static [Self] = &[
Self::NotMxf,
Self::ParseError,
Self::NoEssenceContainers,
Self::Op1a,
Self::NonHeaderFirstPartition,
Self::HeaderPartitionOpen,
Self::MissingHeaderMetadata,
];
}
impl From<St377_1_2011> for String {
fn from(c: St377_1_2011) -> String {
c.code().to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
pub enum St2067_2_2016 {
OperationalPatternNotOP1A,
SoundDescriptorNotWAVEPCM,
AudioSampleRateUnsupported,
QuantizationBitsNot24,
ChannelLabelCountMismatch,
MCAChannelIDMissing,
SoundFieldGroupLabelCount,
AudioNotClipWrapped,
RFC5646SpokenLanguageMissing,
ChannelAssignmentNotMCA,
SoundfieldGroupMissingMCATitle,
SoundfieldGroupMissingMCATitleVersion,
SoundfieldGroupMissingMCAAudioContentKind,
SoundfieldGroupMissingMCAAudioElementKind,
TimedTextUCSEncodingNotUTF8,
TimedTextNamespaceNotIMSC,
TimedTextResourceMIMETypeUnsupported,
TimedTextMappingKindNot0x13,
}
impl ValidationCode for St2067_2_2016 {
fn code(&self) -> &'static str {
match self {
Self::OperationalPatternNotOP1A => "ST2067-2:2016:5.2/OperationalPatternNotOP1A",
Self::SoundDescriptorNotWAVEPCM => "ST2067-2:2016:5.3.4.1/SoundDescriptorNotWAVEPCM",
Self::AudioSampleRateUnsupported => "ST2067-2:2016:5.3.2.2/AudioSampleRateUnsupported",
Self::QuantizationBitsNot24 => "ST2067-2:2016:5.3.2.3/QuantizationBitsNot24",
Self::ChannelLabelCountMismatch => "ST2067-2:2016:5.3.6.2/ChannelLabelCountMismatch",
Self::MCAChannelIDMissing => "ST2067-2:2016:5.3.6.2/MCAChannelIDMissing",
Self::SoundFieldGroupLabelCount => "ST2067-2:2016:5.3.6.3/SoundFieldGroupLabelCount",
Self::AudioNotClipWrapped => "ST2067-2:2016:5.3.3/AudioNotClipWrapped",
Self::RFC5646SpokenLanguageMissing => "ST2067-2:2016:5.3/RFC5646SpokenLanguageMissing",
Self::ChannelAssignmentNotMCA => "ST2067-2:2016:5.3.4.2/ChannelAssignmentNotMCA",
Self::SoundfieldGroupMissingMCATitle => {
"ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCATitle"
}
Self::SoundfieldGroupMissingMCATitleVersion => {
"ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCATitleVersion"
}
Self::SoundfieldGroupMissingMCAAudioContentKind => {
"ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCAAudioContentKind"
}
Self::SoundfieldGroupMissingMCAAudioElementKind => {
"ST2067-2:2016:5.3.6.5/SoundfieldGroupMissing/MCAAudioElementKind"
}
Self::TimedTextUCSEncodingNotUTF8 => "ST2067-2:2016:5.4/TimedTextUCSEncodingNotUTF8",
Self::TimedTextNamespaceNotIMSC => "ST2067-2:2016:5.4/TimedTextNamespaceNotIMSC",
Self::TimedTextResourceMIMETypeUnsupported => {
"ST2067-2:2016:5.4.5/TimedTextResourceMIMETypeUnsupported"
}
Self::TimedTextMappingKindNot0x13 => "ST2067-2:2016:5.4/TimedTextMappingKindNot0x13",
}
}
fn description(&self) -> &'static str {
match self {
Self::OperationalPatternNotOP1A =>
"MXF essence Operational Pattern UL is not OP1a (ST 378:2004).",
Self::SoundDescriptorNotWAVEPCM =>
"Audio essence descriptor must be WAVEPCMDescriptor.",
Self::AudioSampleRateUnsupported =>
"AudioSampleRate must be 48000 Hz or 96000 Hz.",
Self::QuantizationBitsNot24 => "QuantizationBits must be 24.",
Self::ChannelLabelCountMismatch =>
"Number of AudioChannelLabelSubDescriptors must equal ChannelCount.",
Self::MCAChannelIDMissing =>
"Every channel index 1..ChannelCount must have an AudioChannelLabelSubDescriptor.",
Self::SoundFieldGroupLabelCount =>
"Exactly one SoundfieldGroupLabelSubDescriptor is required per WAVEPCMDescriptor.",
Self::AudioNotClipWrapped =>
"Audio essence must be Wave Clip-Wrapped (ST 382 §10).",
Self::RFC5646SpokenLanguageMissing =>
"Audio descriptor should carry an RFC-5646 spoken language tag.",
Self::ChannelAssignmentNotMCA =>
"ChannelAssignment UL must be a SMPTE 428-12 MCA channel-layout UL.",
Self::SoundfieldGroupMissingMCATitle =>
"SoundfieldGroupLabelSubDescriptor is missing MCATitle.",
Self::SoundfieldGroupMissingMCATitleVersion =>
"SoundfieldGroupLabelSubDescriptor is missing MCATitleVersion.",
Self::SoundfieldGroupMissingMCAAudioContentKind =>
"SoundfieldGroupLabelSubDescriptor is missing MCAAudioContentKind.",
Self::SoundfieldGroupMissingMCAAudioElementKind =>
"SoundfieldGroupLabelSubDescriptor is missing MCAAudioElementKind.",
Self::TimedTextUCSEncodingNotUTF8 =>
"TimedTextDescriptor UCSEncoding must be UTF-8.",
Self::TimedTextNamespaceNotIMSC =>
"TimedTextDescriptor NamespaceURI must be one of the IMSC1 profile namespaces.",
Self::TimedTextResourceMIMETypeUnsupported =>
"TimeTextResourceSubDescriptor MIMEType must be image/png or application/x-font-opentype.",
Self::TimedTextMappingKindNot0x13 =>
"Timed-text ContainerFormat UL Mapping Kind byte must be 0x13 (IMSC).",
}
}
fn default_severity(&self) -> Severity {
match self {
Self::RFC5646SpokenLanguageMissing
| Self::SoundfieldGroupMissingMCATitle
| Self::SoundfieldGroupMissingMCATitleVersion
| Self::SoundfieldGroupMissingMCAAudioContentKind
| Self::SoundfieldGroupMissingMCAAudioElementKind => Severity::Warning,
_ => Severity::Error,
}
}
fn category(&self) -> Category {
match self {
Self::SoundDescriptorNotWAVEPCM
| Self::AudioSampleRateUnsupported
| Self::QuantizationBitsNot24
| Self::ChannelLabelCountMismatch
| Self::MCAChannelIDMissing
| Self::SoundFieldGroupLabelCount
| Self::RFC5646SpokenLanguageMissing
| Self::ChannelAssignmentNotMCA
| Self::SoundfieldGroupMissingMCATitle
| Self::SoundfieldGroupMissingMCATitleVersion
| Self::SoundfieldGroupMissingMCAAudioContentKind
| Self::SoundfieldGroupMissingMCAAudioElementKind => Category::Audio,
Self::TimedTextUCSEncodingNotUTF8
| Self::TimedTextNamespaceNotIMSC
| Self::TimedTextResourceMIMETypeUnsupported => Category::Subtitle,
Self::OperationalPatternNotOP1A
| Self::AudioNotClipWrapped
| Self::TimedTextMappingKindNot0x13 => Category::Container,
}
}
fn example(&self) -> Option<&'static str> {
Some(match self {
Self::OperationalPatternNotOP1A =>
"Header partition pack OP UL bytes 13..14 = 02 03 (OP2a) instead of OP1a (01 01)",
Self::SoundDescriptorNotWAVEPCM =>
"Audio essence descriptor is AES3PCMDescriptor instead of WAVEPCMDescriptor",
Self::AudioSampleRateUnsupported =>
"<AudioSampleRate>44100/1</AudioSampleRate> <!-- only 48000 or 96000 allowed -->",
Self::QuantizationBitsNot24 =>
"<QuantizationBits>16</QuantizationBits>",
Self::ChannelLabelCountMismatch =>
"<ChannelCount>6</ChannelCount> but only 2 AudioChannelLabelSubDescriptor entries present",
Self::MCAChannelIDMissing =>
"ChannelCount=6 but no AudioChannelLabelSubDescriptor with MCAChannelID=4",
Self::SoundFieldGroupLabelCount =>
"WAVEPCMDescriptor with 2 SoundfieldGroupLabelSubDescriptor children (must be exactly 1)",
Self::AudioNotClipWrapped =>
"ContainerFormat UL byte 14 = 0x02 (frame-wrapped) instead of 0x06 (clip-wrapped)",
Self::RFC5646SpokenLanguageMissing =>
"No RFC5646SpokenLanguage tag on any AudioChannelLabel/SoundfieldGroupLabel sub-descriptor",
Self::ChannelAssignmentNotMCA =>
"ChannelAssignment UL outside the SMPTE 428-12 MCA label range (bytes 9..16)",
Self::SoundfieldGroupMissingMCATitle =>
"SoundfieldGroupLabelSubDescriptor with no <MCATitle> item",
Self::SoundfieldGroupMissingMCATitleVersion =>
"SoundfieldGroupLabelSubDescriptor with no <MCATitleVersion> item",
Self::SoundfieldGroupMissingMCAAudioContentKind =>
"SoundfieldGroupLabelSubDescriptor with no <MCAAudioContentKind> item",
Self::SoundfieldGroupMissingMCAAudioElementKind =>
"SoundfieldGroupLabelSubDescriptor with no <MCAAudioElementKind> item",
Self::TimedTextUCSEncodingNotUTF8 =>
"<UCSEncoding>ISO-8859-1</UCSEncoding>",
Self::TimedTextNamespaceNotIMSC =>
"<NamespaceURI>http://www.w3.org/ns/ttml</NamespaceURI> <!-- not an IMSC1 profile URI -->",
Self::TimedTextResourceMIMETypeUnsupported =>
"<TimeTextResourceSubDescriptor><MIMEType>application/json</MIMEType></TimeTextResourceSubDescriptor>",
Self::TimedTextMappingKindNot0x13 =>
"Timed-text ContainerFormat UL byte 15 (Mapping Kind) = 0x12 instead of 0x13 (IMSC)",
})
}
}
impl St2067_2_2016 {
pub const ALL: &'static [Self] = &[
Self::OperationalPatternNotOP1A,
Self::SoundDescriptorNotWAVEPCM,
Self::AudioSampleRateUnsupported,
Self::QuantizationBitsNot24,
Self::ChannelLabelCountMismatch,
Self::MCAChannelIDMissing,
Self::SoundFieldGroupLabelCount,
Self::AudioNotClipWrapped,
Self::RFC5646SpokenLanguageMissing,
Self::ChannelAssignmentNotMCA,
Self::SoundfieldGroupMissingMCATitle,
Self::SoundfieldGroupMissingMCATitleVersion,
Self::SoundfieldGroupMissingMCAAudioContentKind,
Self::SoundfieldGroupMissingMCAAudioElementKind,
Self::TimedTextUCSEncodingNotUTF8,
Self::TimedTextNamespaceNotIMSC,
Self::TimedTextResourceMIMETypeUnsupported,
Self::TimedTextMappingKindNot0x13,
];
}
impl From<St2067_2_2016> for String {
fn from(c: St2067_2_2016) -> String {
c.code().to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
pub enum St377_4_2012 {
MCALinkIDMissing,
SoundfieldGroupLinkIDMismatch,
}
impl ValidationCode for St377_4_2012 {
fn code(&self) -> &'static str {
match self {
Self::MCALinkIDMissing => "ST377-4:2012:6.3.2/MCALinkIDMissing",
Self::SoundfieldGroupLinkIDMismatch => {
"ST377-4:2012:6.3.2/SoundfieldGroupLinkIDMismatch"
}
}
}
fn description(&self) -> &'static str {
match self {
Self::MCALinkIDMissing => "Every MCA sub-descriptor must carry an MCALinkID.",
Self::SoundfieldGroupLinkIDMismatch =>
"AudioChannelLabelSubDescriptor SoundfieldGroupLinkID must equal the SoundfieldGroup's MCALinkID.",
}
}
fn default_severity(&self) -> Severity {
Severity::Error
}
fn category(&self) -> Category {
Category::Audio
}
fn example(&self) -> Option<&'static str> {
Some(match self {
Self::MCALinkIDMissing =>
"<AudioChannelLabelSubDescriptor>…</AudioChannelLabelSubDescriptor> <!-- no <MCALinkID> child -->",
Self::SoundfieldGroupLinkIDMismatch =>
"<AudioChannelLabelSubDescriptor><SoundfieldGroupLinkID>urn:uuid:abc…</SoundfieldGroupLinkID></AudioChannelLabelSubDescriptor> <!-- but the parent SoundfieldGroupLabelSubDescriptor MCALinkID = urn:uuid:def… -->",
})
}
}
impl St377_4_2012 {
pub const ALL: &'static [Self] = &[Self::MCALinkIDMissing, Self::SoundfieldGroupLinkIDMismatch];
}
impl From<St377_4_2012> for String {
fn from(c: St377_4_2012) -> String {
c.code().to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::EnumIter)]
pub enum ImfernoMxf {
OpenFailed,
PartitionPackParseFailed,
RegXmlConversionFailed,
EssenceContainersDetected,
}
impl ValidationCode for ImfernoMxf {
fn code(&self) -> &'static str {
match self {
Self::OpenFailed => "IMFERNO:Mxf/OpenFailed",
Self::PartitionPackParseFailed => "IMFERNO:Mxf/PartitionPackParseFailed",
Self::RegXmlConversionFailed => "IMFERNO:Mxf/RegXmlConversionFailed",
Self::EssenceContainersDetected => "IMFERNO:Mxf/EssenceContainersDetected",
}
}
fn description(&self) -> &'static str {
match self {
Self::OpenFailed => "MXF file could not be opened for reading.",
Self::PartitionPackParseFailed => "MXF header partition pack failed to parse.",
Self::RegXmlConversionFailed => "MXF header metadata could not be converted to RegXML.",
Self::EssenceContainersDetected => {
"Informational trace of how many essence containers the MXF declares."
}
}
}
fn default_severity(&self) -> Severity {
match self {
Self::OpenFailed | Self::PartitionPackParseFailed => Severity::Critical,
Self::RegXmlConversionFailed => Severity::Warning,
Self::EssenceContainersDetected => Severity::Info,
}
}
fn category(&self) -> Category {
Category::Container
}
fn example(&self) -> Option<&'static str> {
Some(match self {
Self::OpenFailed =>
"fs::File::open(\"audio1.mxf\") returned Err (file missing or unreadable)",
Self::PartitionPackParseFailed =>
"Header partition pack KLV is malformed (e.g. BER length undershoots the minimum 88-byte body)",
Self::RegXmlConversionFailed =>
"smpte-mxf could not convert header metadata to RegXML (e.g. unknown KLV set in header)",
Self::EssenceContainersDetected =>
"Header partition pack declares N essence container ULs — info notice surfaces the count for traceability",
})
}
}
impl ImfernoMxf {
pub const ALL: &'static [Self] = &[
Self::OpenFailed,
Self::PartitionPackParseFailed,
Self::RegXmlConversionFailed,
Self::EssenceContainersDetected,
];
}
impl From<ImfernoMxf> for String {
fn from(c: ImfernoMxf) -> String {
c.code().to_string()
}
}