Files
qbz/crates/qbz-rip/examples/rip_one.rs
T
vicrodh b69a058bc3 feat(rip): a provenance log and the cover, beside the tracks
qbz-rip.log is deliberately NOT an EAC log. An EAC or XLD log is trusted for
what it PROVES — test-and-copy, a drive read-offset correction, an AccurateRip
cross-check against thousands of other rips of the same disc — and QBZ does
none of the three. A file that imitated the format without the method would be
worse than no file: it invites a claim this ripper cannot back, and the people
who read rip logs are exactly the people who check.

So the header says what it is and is not in the first block, not in small
print. What remains is true and useful: which disc (DiscID and TOC fingerprint,
both recomputable by anyone holding it), which drive, and per track the audio
md5 that already lives in the file's own STREAMINFO — verifiable with flac -t
without QBZ. Verified end to end: metaflac --show-md5sum prints exactly what
the log claims, and flac -t passes on every track.

"0 read errors" is a real promise and is stated as one: an unreadable sector
ABORTS the rip rather than being written as silence, which is stronger than a
burst-mode EAC rip. Where the OS did the reading there is no drive to name, and
the log says that instead of printing a volume as though it were hardware.

A partial rip declares itself partial: three tracks with no such line read as
the whole album.

cover.jpg keeps the source's EXTENSION — renaming a PNG to .jpg is how a
picture stops opening. Neither sidecar can fail the rip: the audio is the
deliverable, and a missing extra is a blemish on a folder of correct files.

Five tests, including that the log states all three things it does not do, that
the logged digest IS the file's own, and the calendar arithmetic that is the
only thing here that could be quietly wrong for years.

cc
2026-08-21 18:57:42 -06:00

57 lines
2.0 KiB
Rust

//! Rip ONE track off the disc in the drive, tagged, into a directory.
//! `cargo run -p qbz-rip --example rip_one -- <track#> <destdir>`
fn main() {
let mut a = std::env::args().skip(1);
let want: u32 = a.next().expect("track number").parse().unwrap();
let dest = std::path::PathBuf::from(a.next().expect("destination"));
let dev = qbz_disc::list_devices().into_iter().next().expect("a drive");
let toc = qbz_disc::read_toc(&dev).expect("toc");
let starts: Vec<u32> = toc.audio_tracks().map(|t| t.start_lsn).collect();
let id = qbz_disc::discid::disc_id(&starts, toc.leadout_lsn).expect("disc id");
println!("disc id {id}");
let t = toc
.audio_tracks()
.find(|t| t.number as u32 == want)
.expect("that track");
println!("track {} — {} sectors, {}s", t.number, t.sectors, t.duration_secs());
let plan = qbz_rip::RipPlan {
destination: dest,
album: "Fear Inoculum".into(),
album_artist: "Tool".into(),
year: Some(2019),
tracks: vec![qbz_rip::RipTrack {
number: t.number as u32,
title: "Chocolate Chip Trip".into(),
artist: "Tool".into(),
source: qbz_rip::RipSource::Cd {
device: dev.clone(),
start_lsn: t.start_lsn,
sectors: t.sectors,
},
}],
disc_id: Some(id.clone()),
toc_fingerprint: Some(toc.fingerprint()),
disc_track_count: toc.audio_tracks().count(),
cover: None,
};
let started = std::time::Instant::now();
let mut last = -1i32;
let out = qbz_rip::rip(&plan, |p| {
let pct = (p.fraction * 100.0) as i32;
if pct / 10 != last / 10 {
println!(" {pct}%");
last = pct;
}
true
})
.expect("rip");
println!("wrote {:?} in {:.1}s", out, started.elapsed().as_secs_f32());
for p in &out {
let md = std::fs::metadata(p).unwrap();
println!(" {} bytes", md.len());
}
}