From 3ecfaf80ce65c61f6f1626e90c8b2048541b9d78 Mon Sep 17 00:00:00 2001 From: arg274 <4648027+arg274@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:33:52 -0700 Subject: [PATCH 1/2] Parse every EZ CD Audio Converter log format The parser was written against one 10.3.0 log and patched for 6.0.0. A static analysis of how 2.8, 6.0, 7.1, 8.2, 11.5 and 12.4 write their logs now covers every layout they produce, in both the Normal/Range and the Single File/Disc Image profiles. Tracks come from the Details blocks rather than the summary table, so cancelled and failed tracks are aborted, including the 12.4 track whose encoder fails to close and which logs no closing line at all. Both CRC label schemes are read: "CRC" and "CRC verify" up to 6.0, "CRC (test)" and "CRC (copy)" from 7.1. Per-sector errors are read in both forms, one line per flag up to 7.1 and one combined line from 8.2. Data errors are C2 hits and become damaged sectors, synchronization errors become jitter, and CRC errors, sectors whose copy and test reads differ, become inconsistencies. Counts include both passes, as EZ CD counts them, and positions are merged into spans of adjacent sectors. The TOC is built from sectors, which also covers the reduced image log that prints no MSF columns. Pre-emphasis was read from the end of the row, which on 12.4 is the HDCD column, and "De-emphasized" never matched. Settings with an equivalent are now mapped: AccurateCDDA to accurate stream, "Drive read cache" (2.8 to 7.1) inverted to defeat audio cache, the silence deletion lines, and 12.4's Loudness, Peak and Gain normalization, which used to leave normalization unknown. An album line without an artist keeps its title. No read mode re-reads a sector, they differ only in how errors are flagged, so all of them are burst. Tracks run from one index 01 to the next and nothing before the first is read, so detected pre-gaps mean appended except HTOA and undetected ones appended undetected. Versions without the setting are unknown rather than inapplicable, which drew an incorrect gap handling deduction. The 12.4 track CRC covers every sample, silence included, so null samples are used. The encoder is the output format's extension, as EAC and XLD report it, rather than the plugin's description. Checked against the 10.3.0 and 6.0.0 logs, a 12.4 mock, and synthetic 2.8, 7.1, 8.2, 11.5 and 12.4 logs built from the analysis, which parse the same with LF line ends and trailing spaces stripped. Co-Authored-By: Claude Opus 5.5 (1M context) --- cambia-core/src/parser/ezcd_parser.rs | 297 ++++++++++++++++++++------ cambia-core/src/track.rs | 16 ++ 2 files changed, 246 insertions(+), 67 deletions(-) diff --git a/cambia-core/src/parser/ezcd_parser.rs b/cambia-core/src/parser/ezcd_parser.rs index 2e481f9..d6bb576 100644 --- a/cambia-core/src/parser/ezcd_parser.rs +++ b/cambia-core/src/parser/ezcd_parser.rs @@ -1,23 +1,45 @@ +use std::str::FromStr; + use regex::Regex; -use crate::{extract::{Extractor, Quartet, ReleaseInfo, Ripper, TrackExtractor}, integrity::IntegrityChecker, toc::{Toc, TocEntry, TocRaw}, track::{TestAndCopy, TrackEntry}, translate::{Translator, TranslatorCombined}, util::Time}; +use crate::{extract::{Extractor, Gap, Quartet, ReadMode, ReleaseInfo, Ripper, TrackExtractor}, integrity::IntegrityChecker, toc::{Toc, TocEntry, TocRaw}, track::{TestAndCopy, TrackEntry, TrackError, TrackErrorData, TrackErrorRange}, translate::{Translator, TranslatorCombined}, util::Time}; use simple_text_decode::DecodedText; -use super::{Parser, ParsedLog, ParserCombined, ParsedLogCombined, ParserTrack}; +use super::{split_tracks, Parser, ParsedLog, ParserCombined, ParsedLogCombined, ParserTrack}; lazy_static! { static ref RIPPER_VERSION: Regex = Regex::new(r"EZ CD Audio Converter (\S+)").unwrap(); - static ref RELEASE_INFO: Regex = Regex::new(r"(?m)^(?P.+?) - (?P.+?)\s*$").unwrap(); + static ref RELEASE_INFO: Regex = Regex::new(r"(?m)^Log creation date:.*\n\s*(?P<relinfo>.+?)\s*\n\s*Device: ").unwrap(); static ref USED_DRIVE: Regex = Regex::new(r"(?m)^Device:\s*(?:\[[^\]]*\])?\s*(?P<drive>.+?)\s*$").unwrap(); // Neither the mount point nor the trailing firmware is part of the AccurateRip name - static ref DRIVE_TRIM: Regex = Regex::new(r"\s+[0-9A-Za-z.]{4}$").unwrap(); - static ref SETTING: Regex = Regex::new(r"(?m)^(?P<key>[^:\n]+?)\s*:+\s*(?P<value>.+?)\s*$").unwrap(); - - static ref TOC: Regex = Regex::new(r"(?m)^\s*\[.\]\s+(?P<track>\d+)\s+(?P<start>[\d:.]+)\s+(?P<length>[\d:.]+)\s+(?P<start_sector>\d+)\s+(?P<end_sector>\d+)(?P<rest>.*)$").unwrap(); - static ref PREGAP: Regex = Regex::new(r"(?P<time>\d+:\d+\.\d+)").unwrap(); - static ref PREEMPHASIS: Regex = Regex::new(r"(?P<boolean>Yes|No)\s*$").unwrap(); - - static ref TRACKS: Regex = Regex::new(r#"(?m)^\s*(?P<track>\d+)\s+(?P<status>\S+)\s+(?P<copy>[0-9A-Fa-f]{8})(?:\s+(?P<test>[0-9A-Fa-f]{8}))?\s+(?P<errors>\d+(?:\s+\d+)*)\s+"(?P<filename>.*)""#).unwrap(); + static ref DRIVE_TRIM: Regex = Regex::new(r"\s+\S{1,4}$").unwrap(); + + static ref PROFILE: Regex = Regex::new(r"(?m)^Profile\s*: (?P<value>.+?)\s*$").unwrap(); + static ref READ_MODE: Regex = Regex::new(r"(?m)^Read mode\s*: (?P<value>.+?)\s*$").unwrap(); + static ref USE_C2: Regex = Regex::new(r"(?m)^C2 pointers\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref ACCURATE_STREAM: Regex = Regex::new(r"(?m)^AccurateCDDA\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref DRIVE_READ_CACHE: Regex = Regex::new(r"(?m)^Drive read cache\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref VERIFY_AUDIO: Regex = Regex::new(r"(?m)^Verify audio\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref READ_OFFSET: Regex = Regex::new(r"(?m)^Sample offset\s*: (?P<value>[+-]?\d+)").unwrap(); + static ref DETECT_PREGAPS: Regex = Regex::new(r"(?m)^Detect pre-gaps:?\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref CALCULATE_REPLAYGAIN: Regex = Regex::new(r"(?m)^Calculate (?:EBU R 128 \| )?ReplayGain\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref NORMALIZE: Regex = Regex::new(r"(?m)^(?:Apply ReplayGain|Loudness Normalization|Peak Normalization|Gain)\s*: (?P<value>.+?)\s*$").unwrap(); + static ref DELETE_SILENCE: Regex = Regex::new(r"(?m)^Delete (?:leading|trailing) digital silence\s*: (?P<boolean>Enabled|Disabled)").unwrap(); + static ref OUTPUT_FORMAT: Regex = Regex::new(r"(?m)^Output format\s*: \.?(?P<value>\w+)").unwrap(); + static ref VERIFY_COLUMN: Regex = Regex::new(r"(?m)^Track +Status +CRC(?P<verify> \(copy\) +CRC \(test\)| +CRC verify)?").unwrap(); + + static ref TOC: Regex = Regex::new(r"(?m)^ *\[[X ]\] +(?P<track>\d+) +(?:\d+:\d{2}\.\d{2} +\d+:\d{2}\.\d{2} +)?(?P<start_sector>\d+) +(?P<end_sector>\d+)(?P<rest>.*)$").unwrap(); + static ref PREGAP: Regex = Regex::new(r"^ +(?P<time>\d+:\d{2}\.\d{2})").unwrap(); + static ref PREEMPHASIS: Regex = Regex::new(r" (?P<value>No|Yes|De-emphasized)\b").unwrap(); + + static ref DETAILS: Regex = Regex::new(r"(?m)^Details\s*\n-+").unwrap(); + static ref SPLIT_TRACKS: Regex = Regex::new(r"(?m)^Track (\d+)\s*$").unwrap(); + static ref FILENAME: Regex = Regex::new(r#"(?m)^\s+Filename "(?P<value>.*)"\s*$"#).unwrap(); + static ref FINISHED: Regex = Regex::new(r"(?m)^\s+Finished (?:successfully|with errors)\s*$").unwrap(); + static ref TEST_CRC: Regex = Regex::new(r"(?m)^\s+CRC (?:\(test\)|verify)\s*: (?P<value>[0-9A-F]{8})\s*$").unwrap(); + static ref COPY_CRC: Regex = Regex::new(r"(?m)^\s+CRC(?: \(copy\))?\s*: (?P<value>[0-9A-F]{8})\s*$").unwrap(); + static ref SECTOR_ERROR: Regex = Regex::new(r"(?m)^\s+(?:(?P<kind>Read|Data|Synchronization) error at (?P<at>\d+:\d{2}:\d{2})|(?P<pos>\d+:\d{2}:\d{2})(?P<kinds>(?: +(?:Read|Data|Synchronization|CRC) error)+))\s*$").unwrap(); + static ref ERROR_KIND: Regex = Regex::new(r"(Read|Data|Synchronization|CRC) error").unwrap(); } pub struct EzcdParser { @@ -31,12 +53,10 @@ struct EzcdParserSingle { struct EzcdParserTrack { num: u8, - status: String, - copy_crc: String, - test_crc: String, - filename: String, + is_range: bool, pregap: Option<Time>, preemphasis: Option<bool>, + raw: String, } impl EzcdParser { @@ -56,29 +76,62 @@ impl EzcdParserSingle { } } - fn setting(&self, key: &str) -> Option<String> { - SETTING.captures_iter(&self.log) - .find(|captures| captures["key"].trim() == key) - .map(|captures| captures["value"].trim().to_owned()) + fn boolean_matcher(&self, regex: &Regex) -> Quartet { + let captures = regex.captures(&self.log); + match captures { + Some(captures) => { + let value = captures.name("boolean").unwrap().as_str(); + match value { + "Enabled" => Quartet::True, + "Disabled" => Quartet::False, + _ => Quartet::Unknown, + } + }, + None => Quartet::Unknown, + } } - fn boolean_matcher(&self, key: &str) -> Quartet { - match self.setting(key).as_deref() { - Some("Enabled") => Quartet::True, - Some("Disabled") => Quartet::False, - _ => Quartet::Unknown, + fn string_match(&self, regex: &Regex) -> String { + match regex.captures(&self.log) { + Some(val) => val.name("value").unwrap().as_str().trim().to_string(), + None => String::default(), } } + fn optional_match<T>(&self, regex: &Regex) -> Option<T> + where + T: FromStr, { + regex.captures(&self.log).and_then(|val| { + val.name("value").and_then(|v| v.as_str().trim().parse::<T>().ok()) + }) + } + fn toc_extras(&self) -> Vec<(u8, Option<Time>, Option<bool>)> { TOC.captures_iter(&self.log).filter_map(|captures| { let num = captures["track"].parse::<u8>().ok()?; let rest = &captures["rest"]; let pregap = PREGAP.captures(rest).map(|pregap| Time::from_mm_ss_ff(&pregap["time"])); - let preemphasis = PREEMPHASIS.captures(rest).map(|preemphasis| &preemphasis["boolean"] == "Yes"); + let preemphasis = PREEMPHASIS.captures(rest).map(|preemphasis| &preemphasis["value"] != "No"); Some((num, pregap, preemphasis)) }).collect() } + + fn tracks(&self) -> Vec<EzcdParserTrack> { + let details = DETAILS.find(&self.log).map_or(self.log.as_str(), |m| &self.log[m.end()..]); + let is_range = self.string_match(&PROFILE) == "Range"; + let toc_extras = self.toc_extras(); + + split_tracks(details, &SPLIT_TRACKS).into_iter().map(|(num, raw)| { + let extras = toc_extras.iter().find(|(track, _, _)| *track == num); + EzcdParserTrack::new( + num, + is_range, + extras.and_then(|(_, pregap, _)| *pregap), + extras.and_then(|(_, _, preemphasis)| *preemphasis), + raw.to_owned(), + ) + }).collect() + } } impl ParserCombined for EzcdParser { @@ -118,14 +171,15 @@ impl Extractor for EzcdParserSingle { } fn extract_release_info(&self) -> ReleaseInfo { - let relinfo = self.log - .lines() - .skip_while(|line| !line.starts_with("Log creation date")) - .skip(1) - .find(|line| !line.trim().is_empty()); - - match relinfo.and_then(|line| RELEASE_INFO.captures(line)) { - Some(captures) => ReleaseInfo::new(captures["artist"].trim().to_owned(), captures["title"].trim().to_owned()), + let captures = RELEASE_INFO.captures(&self.log); + match captures { + Some(captures) => { + let relinfo = captures.name("relinfo").unwrap().as_str(); + match relinfo.split_once(" - ") { + Some((artist, title)) => ReleaseInfo::new(artist.trim().to_owned(), title.trim().to_owned()), + None => ReleaseInfo::new(String::new(), relinfo.trim().to_owned()), + } + }, None => ReleaseInfo::default(), } } @@ -139,40 +193,107 @@ impl Extractor for EzcdParserSingle { } fn extract_read_offset(&self) -> Option<i16> { - self.setting("Sample offset").and_then(|offset| offset.parse::<i16>().ok()) + self.optional_match::<i16>(&READ_OFFSET) + } + + fn extract_accurate_stream(&self) -> Quartet { + self.boolean_matcher(&ACCURATE_STREAM) + } + + fn extract_defeat_audio_cache(&self) -> Quartet { + match self.boolean_matcher(&DRIVE_READ_CACHE) { + Quartet::True => Quartet::False, + Quartet::False => Quartet::True, + other => other, + } } fn extract_use_c2(&self) -> Quartet { - self.boolean_matcher("C2 pointers") + self.boolean_matcher(&USE_C2) + } + + fn extract_delete_silence(&self) -> Quartet { + let deletes: Vec<bool> = DELETE_SILENCE.captures_iter(&self.log).map(|captures| &captures["boolean"] == "Enabled").collect(); + if deletes.contains(&true) { + Quartet::True + } else if deletes.is_empty() { + Quartet::Unknown + } else { + Quartet::False + } + } + + fn extract_use_null_samples(&self) -> Quartet { + Quartet::True } fn extract_test_and_copy(&self) -> Quartet { - self.boolean_matcher("Verify audio") + match self.boolean_matcher(&VERIFY_AUDIO) { + Quartet::Unknown => match VERIFY_COLUMN.captures(&self.log) { + Some(captures) => if captures.name("verify").is_some() { Quartet::True } else { Quartet::False }, + None => Quartet::Unknown, + }, + other => other, + } } - // 10.3.0 has no Apply line, but gain that was never calculated was never applied + // Image profiles log no gain lines, but gain that was never calculated was never applied fn extract_normalize(&self) -> Quartet { - match (self.boolean_matcher("Apply ReplayGain"), self.boolean_matcher("Calculate ReplayGain")) { - (Quartet::Unknown, Quartet::False) => Quartet::False, - (apply, _) => apply, + let gains: Vec<String> = NORMALIZE.captures_iter(&self.log).map(|captures| captures["value"].to_owned()).collect(); + if gains.iter().any(|gain| gain != "Disabled") { + Quartet::True + } else if !gains.is_empty() { + Quartet::False + } else { + match self.boolean_matcher(&CALCULATE_REPLAYGAIN) { + Quartet::False => Quartet::False, + _ => Quartet::Unknown, + } + } + } + + fn extract_read_mode(&self) -> ReadMode { + match self.string_match(&READ_MODE).as_str() { + "Normal" | "Error detection" | "Error detection (compatibility)" | "Simple error detection" | "Advanced error detection" => ReadMode::Burst, + _ => ReadMode::Unknown, + } + } + + fn extract_gap_handling(&self) -> Gap { + match self.boolean_matcher(&DETECT_PREGAPS) { + Quartet::True => Gap::AppendNoHtoa, + Quartet::False => Gap::AppendUndetected, + _ => Gap::Unknown, } } fn extract_audio_encoder(&self) -> Vec<String> { - match self.setting("Encoder") { - Some(encoder) => vec![encoder], - None => Vec::new(), + let captures = OUTPUT_FORMAT.captures(&self.log); + match captures { + Some(captures) => vec![captures["value"].to_ascii_lowercase()], + None => { + let mut extensions: Vec<String> = self.tracks().iter().filter_map(|track| { + std::path::Path::new(&track.string_match(&FILENAME)) + .extension() + .map(|extension| extension.to_string_lossy().to_ascii_lowercase()) + }).collect(); + extensions.sort(); + extensions.dedup(); + extensions + }, } } fn extract_toc(&self) -> Toc { let entries: Vec<TocEntry> = TOC.captures_iter(&self.log).filter_map(|captures| { + let start_sector = captures["start_sector"].parse::<u32>().ok()?; + let end_sector = captures["end_sector"].parse::<u32>().ok()?; Some(TocEntry::new( captures["track"].parse::<u32>().ok()?, - Time::from_mm_ss_ff(&captures["start"]), - Time::from_mm_ss_ff(&captures["length"]), - captures["start_sector"].parse::<u32>().ok()?, - captures["end_sector"].parse::<u32>().ok()?, + Time::from_frames(start_sector), + Time::from_frames((end_sector + 1).checked_sub(start_sector)?), + start_sector, + end_sector, )) }).collect(); @@ -180,22 +301,7 @@ impl Extractor for EzcdParserSingle { } fn extract_tracks(&self) -> Vec<TrackEntry> { - let toc_extras = self.toc_extras(); - - TRACKS.captures_iter(&self.log).filter_map(|captures| { - let num = captures["track"].parse::<u8>().ok()?; - let extras = toc_extras.iter().find(|(track, _, _)| *track == num); - let track_parser = EzcdParserTrack { - num, - status: captures["status"].to_owned(), - copy_crc: captures["copy"].to_uppercase(), - test_crc: captures.name("test").map(|test| test.as_str().to_uppercase()).unwrap_or_default(), - filename: captures["filename"].to_owned(), - pregap: extras.and_then(|(_, pregap, _)| *pregap), - preemphasis: extras.and_then(|(_, _, preemphasis)| *preemphasis), - }; - Some(track_parser.parse_track()) - }).collect() + self.tracks().iter().map(|track| track.parse_track()).collect() } } @@ -207,6 +313,19 @@ impl Translator for EzcdParserSingle { impl IntegrityChecker for EzcdParserSingle {} +impl EzcdParserTrack { + fn new(num: u8, is_range: bool, pregap: Option<Time>, preemphasis: Option<bool>, raw: String) -> Self { + EzcdParserTrack { num, is_range, pregap, preemphasis, raw } + } + + fn string_match(&self, regex: &Regex) -> String { + match regex.captures(&self.raw) { + Some(val) => val.name("value").unwrap().as_str().trim().to_string(), + None => String::default(), + } + } +} + impl ParserTrack for EzcdParserTrack {} impl TrackExtractor for EzcdParserTrack { @@ -215,15 +334,16 @@ impl TrackExtractor for EzcdParserTrack { } fn extract_is_range(&self) -> bool { - false + self.is_range } fn extract_is_aborted(&self) -> bool { - self.status != "Success" + !FINISHED.is_match(&self.raw) } fn extract_filenames(&self) -> Vec<String> { - if self.filename.is_empty() { Vec::new() } else { vec![self.filename.clone()] } + let filename = self.string_match(&FILENAME); + if filename.is_empty() { Vec::new() } else { vec![filename] } } fn extract_pregap_length(&self) -> Option<Time> { @@ -235,6 +355,49 @@ impl TrackExtractor for EzcdParserTrack { } fn extract_test_and_copy(&self) -> TestAndCopy { - TestAndCopy::new_no_skipzero(self.test_crc.clone(), self.copy_crc.clone()) + TestAndCopy::new_no_skipzero(self.string_match(&TEST_CRC), self.string_match(©_CRC)) + } + + fn extract_errors(&self) -> TrackError { + let mut sectors: Vec<(&str, u32)> = Vec::new(); + + for captures in SECTOR_ERROR.captures_iter(&self.raw) { + match (captures.name("kind"), captures.name("at")) { + (Some(kind), Some(at)) => sectors.push((kind.as_str(), frames(at.as_str()))), + _ => { + let pos = frames(&captures["pos"]); + for kind in ERROR_KIND.captures_iter(captures.name("kinds").unwrap().as_str()) { + sectors.push((kind.get(1).unwrap().as_str(), pos)); + } + }, + } + } + + let errors = |error_type: &str| error_data(sectors.iter().filter(|(kind, _)| *kind == error_type).map(|(_, pos)| *pos).collect()); + + TrackError::new_ezcd(errors("Read"), errors("Synchronization"), errors("Data"), errors("CRC")) } } + +fn frames(msf: &str) -> u32 { + let mut parts = msf.split(':').map(|part| part.parse::<u32>().unwrap_or_default()); + let (m, s, f) = (parts.next().unwrap_or_default(), parts.next().unwrap_or_default(), parts.next().unwrap_or_default()); + (m * 60 + s) * 75 + f +} + +fn error_data(mut positions: Vec<u32>) -> TrackErrorData { + let count = positions.len() as u32; + positions.sort_unstable(); + positions.dedup(); + + let mut spans: Vec<(u32, u32)> = Vec::new(); + for pos in positions { + match spans.last_mut() { + Some((_, end)) if *end + 1 == pos => *end = pos, + _ => spans.push((pos, pos)), + } + } + + let ranges = spans.into_iter().map(|(start, end)| TrackErrorRange::new_from_end(Time::from_frames(start), Time::from_frames(end))).collect(); + TrackErrorData::new(count, ranges) +} diff --git a/cambia-core/src/track.rs b/cambia-core/src/track.rs index b32ce40..9834e78 100644 --- a/cambia-core/src/track.rs +++ b/cambia-core/src/track.rs @@ -237,6 +237,22 @@ impl TrackError { missing_samples: TrackErrorData::new_from_bool(m_s), } } + + pub fn new_ezcd(read: TrackErrorData, jitter_generic: TrackErrorData, damaged_sectors: TrackErrorData, inconsistent_err_sectors: TrackErrorData) -> Self { + TrackError { + read, + skip: TrackErrorData::default(), + jitter_generic, + jitter_edge: TrackErrorData::default(), + jitter_atom: TrackErrorData::default(), + drift: TrackErrorData::default(), + dropped: TrackErrorData::default(), + duplicated: TrackErrorData::default(), + damaged_sectors, + inconsistent_err_sectors, + missing_samples: TrackErrorData::default(), + } + } } impl TrackErrorData { From 45b874684b6492decbd61a3a48cfe31186171367 Mon Sep 17 00:00:00 2001 From: arg274 <4648027+arg274@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:34:41 -0700 Subject: [PATCH 2/2] Name the XLD error arguments after their fields new_xld took abbreviations such as r_c, jg_c and m_s, and XLD's extract_errors kept the same names for its locals. new_eac and new_ezcd take the field names, so new_xld and its caller now do as well. Output is unchanged on all 608 XLD sample logs, ignoring array order. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --- cambia-core/src/parser/xld_parser.rs | 42 ++++++++++++++-------------- cambia-core/src/track.rs | 24 ++++++++-------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/cambia-core/src/parser/xld_parser.rs b/cambia-core/src/parser/xld_parser.rs index 7c755b4..dc61495 100644 --- a/cambia-core/src/parser/xld_parser.rs +++ b/cambia-core/src/parser/xld_parser.rs @@ -446,9 +446,9 @@ impl TrackExtractor for XldParserTrack { fn extract_errors(&self) -> TrackError { let captures_all = ERROR.captures_iter(&self.raw); - let (mut r_c, mut s_c, mut drf_c, mut drp_c, mut dup_c, mut dmg_c, mut inc_c) = (0_u32, 0_u32, 0_u32, 0_u32, 0_u32, 0_u32, 0_u32); - let (mut jg_c, mut je_c, mut ja_c) = (0_u32, 0_u32, 0_u32); - let mut m_s = false; + let (mut read, mut skip, mut drift, mut dropped, mut duplicated, mut damaged_count, mut inconsistent_count) = (0_u32, 0_u32, 0_u32, 0_u32, 0_u32, 0_u32, 0_u32); + let (mut jitter_generic, mut jitter_edge, mut jitter_atom) = (0_u32, 0_u32, 0_u32); + let mut missing_samples = false; for captures in captures_all { let error_type = captures.name("type").unwrap().as_str(); @@ -458,28 +458,28 @@ impl TrackExtractor for XldParserTrack { }; match error_type { - "Read error" => { r_c = count }, - "Skipped (treated as error)" => { s_c = count }, - "Damaged sector count" => { dmg_c = count }, - "Jitter error (maybe fixed)" => { jg_c = count }, - "Edge jitter error (maybe fixed)" => { je_c = count }, - "Atom jitter error (maybe fixed)" => { ja_c = count }, - "Drift error (maybe fixed)" => { drf_c = count }, - "Dropped bytes error (maybe fixed)" => { drp_c = count }, - "Duplicated bytes error (maybe fixed)" => { dup_c = count }, - "Inconsistency in error sectors" => { inc_c = count }, - "Missing samples" => { m_s = true }, + "Read error" => { read = count }, + "Skipped (treated as error)" => { skip = count }, + "Damaged sector count" => { damaged_count = count }, + "Jitter error (maybe fixed)" => { jitter_generic = count }, + "Edge jitter error (maybe fixed)" => { jitter_edge = count }, + "Atom jitter error (maybe fixed)" => { jitter_atom = count }, + "Drift error (maybe fixed)" => { drift = count }, + "Dropped bytes error (maybe fixed)" => { dropped = count }, + "Duplicated bytes error (maybe fixed)" => { duplicated = count }, + "Inconsistency in error sectors" => { inconsistent_count = count }, + "Missing samples" => { missing_samples = true }, _ => {} } } // Damaged sector positions - let mut dmg_r: Vec<TrackErrorRange> = Vec::new(); + let mut damaged_ranges: Vec<TrackErrorRange> = Vec::new(); if let Some(c) = DAMAGED_SECTORS.captures(&self.raw) { let positions = ERROR_TIME.captures_iter(c.get(0).unwrap().as_str()); for position in positions { - dmg_r.push( + damaged_ranges.push( TrackErrorRange::new( Time::from_mm_ss_cs(position.name("time").unwrap().as_str()), Time::from_ss("0") @@ -487,15 +487,15 @@ impl TrackExtractor for XldParserTrack { ); } } - let dmg_d = TrackErrorData::new(dmg_c, dmg_r); + let damaged_sectors = TrackErrorData::new(damaged_count, damaged_ranges); // Suspicious positions - let mut inc_r: Vec<TrackErrorRange> = Vec::new(); + let mut inconsistent_ranges: Vec<TrackErrorRange> = Vec::new(); if let Some(c) = SUSPICIOUS_POSITIONS.captures(&self.raw) { let positions = ERROR_TIME.captures_iter(c.get(0).unwrap().as_str()); for position in positions { - inc_r.push( + inconsistent_ranges.push( TrackErrorRange::new( Time::from_mm_ss_cs(position.name("time").unwrap().as_str()), Time::from_ss("0") @@ -503,9 +503,9 @@ impl TrackExtractor for XldParserTrack { ); } } - let inc_d = TrackErrorData::new(inc_c, inc_r); + let inconsistent_err_sectors = TrackErrorData::new(inconsistent_count, inconsistent_ranges); - TrackError::new_xld(r_c, s_c, jg_c, je_c, ja_c, drf_c, drp_c, dup_c, dmg_d, inc_d, m_s) + TrackError::new_xld(read, skip, jitter_generic, jitter_edge, jitter_atom, drift, dropped, duplicated, damaged_sectors, inconsistent_err_sectors, missing_samples) } fn extract_ar_info(&self) -> Vec<AccurateRipUnit> { diff --git a/cambia-core/src/track.rs b/cambia-core/src/track.rs index 9834e78..8494bc0 100644 --- a/cambia-core/src/track.rs +++ b/cambia-core/src/track.rs @@ -222,19 +222,19 @@ impl TrackError { } #[allow(clippy::too_many_arguments)] - pub fn new_xld(r_c: u32, s_c: u32, jg_c: u32, je_c: u32, ja_c: u32, drf_c: u32, drp_c: u32, dup_c: u32, dmg_d: TrackErrorData, inc_d: TrackErrorData, m_s: bool) -> Self { + pub fn new_xld(read: u32, skip: u32, jitter_generic: u32, jitter_edge: u32, jitter_atom: u32, drift: u32, dropped: u32, duplicated: u32, damaged_sectors: TrackErrorData, inconsistent_err_sectors: TrackErrorData, missing_samples: bool) -> Self { TrackError { - read: TrackErrorData::new_from_count(r_c), - skip: TrackErrorData::new_from_count(s_c), - jitter_generic: TrackErrorData::new_from_count(jg_c), - jitter_edge: TrackErrorData::new_from_count(je_c), - jitter_atom: TrackErrorData::new_from_count(ja_c), - drift: TrackErrorData::new_from_count(drf_c), - dropped: TrackErrorData::new_from_count(drp_c), - duplicated: TrackErrorData::new_from_count(dup_c), - damaged_sectors: dmg_d, - inconsistent_err_sectors: inc_d, - missing_samples: TrackErrorData::new_from_bool(m_s), + read: TrackErrorData::new_from_count(read), + skip: TrackErrorData::new_from_count(skip), + jitter_generic: TrackErrorData::new_from_count(jitter_generic), + jitter_edge: TrackErrorData::new_from_count(jitter_edge), + jitter_atom: TrackErrorData::new_from_count(jitter_atom), + drift: TrackErrorData::new_from_count(drift), + dropped: TrackErrorData::new_from_count(dropped), + duplicated: TrackErrorData::new_from_count(duplicated), + damaged_sectors, + inconsistent_err_sectors, + missing_samples: TrackErrorData::new_from_bool(missing_samples), } }