labels: expose analyze() for corpus regression tooling

Promotes `mod labels` to `pub mod labels` and adds `analyze()` plus
`LabelAnalysis` (both `#[doc(hidden)]`) so an out-of-tree diagnostic
binary (freemkv-tools labels-analyze) can introspect which BD-J parser
matched a given disc, what JAR files the discriminators saw, and what
labels came out — without going through the production `apply()` path
that mutates DiscTitles.

Also adds `tracing::info!(parser = name, "label parser matched")` /
"no label parser matched" inside `extract()`. Dev-only signal: users
get the same seamless behavior; developers can finally tell whether a
disc hit a real parser or fell through to the codec-name fallback in
fill_defaults().

The new `jar_inventory()` helper deduplicates and sorts filenames
under any `/BDMV/JAR/<x>/` subdirectory — same plumbing the existing
`jar_file_exists()` discriminators use, just enumerated rather than
predicate-tested. Used by `analyze()` to surface unrecognized
parser-source files when no parser matches, which is the input to
"do we need a new parser?" triage.

No behavior change to the production label path. `apply()` and
`extract()` remain functionally identical; the new public surface
exists alongside.
This commit is contained in:
MattJackson
2026-05-10 07:42:45 -07:00
parent e1c938b82a
commit e5989c8270
2 changed files with 71 additions and 2 deletions
+70 -1
View File
@@ -268,16 +268,85 @@ fn generate_audio_label(
} }
fn extract(reader: &mut dyn SectorReader, udf: &UdfFs) -> Vec<StreamLabel> { fn extract(reader: &mut dyn SectorReader, udf: &UdfFs) -> Vec<StreamLabel> {
for (_name, detect, parse) in PARSERS { for (name, detect, parse) in PARSERS {
if detect(udf) { if detect(udf) {
tracing::info!(parser = name, "label parser matched");
if let Some(labels) = parse(reader, udf) { if let Some(labels) = parse(reader, udf) {
return labels; return labels;
} }
} }
} }
tracing::info!("no label parser matched");
Vec::new() Vec::new()
} }
/// Diagnostic introspection — returns the parser that matched, the
/// labels it emitted, and the inventory of files under `/BDMV/JAR/*/`
/// that the discriminators looked at. Intended for `freemkv-tools
/// labels-analyze` and corpus regression tooling, not production code
/// paths. The matching/parsing logic is identical to [`extract`]; only
/// the return shape is richer.
#[doc(hidden)]
pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis {
let inventory = jar_inventory(udf);
for (name, detect, parse) in PARSERS {
if detect(udf) {
tracing::info!(parser = name, "label parser matched");
if let Some(labels) = parse(reader, udf) {
return LabelAnalysis {
parser: Some(name),
jar_inventory: inventory,
labels,
};
}
}
}
tracing::info!("no label parser matched");
LabelAnalysis {
parser: None,
jar_inventory: inventory,
labels: Vec::new(),
}
}
/// Result of [`analyze`].
#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct LabelAnalysis {
/// Which parser matched ("paramount" / "criterion" / "pixelogic" /
/// "ctrm"), or `None` if no parser matched and the labels code
/// would have fallen through to `fill_defaults`.
pub parser: Option<&'static str>,
/// Filenames found under any `/BDMV/JAR/*/` subdirectory, deduped
/// and sorted. Helps spot unknown authoring formats when `parser`
/// is `None`.
pub jar_inventory: Vec<String>,
/// Raw labels emitted by the matched parser (empty if `parser` is
/// `None`).
pub labels: Vec<StreamLabel>,
}
/// List filenames found under any `/BDMV/JAR/<x>/` subdirectory of
/// the disc. Deduped, sorted. Returns an empty vec if no JAR dir is
/// present.
fn jar_inventory(udf: &UdfFs) -> Vec<String> {
let Some(jar_dir) = udf.find_dir("/BDMV/JAR") else {
return Vec::new();
};
let mut out: Vec<String> = Vec::new();
for entry in &jar_dir.entries {
if entry.is_dir {
for child in &entry.entries {
if !child.is_dir && !out.contains(&child.name) {
out.push(child.name.clone());
}
}
}
}
out.sort();
out
}
// ── Shared helpers ───────────────────────────────────────────────────────── // ── Shared helpers ─────────────────────────────────────────────────────────
/// Check if a file exists in any BDMV/JAR subdirectory. /// Check if a file exists in any BDMV/JAR subdirectory.
+1 -1
View File
@@ -84,7 +84,7 @@ pub(crate) mod identity;
pub(crate) mod ifo; pub(crate) mod ifo;
pub(crate) mod io; pub(crate) mod io;
pub mod keydb; pub mod keydb;
pub(crate) mod labels; pub mod labels;
pub(crate) mod mpls; pub(crate) mod mpls;
pub mod mux; pub mod mux;
pub mod pes; pub mod pes;