imferno-core 2.2.0

SMPTE ST 2067 IMF parser and validator
Documentation
//! Typed validation-code catalogue for SMPTE ST 2067-201 (IAB Level 0 Plug-in).
//!
//! The same 21 reason codes apply to both the 2019 and 2021 editions; a
//! `macro_rules!` generates `St2067_201_2019` and `St2067_201_2021` from a
//! single source-of-truth variant list.  Each edition enum also exposes
//! `for_code` so that shared helper functions can produce the right
//! `&'static str` without any runtime string building.

use crate::diagnostics::codes::ValidationCode;
use crate::diagnostics::{Category, Severity};

// ─────────────────────────────────────────────────────────────────────────────
// Spec-agnostic reason codes
// ─────────────────────────────────────────────────────────────────────────────

/// Spec-agnostic reason codes for IAB Level 0 Plug-in validation.
///
/// Passed to each edition's `for_code` dispatch function to get the full
/// `&'static str` code without any runtime string building.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IabCode {
    /// IABEssenceDescriptor: Codec item shall not be present (§5.9).
    CodecForbidden,
    /// IABEssenceDescriptor: ElectrospatialFormulation shall not be present (§5.9).
    ElectrospatialFormulationForbidden,
    /// IABEssenceDescriptor: QuantizationBits is missing; shall be 24 (§5.9).
    QuantizationBitsMissing,
    /// IABEssenceDescriptor: QuantizationBits is present but not 24 (§5.9).
    QuantizationBitsInvalid,
    /// IABEssenceDescriptor: ContainerFormat is missing (§5.3).
    ContainerFormatMissing,
    /// IABEssenceDescriptor: ContainerFormat is not the required IAB container UL (§5.3).
    EssenceContainerInvalid,
    /// IABEssenceDescriptor: AudioSampleRate is missing; shall be 48000/1 (§5.9).
    AudioSamplingRateMissing,
    /// IABEssenceDescriptor: AudioSampleRate is not 48000/1 (§5.9).
    AudioSamplingRateInvalid,
    /// IABEssenceDescriptor: SoundCompression is missing (§5.9).
    SoundCompressionMissing,
    /// IABEssenceDescriptor: SoundCompression is not the required IAB compression UL (§5.9).
    SoundCompressionInvalid,
    /// IABEssenceDescriptor: ChannelCount shall be the distinguished value 0 (§5.9, 2019 only).
    ChannelCountNotZero,
    /// IABEssenceDescriptor: IABSoundfieldLabelSubDescriptor shall be present (§5.9).
    SubDescriptorMissing,
    /// IABSoundfieldLabelSubDescriptor: MCATagSymbol shall be "IAB" — missing (§5.9).
    MCATagSymbolMissing,
    /// IABSoundfieldLabelSubDescriptor: MCATagSymbol shall be "IAB" — wrong value (§5.9).
    MCATagSymbolInvalid,
    /// IABSoundfieldLabelSubDescriptor: MCATagName shall be "IAB" — missing (§5.9).
    MCATagNameMissing,
    /// IABSoundfieldLabelSubDescriptor: MCATagName shall be "IAB" — wrong value (§5.9).
    MCATagNameInvalid,
    /// IABSoundfieldLabelSubDescriptor: MCALabelDictionaryID is missing (§5.9).
    MCALabelDictionaryIDMissing,
    /// IABSoundfieldLabelSubDescriptor: MCALabelDictionaryID is not the required IAB label UL (§5.9).
    MCALabelDictionaryIDInvalid,
    /// Segment has IABSequence but no MainAudioSequence (§6.2).
    MainAudioMissing,
    /// IABSequence shall contain at least one Resource (§6.2).
    IABSequenceNoResources,
    /// IABSequence Resource.SourceEncoding does not reference an IABEssenceDescriptor (§6.2).
    IABSequenceSourceEncodingInvalid,
}

// ─────────────────────────────────────────────────────────────────────────────
// Edition-specific enums (generated by macro)
// ─────────────────────────────────────────────────────────────────────────────

