labels: distinguish "parser detected" from "parser succeeded"

Adds `parsers_detected: Vec<&'static str>` to `LabelAnalysis`. Records
every parser whose discriminator matched, regardless of whether its
parse() then returned Some/None.

Why: when `parser=None` we currently can't tell apart:
  (a) no parser recognized this disc — missing parser, candidate for a
      new module
  (b) a parser recognized it but parse() returned None / empty —
      capture truncated, or genuine empty authoring data, or a parser
      bug

Surfaced concretely on the 11-disc capture session 2026-05-10:
disc-04 had `bluray_project.bin` in jar_inventory (pixelogic detect()
returned true) but parse() returned None because file content was
past the 1 GB capture window. Old API: parser=None — looked like
"missing parser." New API: detected=[pixelogic], parser=None — clearly
"capture problem, not a parser gap."

The tracing log line on the no-parser-emitted-labels path now
distinguishes the two cases too.

No behavior change to production rip path. extract() is unchanged;
only the diagnostic analyze() returns the richer result.
This commit is contained in:
2026-05-10 12:36:05 -07:00
parent 993d4bf7e1
commit 2984959fe3
+29 -5
View File
@@ -289,21 +289,37 @@ fn extract(reader: &mut dyn SectorReader, udf: &UdfFs) -> Vec<StreamLabel> {
#[doc(hidden)] #[doc(hidden)]
pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis { pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis {
let inventory = jar_inventory(udf); let inventory = jar_inventory(udf);
// Record every parser whose discriminator matched — even if its
// parse step then returned None — so the analyzer can distinguish
// "no parser recognized this disc" from "parser recognized it but
// couldn't read the file" (e.g. content past a truncated capture)
// or "parser ran but produced no labels."
let mut parsers_detected: Vec<&'static str> = Vec::new();
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"); tracing::info!(parser = name, "label parser matched");
parsers_detected.push(name);
if let Some(labels) = parse(reader, udf) { if let Some(labels) = parse(reader, udf) {
return LabelAnalysis { return LabelAnalysis {
parser: Some(name), parser: Some(name),
parsers_detected,
jar_inventory: inventory, jar_inventory: inventory,
labels, labels,
}; };
} }
} }
} }
tracing::info!("no label parser matched"); if parsers_detected.is_empty() {
tracing::info!("no label parser matched");
} else {
tracing::info!(
detected = ?parsers_detected,
"label parsers detected but produced no labels"
);
}
LabelAnalysis { LabelAnalysis {
parser: None, parser: None,
parsers_detected,
jar_inventory: inventory, jar_inventory: inventory,
labels: Vec::new(), labels: Vec::new(),
} }
@@ -314,12 +330,20 @@ pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LabelAnalysis { pub struct LabelAnalysis {
/// Which parser matched ("paramount" / "criterion" / "pixelogic" / /// Which parser matched ("paramount" / "criterion" / "pixelogic" /
/// "ctrm"), or `None` if no parser matched and the labels code /// "ctrm") AND emitted labels. `None` means either no parser
/// would have fallen through to `fill_defaults`. /// recognized the disc, OR a parser recognized it but its parse
/// step returned None (file unreadable, no parseable tokens). Use
/// `parsers_detected` to disambiguate.
pub parser: Option<&'static str>, pub parser: Option<&'static str>,
/// Every parser whose discriminator matched, in priority order.
/// Distinguishes "we recognized this disc but couldn't extract
/// labels" from "we don't recognize this disc at all" — the
/// former points at a parser bug or a truncated capture, the
/// latter points at a missing parser.
pub parsers_detected: Vec<&'static str>,
/// Filenames found under any `/BDMV/JAR/*/` subdirectory, deduped /// Filenames found under any `/BDMV/JAR/*/` subdirectory, deduped
/// and sorted. Helps spot unknown authoring formats when `parser` /// and sorted. Helps spot unknown authoring formats when no
/// is `None`. /// parser detected.
pub jar_inventory: Vec<String>, pub jar_inventory: Vec<String>,
/// Raw labels emitted by the matched parser (empty if `parser` is /// Raw labels emitted by the matched parser (empty if `parser` is
/// `None`). /// `None`).