use std::collections::{HashMap, HashSet};
use crate::cpl::CompositionPlaylist;
use crate::diagnostics::{Category, Severity, ValidationIssue};
use crate::validation::{App2E2021, ConstraintsValidator};
use crate::validation::isxd_codes::{self as isxd_codes, IsxdCode};
pub struct AppIsxdPlugin2022;
impl ConstraintsValidator for AppIsxdPlugin2022 {
fn spec_id(&self) -> &str {
"ST 2067-202:2022 (ISXD Plug-in)"
}
fn validate_cpl(&self, cpl: &CompositionPlaylist) -> Vec<ValidationIssue> {
let mut issues = Vec::new();
App2E2021.validate_all(cpl, true, &mut issues);
validate_isxd_descriptors(cpl, isxd_codes::St2067_202_2022::for_code, &mut issues);
validate_isxd_sequences(cpl, isxd_codes::St2067_202_2022::for_code, &mut issues);
issues
}
}
pub const URI_2022: &str = "http://www.smpte-ra.org/ns/2067-202/2022";
fn validate_isxd_descriptors(
cpl: &CompositionPlaylist,
code: fn(IsxdCode) -> &'static str,
issues: &mut Vec<ValidationIssue>,
) {
let eds = match cpl.essence_descriptor_list.as_ref() {
Some(list) => &list.essence_descriptors,
None => return,
};
for ed in eds {
let isxd = match ed.isxd_data_essence_descriptor.as_ref() {
Some(d) => d,
None => continue,
};
let has_ccsd = isxd
.sub_descriptors
.as_ref()
.and_then(|s| s.container_constraints_sub_descriptor.as_ref())
.is_some();
if !has_ccsd {
issues.push(ValidationIssue::new(
Severity::Error,
Category::Audio,
code(IsxdCode::SubDescriptorMissing),
format!(
"ISXDDataEssenceDescriptor (EssenceDescriptor Id={}) is missing \
ContainerConstraintsSubDescriptor.",
ed.id
),
));
}
if isxd.namespace_uri.is_none() {
issues.push(ValidationIssue::new(
Severity::Warning,
Category::Audio,
code(IsxdCode::NamespaceUriMissing),
format!(
"ISXDDataEssenceDescriptor (EssenceDescriptor Id={}) is missing NamespaceURI.",
ed.id
),
));
}
}
}
fn validate_isxd_sequences(
cpl: &CompositionPlaylist,
code: fn(IsxdCode) -> &'static str,
issues: &mut Vec<ValidationIssue>,
) {
let isxd_descriptor_map: HashMap<String, Option<String>> = cpl
.essence_descriptor_list
.as_ref()
.map(|edl| {
edl.essence_descriptors
.iter()
.filter_map(|ed| {
ed.isxd_data_essence_descriptor
.as_ref()
.map(|d| (ed.id.to_string(), d.namespace_uri.clone()))
})
.collect()
})
.unwrap_or_default();
for segment in &cpl.segment_list.segments {
let sl = &segment.sequence_list;
for isxd_seq in &sl.isxd_sequences {
let resources = &isxd_seq.resource_list.resources;
if resources.is_empty() {
issues.push(ValidationIssue::new(
Severity::Error,
Category::Audio,
code(IsxdCode::ISXDSequenceNoResources),
format!("ISXDSequence (Id={}) contains no Resources.", isxd_seq.id),
));
continue;
}
let mut namespace_uris: HashSet<String> = HashSet::new();
for resource in resources {
let se_uuid = match resource.source_encoding {
Some(ref uuid) => uuid.to_string(),
None => continue,
};
match isxd_descriptor_map.get(&se_uuid) {
Some(ns_uri) => {
if let Some(uri) = ns_uri {
namespace_uris.insert(uri.clone());
}
}
None => {
issues.push(ValidationIssue::new(
Severity::Error,
Category::Audio,
code(IsxdCode::ISXDSequenceSourceEncodingInvalid),
format!(
"ISXDSequence (Id={}) Resource (Id={}) SourceEncoding={} does not \
reference an ISXDDataEssenceDescriptor.",
isxd_seq.id, resource.id, se_uuid
),
));
}
}
}
if namespace_uris.len() > 1 {
let mut uris: Vec<_> = namespace_uris.into_iter().collect();
uris.sort();
issues.push(ValidationIssue::new(
Severity::Error,
Category::Audio,
code(IsxdCode::NamespaceUriMismatch),
format!(
"ISXDSequence (Id={}) references descriptors with inconsistent \
NamespaceURI values: {:?}",
isxd_seq.id, uris
),
));
}
}
}
}