feat(rip): rip a CD to tagged FLAC, bit-exact and seekable
New crate `qbz-rip`: CD audio in, tagged FLAC out. Frontend-agnostic — it
knows discs, samples and files, and reports progress through a callback so a
UI can drive it without this crate depending on one.
`flacenc`, pure Rust: no libFLAC to build, nothing extra to bundle into
Flatpak, Snap or AppImage, and macOS gets it for free. `lofty` (already in the
tree) writes the Vorbis comments.
`RipSource` is the portability seam, and it is the only part that differs
between platforms: Linux reads sectors through ioctls, while macOS MOUNTS an
audio CD and presents each track as an AIFF under /Volumes — so there a "read"
is a file read. Everything after the seam is shared.
Two defects were found by ripping a real disc and looking at the result, and
neither would have failed a test:
1. The tags were never written. Tagging happened on the `.flac.part` file,
and lofty identifies a file by its EXTENSION — so it wrote nothing, and
said nothing. Tagging now happens after the rename, with a test that
reads the bytes back and asserts the strings are in them.
2. The files could not be SEEKED. `flac -d --skip=2:00` returned
FLAC__STREAM_DECODER_SEEK_ERROR and `flac -t` warned 3102 times over 3103
frames. The cause is precise: a stream's last frame is short (12 709 620
samples at a 4096 block leaves 3828), flacenc reports that honestly as
min_blocksize 3828 / max 4096 — but in FLAC `min != max` MEANS
variable-blocksize, and a decoder reading one expects SAMPLE numbers in
the frame headers while the frames correctly carry FRAME numbers.
libFLAC's own encoder writes min == max for exactly this reason. Two
bytes of STREAMINFO, and now: zero warnings, seek works.
Verified against the drive, not inferred: 176 400 bytes decoded out of the
FLAC are byte-identical to the same sectors read straight off the disc. The
whole album ripped from the UI — seven tracks, 436 MB, every one decoding
clean, tagged with TITLE/ARTIST/ALBUM/TRACKNUMBER/TRACKTOTAL/DATE/ALBUMARTIST,
and no partial file left behind.
Filenames are sanitised for the strictest filesystem the library might be
copied to, including Windows' reserved DEVICE names and its habit of silently
dropping a trailing dot — which would turn two tracks into one collision.
cc
This commit is contained in:
5 files changed
+680
-1
No files matched your search
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "qbz-rip"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
qbz-disc = { path = "../qbz-disc" }
|
||||
# Pure Rust, so it works on macOS with no native dependency and nothing extra
|
||||
# to bundle into Flatpak, Snap or AppImage.
|
||||
flacenc = "0.5"
|
||||
# Already in the tree; writes FLAC Vorbis comments.
|
||||
lofty = "0.23"
|
||||
Reference in new issue
Block a user