macro_rules! define_iab_enum {
    ($name:ident, $prefix:literal) => {
        /// IAB Level 0 Plug-in validation codes, edition
        #[doc = $prefix]
        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
        pub enum $name {
            /// IABEssenceDescriptor: Codec item shall not be present (§5.9).
            CodecForbidden,
            /// IABEssenceDescriptor: ElectrospatialFormulation shall not be present (§5.9).
            ElectrospatialFormulationForbidden,
            /// IABEssenceDescriptor: QuantizationBits is missing; shall be 24 (§5.9).
            QuantizationBitsMissing,
            /// IABEssenceDescriptor: QuantizationBits is present but not 24 (§5.9).
            QuantizationBitsInvalid,
            /// IABEssenceDescriptor: ContainerFormat is missing (§5.3).
            ContainerFormatMissing,
            /// IABEssenceDescriptor: ContainerFormat is not the required IAB container UL (§5.3).
            EssenceContainerInvalid,
            /// IABEssenceDescriptor: AudioSampleRate is missing; shall be 48000/1 (§5.9).
            AudioSamplingRateMissing,
            /// IABEssenceDescriptor: AudioSampleRate is not 48000/1 (§5.9).
            AudioSamplingRateInvalid,
            /// IABEssenceDescriptor: SoundCompression is missing (§5.9).
            SoundCompressionMissing,
            /// IABEssenceDescriptor: SoundCompression is not the required IAB compression UL (§5.9).
            SoundCompressionInvalid,
            /// IABEssenceDescriptor: ChannelCount shall be the distinguished value 0 (§5.9, 2019 only).
            ChannelCountNotZero,
            /// IABEssenceDescriptor: IABSoundfieldLabelSubDescriptor shall be present (§5.9).
            SubDescriptorMissing,
            /// IABSoundfieldLabelSubDescriptor: MCATagSymbol shall be "IAB" — missing (§5.9).
            MCATagSymbolMissing,
            /// IABSoundfieldLabelSubDescriptor: MCATagSymbol shall be "IAB" — wrong value (§5.9).
            MCATagSymbolInvalid,
            /// IABSoundfieldLabelSubDescriptor: MCATagName shall be "IAB" — missing (§5.9).
            MCATagNameMissing,
            /// IABSoundfieldLabelSubDescriptor: MCATagName shall be "IAB" — wrong value (§5.9).
            MCATagNameInvalid,
            /// IABSoundfieldLabelSubDescriptor: MCALabelDictionaryID is missing (§5.9).
            MCALabelDictionaryIDMissing,
            /// IABSoundfieldLabelSubDescriptor: MCALabelDictionaryID is not the required IAB label UL (§5.9).
            MCALabelDictionaryIDInvalid,
            /// Segment has IABSequence but no MainAudioSequence (§6.2).
            MainAudioMissing,
            /// IABSequence shall contain at least one Resource (§6.2).
            IABSequenceNoResources,
            /// IABSequence Resource.SourceEncoding does not reference an IABEssenceDescriptor (§6.2).
            IABSequenceSourceEncodingInvalid,
        }

        impl ValidationCode for $name {
            fn code(&self) -> &'static str {
                match self {
                    Self::CodecForbidden                 => concat!($prefix, ":5.9/CodecForbidden"),
                    Self::ElectrospatialFormulationForbidden => concat!($prefix, ":5.9/ElectrospatialFormulationForbidden"),
                    Self::QuantizationBitsMissing        => concat!($prefix, ":5.9/QuantizationBitsMissing"),
                    Self::QuantizationBitsInvalid        => concat!($prefix, ":5.9/QuantizationBitsInvalid"),
                    Self::ContainerFormatMissing         => concat!($prefix, ":5.3/ContainerFormatMissing"),
                    Self::EssenceContainerInvalid        => concat!($prefix, ":5.3/EssenceContainerInvalid"),
                    Self::AudioSamplingRateMissing       => concat!($prefix, ":5.9/AudioSamplingRateMissing"),
                    Self::AudioSamplingRateInvalid       => concat!($prefix, ":5.9/AudioSamplingRateInvalid"),
                    Self::SoundCompressionMissing        => concat!($prefix, ":5.9/SoundCompressionMissing"),
                    Self::SoundCompressionInvalid        => concat!($prefix, ":5.9/SoundCompressionInvalid"),
                    Self::ChannelCountNotZero            => concat!($prefix, ":5.9/ChannelCountNotZero"),
                    Self::SubDescriptorMissing           => concat!($prefix, ":5.9/SubDescriptorMissing"),
                    Self::MCATagSymbolMissing            => concat!($prefix, ":5.9/MCATagSymbolMissing"),
                    Self::MCATagSymbolInvalid            => concat!($prefix, ":5.9/MCATagSymbolInvalid"),
                    Self::MCATagNameMissing              => concat!($prefix, ":5.9/MCATagNameMissing"),
                    Self::MCATagNameInvalid              => concat!($prefix, ":5.9/MCATagNameInvalid"),
                    Self::MCALabelDictionaryIDMissing    => concat!($prefix, ":5.9/MCALabelDictionaryIDMissing"),
                    Self::MCALabelDictionaryIDInvalid    => concat!($prefix, ":5.9/MCALabelDictionaryIDInvalid"),
                    Self::MainAudioMissing               => concat!($prefix, ":6.2/MainAudioMissing"),
                    Self::IABSequenceNoResources         => concat!($prefix, ":6.2/IABSequenceNoResources"),
                    Self::IABSequenceSourceEncodingInvalid => concat!($prefix, ":6.2/IABSequenceSourceEncodingInvalid"),
                }
            }
            fn description(&self) -> &'static str {
                match self {
                    Self::CodecForbidden                 => "IABEssenceDescriptor: Codec item shall not be present (§5.9).",
                    Self::ElectrospatialFormulationForbidden => "IABEssenceDescriptor: ElectrospatialFormulation shall not be present (§5.9).",
                    Self::QuantizationBitsMissing        => "IABEssenceDescriptor: QuantizationBits is missing; shall be 24.",
                    Self::QuantizationBitsInvalid        => "IABEssenceDescriptor: QuantizationBits shall be 24.",
                    Self::ContainerFormatMissing         => "IABEssenceDescriptor: ContainerFormat is missing.",
                    Self::EssenceContainerInvalid        => "IABEssenceDescriptor: ContainerFormat is not the required IAB container UL.",
                    Self::AudioSamplingRateMissing       => "IABEssenceDescriptor: AudioSampleRate is missing; shall be 48000/1.",
                    Self::AudioSamplingRateInvalid       => "IABEssenceDescriptor: AudioSampleRate shall be 48000/1.",
                    Self::SoundCompressionMissing        => "IABEssenceDescriptor: SoundCompression is missing.",
                    Self::SoundCompressionInvalid        => "IABEssenceDescriptor: SoundCompression is not the required IAB compression UL.",
                    Self::ChannelCountNotZero            => "IABEssenceDescriptor: ChannelCount shall be the distinguished value 0 (2019 edition).",
                    Self::SubDescriptorMissing           => "IABEssenceDescriptor: IABSoundfieldLabelSubDescriptor shall be present.",
                    Self::MCATagSymbolMissing            => "IABSoundfieldLabelSubDescriptor: MCATagSymbol is missing; shall be \"IAB\".",
                    Self::MCATagSymbolInvalid            => "IABSoundfieldLabelSubDescriptor: MCATagSymbol shall be \"IAB\".",
                    Self::MCATagNameMissing              => "IABSoundfieldLabelSubDescriptor: MCATagName is missing; shall be \"IAB\".",
                    Self::MCATagNameInvalid              => "IABSoundfieldLabelSubDescriptor: MCATagName shall be \"IAB\".",
                    Self::MCALabelDictionaryIDMissing    => "IABSoundfieldLabelSubDescriptor: MCALabelDictionaryID is missing.",
                    Self::MCALabelDictionaryIDInvalid    => "IABSoundfieldLabelSubDescriptor: MCALabelDictionaryID is not the required IAB label UL.",
                    Self::MainAudioMissing               => "Segment has IABSequence but no MainAudioSequence (§6.2).",
                    Self::IABSequenceNoResources         => "IABSequence shall contain at least one Resource (§6.2).",
                    Self::IABSequenceSourceEncodingInvalid => "IABSequence Resource.SourceEncoding does not reference an IABEssenceDescriptor (§6.2).",
                }
            }
            fn default_severity(&self) -> Severity {
                match self {
                    Self::QuantizationBitsMissing
                    | Self::ContainerFormatMissing
                    | Self::AudioSamplingRateMissing
                    | Self::SoundCompressionMissing => Severity::Warning,
                    _ => Severity::Error,
                }
            }
            fn category(&self) -> Category {
                Category::Audio
            }
        }

        impl $name {
            pub const ALL: &'static [Self] = &[
                Self::CodecForbidden,
                Self::ElectrospatialFormulationForbidden,
                Self::QuantizationBitsMissing,
                Self::QuantizationBitsInvalid,
                Self::ContainerFormatMissing,
                Self::EssenceContainerInvalid,
                Self::AudioSamplingRateMissing,
                Self::AudioSamplingRateInvalid,
                Self::SoundCompressionMissing,
                Self::SoundCompressionInvalid,
                Self::ChannelCountNotZero,
                Self::SubDescriptorMissing,
                Self::MCATagSymbolMissing,
                Self::MCATagSymbolInvalid,
                Self::MCATagNameMissing,
                Self::MCATagNameInvalid,
                Self::MCALabelDictionaryIDMissing,
                Self::MCALabelDictionaryIDInvalid,
                Self::MainAudioMissing,
                Self::IABSequenceNoResources,
                Self::IABSequenceSourceEncodingInvalid,
            ];

            /// Dispatch from the spec-agnostic [`IabCode`] to this
            /// edition's static code string.  Used by the shared validator helpers.
            pub fn for_code(r: IabCode) -> &'static str {
                match r {
                    IabCode::CodecForbidden                    => concat!($prefix, ":5.9/CodecForbidden"),
                    IabCode::ElectrospatialFormulationForbidden => concat!($prefix, ":5.9/ElectrospatialFormulationForbidden"),
                    IabCode::QuantizationBitsMissing           => concat!($prefix, ":5.9/QuantizationBitsMissing"),
                    IabCode::QuantizationBitsInvalid           => concat!($prefix, ":5.9/QuantizationBitsInvalid"),
                    IabCode::ContainerFormatMissing            => concat!($prefix, ":5.3/ContainerFormatMissing"),
                    IabCode::EssenceContainerInvalid           => concat!($prefix, ":5.3/EssenceContainerInvalid"),
                    IabCode::AudioSamplingRateMissing          => concat!($prefix, ":5.9/AudioSamplingRateMissing"),
                    IabCode::AudioSamplingRateInvalid          => concat!($prefix, ":5.9/AudioSamplingRateInvalid"),
                    IabCode::SoundCompressionMissing           => concat!($prefix, ":5.9/SoundCompressionMissing"),
                    IabCode::SoundCompressionInvalid           => concat!($prefix, ":5.9/SoundCompressionInvalid"),
                    IabCode::ChannelCountNotZero               => concat!($prefix, ":5.9/ChannelCountNotZero"),
                    IabCode::SubDescriptorMissing              => concat!($prefix, ":5.9/SubDescriptorMissing"),
                    IabCode::MCATagSymbolMissing               => concat!($prefix, ":5.9/MCATagSymbolMissing"),
                    IabCode::MCATagSymbolInvalid               => concat!($prefix, ":5.9/MCATagSymbolInvalid"),
                    IabCode::MCATagNameMissing                 => concat!($prefix, ":5.9/MCATagNameMissing"),
                    IabCode::MCATagNameInvalid                 => concat!($prefix, ":5.9/MCATagNameInvalid"),
                    IabCode::MCALabelDictionaryIDMissing       => concat!($prefix, ":5.9/MCALabelDictionaryIDMissing"),
                    IabCode::MCALabelDictionaryIDInvalid       => concat!($prefix, ":5.9/MCALabelDictionaryIDInvalid"),
                    IabCode::MainAudioMissing                  => concat!($prefix, ":6.2/MainAudioMissing"),
                    IabCode::IABSequenceNoResources            => concat!($prefix, ":6.2/IABSequenceNoResources"),
                    IabCode::IABSequenceSourceEncodingInvalid  => concat!($prefix, ":6.2/IABSequenceSourceEncodingInvalid"),
                }
            }
        }

        impl From<$name> for String {
            fn from(c: $name) -> String {
                c.code().to_string()
            }
        }
    };
}

define_iab_enum!(St2067_201_2019, "ST2067-201:2019");
define_iab_enum!(St2067_201_2021, "ST2067-201:2021");