From 2984959fe3e3b29c0924514d795ec6bd890e1d6c Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Sun, 10 May 2026 12:36:05 -0700 Subject: [PATCH] labels: distinguish "parser detected" from "parser succeeded" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/labels/mod.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/labels/mod.rs b/src/labels/mod.rs index 409f440..602f390 100644 --- a/src/labels/mod.rs +++ b/src/labels/mod.rs @@ -289,21 +289,37 @@ fn extract(reader: &mut dyn SectorReader, udf: &UdfFs) -> Vec { #[doc(hidden)] pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis { 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 { if detect(udf) { tracing::info!(parser = name, "label parser matched"); + parsers_detected.push(name); if let Some(labels) = parse(reader, udf) { return LabelAnalysis { parser: Some(name), + parsers_detected, jar_inventory: inventory, 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 { parser: None, + parsers_detected, jar_inventory: inventory, labels: Vec::new(), } @@ -314,12 +330,20 @@ pub fn analyze(reader: &mut dyn SectorReader, udf: &UdfFs) -> LabelAnalysis { #[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`. + /// "ctrm") AND emitted labels. `None` means either no parser + /// 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>, + /// 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 - /// and sorted. Helps spot unknown authoring formats when `parser` - /// is `None`. + /// and sorted. Helps spot unknown authoring formats when no + /// parser detected. pub jar_inventory: Vec, /// Raw labels emitted by the matched parser (empty if `parser` is /// `None`).