use alloc::string::String;
use alloc::vec::Vec;
use broadcast_common::timescale::{Rounding, rescale_i64};
use dvb_si::descriptors::aac::AacComponentType;
use dvb_si::descriptors::any::{AnyDescriptor, parse_loop};
use dvb_si::descriptors::extension::ExtensionBody;
use dvb_si::descriptors::iso_639_language::AudioType as DvbAudioType;
use super::{Track, TrackSpec};
use crate::aac_asc::HeAacProfile;
const LANG_AUDIO_DESCRIPTION: &str = "qad";
const EDITORIAL_MAIN: u8 = 0x00;
const EDITORIAL_AUDIO_DESCRIPTION: u8 = 0x01;
const EDITORIAL_CLEAN_AUDIO_HI: u8 = 0x02;
const EDITORIAL_SPOKEN_SUBTITLES: u8 = 0x03;
const EDITORIAL_PARAMETRIC: u8 = 0x04;
const EDITORIAL_GENERAL_AUDIENCE: u8 = 0x17;
const EDITORIAL_USER_DEFINED_MIN: u8 = 0x18;
const EDITORIAL_USER_DEFINED_MAX: u8 = 0x1F;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum AudioType {
Undefined,
CleanEffects,
HearingImpaired,
VisualImpairedCommentary,
Other(u8),
}
impl AudioType {
fn from_dvb(t: DvbAudioType) -> Self {
match t {
DvbAudioType::Undefined => Self::Undefined,
DvbAudioType::CleanEffects => Self::CleanEffects,
DvbAudioType::HearingImpaired => Self::HearingImpaired,
DvbAudioType::VisualImpairedCommentary => Self::VisualImpairedCommentary,
other => Self::Other(other.to_u8()),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Undefined => "undefined",
Self::CleanEffects => "clean effects",
Self::HearingImpaired => "hearing impaired",
Self::VisualImpairedCommentary => "visual impaired commentary",
Self::Other(_) => "other",
}
}
}
broadcast_common::impl_spec_display!(AudioType, Other);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum MixType {
Dependent,
Independent,
}
impl MixType {
pub fn name(&self) -> &'static str {
match self {
Self::Dependent => "dependent",
Self::Independent => "independent",
}
}
}
broadcast_common::impl_spec_display!(MixType);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum EditorialClassification {
MainAudio,
AudioDescription,
CleanAudioHearingImpaired,
SpokenSubtitles,
DependentParametricData,
GeneralAudience,
UserDefined(u8),
Reserved(u8),
}
impl EditorialClassification {
fn from_u8(v: u8) -> Self {
match v {
EDITORIAL_MAIN => Self::MainAudio,
EDITORIAL_AUDIO_DESCRIPTION => Self::AudioDescription,
EDITORIAL_CLEAN_AUDIO_HI => Self::CleanAudioHearingImpaired,
EDITORIAL_SPOKEN_SUBTITLES => Self::SpokenSubtitles,
EDITORIAL_PARAMETRIC => Self::DependentParametricData,
EDITORIAL_GENERAL_AUDIENCE => Self::GeneralAudience,
EDITORIAL_USER_DEFINED_MIN..=EDITORIAL_USER_DEFINED_MAX => Self::UserDefined(v),
v => Self::Reserved(v),
}
}
pub fn name(&self) -> &'static str {
match self {
Self::MainAudio => "main audio",
Self::AudioDescription => "audio description",
Self::CleanAudioHearingImpaired => "clean audio for the hearing impaired",
Self::SpokenSubtitles => "spoken subtitles",
Self::DependentParametricData => "dependent parametric data",
Self::GeneralAudience => "general audience supplementary audio",
Self::UserDefined(_) => "user defined",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(EditorialClassification, UserDefined, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum AudioRole {
Main,
AudioDescription,
HearingImpaired,
Unknown,
}
impl AudioRole {
pub fn name(&self) -> &'static str {
match self {
Self::Main => "main",
Self::AudioDescription => "audio description",
Self::HearingImpaired => "hearing impaired",
Self::Unknown => "unknown",
}
}
}
broadcast_common::impl_spec_display!(AudioRole);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct AudioLanguage {
pub language: String,
pub audio_type: AudioType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct SupplementaryAudio {
pub mix_type: MixType,
pub editorial_classification: EditorialClassification,
pub language: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct AudioMetadata {
pub languages: Vec<AudioLanguage>,
pub supplementary: Option<SupplementaryAudio>,
pub role: AudioRole,
pub he_aac: Option<HeAacProfile>,
}
impl AudioMetadata {
pub fn from_es_info(es_info: &[u8]) -> Self {
let mut languages = Vec::new();
let mut supplementary = None;
let mut he_aac = None;
for d in parse_loop(es_info).flatten() {
match d {
AnyDescriptor::Aac(d) if he_aac.is_none() => {
he_aac = d
.extension
.and_then(|e| e.decoded_component_type())
.and_then(he_aac_of_component_type);
}
AnyDescriptor::Iso639Language(d) => {
for e in d.entries {
languages.push(AudioLanguage {
language: e.language_code.as_str().to_ascii_lowercase(),
audio_type: AudioType::from_dvb(e.audio_type),
});
}
}
AnyDescriptor::Extension(x) => {
if let ExtensionBody::SupplementaryAudio(s) = x.body
&& supplementary.is_none()
{
supplementary = Some(SupplementaryAudio {
mix_type: if s.mix_type {
MixType::Independent
} else {
MixType::Dependent
},
editorial_classification: EditorialClassification::from_u8(
s.editorial_classification,
),
language: s
.iso_639_language_code
.map(|l| l.as_str().to_ascii_lowercase()),
});
}
}
_ => {}
}
}
let role = derive_role(&languages, supplementary.as_ref());
Self {
languages,
supplementary,
role,
he_aac,
}
}
}
fn he_aac_of_component_type(ct: AacComponentType) -> Option<HeAacProfile> {
ct.v2.then_some(HeAacProfile::V2)
}
fn derive_role(languages: &[AudioLanguage], sup: Option<&SupplementaryAudio>) -> AudioRole {
let editorial = sup.map(|s| s.editorial_classification);
let lang_is_ad = languages
.iter()
.any(|l| l.language == LANG_AUDIO_DESCRIPTION)
|| sup
.and_then(|s| s.language.as_deref())
.is_some_and(|l| l == LANG_AUDIO_DESCRIPTION);
if languages
.iter()
.any(|l| l.audio_type == AudioType::VisualImpairedCommentary)
|| editorial == Some(EditorialClassification::AudioDescription)
|| lang_is_ad
{
return AudioRole::AudioDescription;
}
if languages
.iter()
.any(|l| l.audio_type == AudioType::HearingImpaired)
|| editorial == Some(EditorialClassification::CleanAudioHearingImpaired)
{
return AudioRole::HearingImpaired;
}
let identified = !languages.is_empty() || sup.is_some();
match editorial {
None | Some(EditorialClassification::MainAudio) if identified => AudioRole::Main,
_ => AudioRole::Unknown,
}
}
impl TrackSpec {
pub fn with_explicit_he_aac(mut self, profile: HeAacProfile) -> crate::error::Result<Self> {
self.config = self.config.with_explicit_he_aac(profile)?;
if let Some(output_hz) = self.config.audio_sample_rate() {
self.timescale = output_hz;
}
Ok(self)
}
pub fn audio_metadata(&self) -> AudioMetadata {
AudioMetadata::from_es_info(&self.es_info_descriptors)
}
}
impl Track {
pub fn with_explicit_he_aac(mut self, profile: HeAacProfile) -> crate::error::Result<Self> {
let from = self.spec.timescale;
self.spec = self.spec.with_explicit_he_aac(profile)?;
let to = self.spec.timescale;
if from != to {
self.samples = self
.samples
.into_iter()
.map(|s| s.rescale_timescale(from, to))
.collect();
self.start_decode_time = u64::try_from(rescale_i64(
i64::try_from(self.start_decode_time).unwrap_or(i64::MAX),
from,
to,
Rounding::Floor,
))
.unwrap_or(u64::MAX);
}
Ok(self)
}
}