mod paramount;
mod criterion;
mod pixelogic;
mod ctrm;
pub mod vocab;
use crate::drive::DriveSession;
use crate::udf::UdfFs;
use crate::disc::{Title, Stream};
#[derive(Debug, Clone)]
pub struct StreamLabel {
pub stream_number: u16,
pub stream_type: StreamLabelType,
pub language: String,
pub name: String,
pub purpose: LabelPurpose,
pub qualifier: LabelQualifier,
pub codec_hint: String,
pub variant: String,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StreamLabelType {
Audio,
Subtitle,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LabelPurpose {
Normal,
Commentary,
Descriptive,
Score,
Ime,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LabelQualifier {
None,
Sdh,
DescriptiveService,
Forced,
}
type DetectFn = fn(&UdfFs) -> bool;
type ParseFn = fn(&mut DriveSession, &UdfFs) -> Option<Vec<StreamLabel>>;
const PARSERS: &[(&str, DetectFn, ParseFn)] = &[
("paramount", paramount::detect, paramount::parse),
("criterion", criterion::detect, criterion::parse),
("pixelogic", pixelogic::detect, pixelogic::parse),
("ctrm", ctrm::detect, ctrm::parse),
];
pub fn apply(session: &mut DriveSession, udf: &UdfFs, titles: &mut [Title]) {
let labels = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
extract(session, udf)
})).unwrap_or_default();
if labels.is_empty() { return; }
for title in titles.iter_mut() {
let mut audio_idx: u16 = 0;
let mut sub_idx: u16 = 0;
for stream in &mut title.streams {
match stream {
Stream::Audio(a) => {
audio_idx += 1;
if let Some(label) = labels.iter().find(|l|
l.stream_type == StreamLabelType::Audio && l.stream_number == audio_idx
) {
let mut parts = Vec::new();
match label.purpose {
LabelPurpose::Commentary => parts.push("Commentary".to_string()),
LabelPurpose::Descriptive => parts.push("Descriptive Audio".to_string()),
LabelPurpose::Score => parts.push("Score".to_string()),
LabelPurpose::Ime => parts.push("IME".to_string()),
LabelPurpose::Normal => {}
}
if !label.variant.is_empty() {
parts.push(format!("({})", label.variant));
}
if !label.codec_hint.is_empty() {
parts.push(label.codec_hint.clone());
}
if !parts.is_empty() {
a.label = parts.join(" ");
} else if !label.name.is_empty() {
a.label = label.name.clone();
}
}
}
Stream::Subtitle(s) => {
sub_idx += 1;
if let Some(label) = labels.iter().find(|l|
l.stream_type == StreamLabelType::Subtitle && l.stream_number == sub_idx
) {
if label.qualifier == LabelQualifier::Forced {
s.forced = true;
}
}
}
_ => {}
}
}
}
}
fn extract(session: &mut DriveSession, udf: &UdfFs) -> Vec<StreamLabel> {
for (_name, detect, parse) in PARSERS {
if detect(udf) {
if let Some(labels) = parse(session, udf) {
return labels;
}
}
}
Vec::new()
}
pub(crate) fn jar_file_exists(udf: &UdfFs, filename: &str) -> bool {
find_jar_file(udf, filename).is_some()
}
pub(crate) fn find_jar_file(udf: &UdfFs, filename: &str) -> Option<String> {
let jar_dir = udf.find_dir("/BDMV/JAR")?;
for entry in &jar_dir.entries {
if entry.is_dir {
let path = format!("/BDMV/JAR/{}/{}", entry.name, filename);
for child in &entry.entries {
if !child.is_dir && child.name.eq_ignore_ascii_case(filename) {
return Some(path);
}
}
}
}
None
}
pub(crate) fn read_jar_file(session: &mut DriveSession, udf: &UdfFs, filename: &str) -> Option<Vec<u8>> {
let path = find_jar_file(udf, filename)?;
udf.read_file(session, &path).ok().filter(|d| !d.is_empty())
}