Simplify labels API: one call, labels on streams or nothing

labels::apply(session, udf, titles) does everything internally.
Disc::scan() is one line: crate::labels::apply().
No intermediate variables, no fallback logic in disc.rs.

App reads disc.titles[].streams — labels already applied if
disc had config files, empty otherwise. MPLS data always there.
This commit is contained in:
MattJackson
2026-04-07 18:46:29 -07:00
parent 7f7a66e039
commit 8e7778c572
2 changed files with 65 additions and 31 deletions
+2 -3
View File
@@ -421,9 +421,8 @@ impl Disc {
// Step 4: Read disc title from META/DL/bdmt_eng.xml // Step 4: Read disc title from META/DL/bdmt_eng.xml
let meta_title = Self::read_meta_title(session, &udf_fs); let meta_title = Self::read_meta_title(session, &udf_fs);
// Step 5: Extract stream labels from disc config files // Step 5: Enhance streams with disc config file labels (if available)
let disc_labels = crate::labels::extract(session, &udf_fs); crate::labels::apply(session, &udf_fs, &mut titles);
Self::apply_disc_labels(&mut titles, &disc_labels);
// JAR labels (for playlist purpose markers only, not stream labels) // JAR labels (for playlist purpose markers only, not stream labels)
let jar_labels = Self::read_jar_labels(session, &udf_fs); let jar_labels = Self::read_jar_labels(session, &udf_fs);
+63 -28
View File
@@ -2,12 +2,8 @@
//! //!
//! Searches the disc UDF filesystem for known config files that contain //! Searches the disc UDF filesystem for known config files that contain
//! stream labels (language, purpose, codec, forced flags). Four formats //! stream labels (language, purpose, codec, forced flags). Four formats
//! supported, tried in order: //! supported, tried in order. If found, labels are applied directly
//! //! to the title streams. If not found, streams keep MPLS data as-is.
//! 1. `language_streams.txt` — Warner CTRM CSV format
//! 2. `menu_base.prop` — Warner CTRM properties format
//! 3. `streamproperties.xml` + `playbackconfig.xml` — Criterion XML format
//! 4. `bluray_project.bin` — Pixelogic binary format
mod language_streams; mod language_streams;
mod menu_base; mod menu_base;
@@ -16,6 +12,7 @@ mod bluray_project;
use crate::drive::DriveSession; use crate::drive::DriveSession;
use crate::udf::UdfFs; use crate::udf::UdfFs;
use crate::disc::{Title, Stream};
/// A stream label extracted from disc config files. /// A stream label extracted from disc config files.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -46,43 +43,81 @@ pub enum StreamLabelType {
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum LabelPurpose { pub enum LabelPurpose {
/// Normal dialogue track
Normal, Normal,
/// Audio commentary
Commentary, Commentary,
/// Descriptive audio (visually impaired)
Descriptive, Descriptive,
/// Music score only
Score, Score,
/// In-movie experience
Ime, Ime,
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum LabelQualifier { pub enum LabelQualifier {
None, None,
/// Subtitles for deaf and hard of hearing
Sdh, Sdh,
/// Descriptive service
DescriptiveService, DescriptiveService,
/// Forced/narrative subtitle
Forced, Forced,
} }
/// Try all parsers in order, return first successful result. /// Search disc for config files, extract labels, apply to streams.
pub fn extract(session: &mut DriveSession, udf: &UdfFs) -> Vec<StreamLabel> { /// If no config files found, streams are left unchanged.
// Try each format in order pub fn apply(session: &mut DriveSession, udf: &UdfFs, titles: &mut [Title]) {
if let Some(labels) = language_streams::parse(session, udf) { let labels = extract(session, udf);
return labels; if labels.is_empty() { return; }
}
if let Some(labels) = menu_base::parse(session, udf) { for title in titles.iter_mut() {
return labels; let mut audio_idx: u16 = 0;
} let mut sub_idx: u16 = 0;
if let Some(labels) = stream_properties::parse(session, udf) {
return labels; for stream in &mut title.streams {
} match stream {
if let Some(labels) = bluray_project::parse(session, udf) { Stream::Audio(a) => {
return labels; 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.region.is_empty() {
parts.push(format!("({})", label.region));
}
if !label.codec_hint.is_empty()
&& !matches!(label.codec_hint.as_str(), "MLP" | "AC3" | "DTS")
{
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> {
if let Some(labels) = language_streams::parse(session, udf) { return labels; }
if let Some(labels) = menu_base::parse(session, udf) { return labels; }
if let Some(labels) = stream_properties::parse(session, udf) { return labels; }
if let Some(labels) = bluray_project::parse(session, udf) { return labels; }
Vec::new() Vec::new()
} }