use std::fmt::{Display, Formatter};
use arib::component::{
AspectRatio, AudioAccessibility, AudioComponentType, AudioMode, SamplingRate,
};
use arib::mmt::descriptor::{
TransferCharacteristics, VideoComponentDescriptor, VideoFrameRate, VideoResolution,
};
use arib::ts::descriptor::{ComponentType, VideoFormat};
#[derive(Clone, Debug)]
pub struct Stream {
pub id: u16,
pub component_tag: Option<u16>,
pub kind: Kind,
pub codec: String,
pub details: Vec<String>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Kind {
Video,
Audio,
Subtitle,
Data,
}
impl Display for Kind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Video => "Video",
Self::Audio => "Audio",
Self::Subtitle => "Subtitle",
Self::Data => "Data",
})
}
}
#[derive(Clone, Debug)]
pub struct Component {
pub component_tag: u16,
pub details: Vec<String>,
}
pub fn of_stream_type(stream_type: u8, component_tag: Option<u16>) -> (Kind, String) {
let (kind, codec) = match stream_type {
0x01 => (Kind::Video, "MPEG-1"),
0x02 => (Kind::Video, "MPEG-2"),
0x1B => (Kind::Video, "H.264"),
0x24 => (Kind::Video, "HEVC"),
0x03 => (Kind::Audio, "MPEG-1 Audio"),
0x04 => (Kind::Audio, "MPEG-2 Audio"),
0x0F => (Kind::Audio, "AAC (ADTS)"),
0x11 => (Kind::Audio, "AAC (LATM)"),
0x06 => match component_tag {
Some(0x30..=0x37) => (Kind::Subtitle, "ARIB caption"),
Some(0x38..=0x3F) => (Kind::Subtitle, "ARIB superimpose"),
_ => (Kind::Data, "private PES"),
},
0x0D => (Kind::Data, "DSM-CC"),
0x05 => (Kind::Data, "private sections"),
stream_type => return (Kind::Data, format!("stream type {stream_type:#04X}")),
};
(kind, codec.to_owned())
}
pub fn of_asset_type(asset_type: [u8; 4]) -> (Kind, String) {
let (kind, codec) = match &asset_type {
b"hev1" | b"hvc1" => (Kind::Video, "HEVC"),
b"mp4a" => (Kind::Audio, "AAC (LATM)"),
b"stpp" => (Kind::Subtitle, "TTML"),
b"aapp" => (Kind::Data, "application"),
_ => {
return (
Kind::Data,
String::from_utf8_lossy(&asset_type).into_owned(),
);
}
};
(kind, codec.to_owned())
}
pub fn video_of_component_type(component_type: ComponentType) -> Vec<String> {
let ComponentType::Video {
format,
aspect_ratio,
} = component_type
else {
return vec![];
};
[
known(format, matches!(format, VideoFormat::Unknown(_))),
aspect_ratio_of(aspect_ratio),
]
.into_iter()
.flatten()
.collect()
}
pub fn video_of_mmt(descriptor: &VideoComponentDescriptor) -> Vec<String> {
let resolution = descriptor.video_resolution;
let lines = !matches!(
resolution,
VideoResolution::Unspecified | VideoResolution::Unknown(_)
);
let scan = if descriptor.video_scan_flag { "p" } else { "i" };
let frame_rate = descriptor.video_frame_rate;
let transfer = descriptor.video_transfer_characteristics;
[
lines.then(|| format!("{resolution}{scan}")),
aspect_ratio_of(descriptor.video_aspect_ratio),
known(
frame_rate,
matches!(
frame_rate,
VideoFrameRate::Unspecified | VideoFrameRate::Unknown(_)
),
),
known(
transfer,
matches!(
transfer,
TransferCharacteristics::Unspecified | TransferCharacteristics::Unknown(_)
),
),
]
.into_iter()
.flatten()
.collect()
}
fn aspect_ratio_of(aspect_ratio: AspectRatio) -> Option<String> {
known(
aspect_ratio,
matches!(
aspect_ratio,
AspectRatio::Unspecified | AspectRatio::Unknown(_)
),
)
}
fn known(value: impl Display, unknown: bool) -> Option<String> {
(!unknown).then(|| value.to_string())
}
pub struct Audio<'a> {
pub component_type: AudioComponentType,
pub sampling_rate: SamplingRate,
pub main_component_flag: bool,
pub iso_639_language_code: [u8; 3],
pub iso_639_language_code_2: Option<[u8; 3]>,
pub text: &'a str,
}
impl Audio<'_> {
pub fn details(&self) -> Vec<String> {
let mode = match self.component_type.mode {
AudioMode::Mono => "mono".to_owned(),
AudioMode::DualMono => "dual mono".to_owned(),
AudioMode::Stereo => "stereo".to_owned(),
AudioMode::Mode3_2_1 => "5.1ch".to_owned(),
AudioMode::Mode5_2_1 => "7.1ch".to_owned(),
AudioMode::Mode3_3_3_5_2_3_3_0_0_2 => "22.2ch".to_owned(),
mode => mode.to_string(),
};
let sampling_rate = self.sampling_rate;
let languages = [
Some(self.iso_639_language_code),
self.iso_639_language_code_2,
]
.into_iter()
.flatten()
.map(|code| String::from_utf8_lossy(&code).into_owned())
.collect::<Vec<_>>()
.join("+");
let accessibility = self.component_type.accessibility;
[
Some(mode),
known(sampling_rate, sampling_rate.hz().is_none()),
(!languages.is_empty()).then_some(languages),
self.main_component_flag.then(|| "main".to_owned()),
known(
accessibility,
matches!(
accessibility,
AudioAccessibility::None | AudioAccessibility::Unknown(_)
),
),
self.component_type
.dialog_control
.then(|| "dialogue control".to_owned()),
(!self.text.is_empty()).then(|| format!("\"{}\"", self.text)),
]
.into_iter()
.flatten()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn describes_video_by_its_component_type() {
let video = |format, aspect_ratio| {
video_of_component_type(ComponentType::Video {
format,
aspect_ratio,
})
};
assert_eq!(
video(VideoFormat::I1080, AspectRatio::SixteenByNine),
["1080i", "16:9"]
);
assert_eq!(
video(VideoFormat::P2160, AspectRatio::FourByThree),
["2160p", "4:3"]
);
assert!(video_of_component_type(ComponentType::Other(0x03)).is_empty());
}
#[test]
fn describes_audio_by_its_component() {
let audio = Audio {
component_type: AudioComponentType::from(0x03),
sampling_rate: SamplingRate::Khz48,
main_component_flag: true,
iso_639_language_code: *b"jpn",
iso_639_language_code_2: None,
text: "日本語",
};
assert_eq!(
audio.details(),
["stereo", "48 kHz", "jpn", "main", "\"日本語\""]
);
let dual = Audio {
component_type: AudioComponentType::from(0x02 | 0b0100_0000),
sampling_rate: SamplingRate::Khz44_1,
main_component_flag: false,
iso_639_language_code: *b"jpn",
iso_639_language_code_2: Some(*b"eng"),
text: "",
};
assert_eq!(
dual.details(),
[
"dual mono",
"44.1 kHz",
"jpn+eng",
"for the hearing impaired"
]
);
}
#[test]
fn names_the_other_audio_modes_as_the_standards_do() {
let mode = |component_type| {
Audio {
component_type: AudioComponentType::from(component_type),
sampling_rate: SamplingRate::Unknown(0),
main_component_flag: false,
iso_639_language_code: *b"jpn",
iso_639_language_code_2: None,
text: "",
}
.details()
.remove(0)
};
assert_eq!(mode(0x08), "3/2");
assert_eq!(mode(0x0A), "3/3.1");
assert_eq!(mode(0x0B), "2/0/0-2/0/2-0.1");
}
}