feat(disc): read a CD on macOS, through a reader split by platform
The disc feature was built entirely on Linux in a day, and the result did not
COMPILE on macOS at all: qbz_disc::cdda was cfg-gated to Linux while qbz-rip,
cdda_qt and audible_qt used its names unconditionally. Any macOS build has been
broken since qbz-disc landed.
ONE SET OF NAMES, two implementations that share no code. Linux talks to the
drive through ioctls and reads raw sectors; macOS does not have to, because the
OS mounts the disc and exposes one AIFF per track plus a .TOC.plist. qbz-rip,
audible_qt and cdda_qt needed no change, which is the test of whether the seam
was in the right place. Every other platform compiles and reports "no drive".
VERIFIED against the owner's Tool — Fear Inoculum, the USB drive moved from the
Linux box to the Mac mini:
- the plist's Start Block includes the 150-sector lead-in that the kernel
omits. Subtracting it reproduces the ioctl's numbers exactly, so both
machines compute DiscID BeNBMsD8Du5NO2W61Yk.B2jwwIs- and fingerprint
38bef21351f7fca3 — which is what lets a disc corrected on one be found on
the other;
- all seven tracks read on macOS md5 to exactly what metaflac reports for the
FLACs the Linux path ripped from the same disc hours earlier.
TWO THINGS ONLY MEASUREMENT CAUGHT.
Byte order. "AIFF is big-endian" is true of the format and wrong here: macOS
writes AIFC with compression sowt, which is twos backwards and means the
samples are already little-endian. An unconditional swap produced audio that
passed every cheap statistic — 97 % non-zero, full-scale peak — and matched the
Linux rip nowhere. The COMM chunk is read and the swap is conditional.
Track resolution. audible_qt rebuilds its TocTrack from a CdRef, which carries
a device and a sector range and no track number, so it passes zero. Matching on
the number meant every CD track on macOS answered "could not read the disc"
while ripping, which passes a real number, worked — two paths disagreeing with
only one of them exercised. Resolution is by start_lsn, the address the seam
actually carries, with a regression test on a synthetic volume that fails with
NoDisc under the old rule.
One transient disagreement is recorded rather than smoothed over: reading all
seven tracks back to back once produced a different digest for the last track,
and three further reads matched. The read is not perfectly reproducible on a
marginal region, which is exactly the caveat this approach already states.
cc
This commit is contained in:
7 files changed
+1255
-492
No files matched your search
@@ -8,10 +8,22 @@ log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
sha1 = "0.10"
|
||||
base64 = { workspace = true }
|
||||
# The disc MEMORY (src/store.rs). SQLite rather than a JSON file because the
|
||||
# app already speaks it everywhere and a row-at-a-time upsert is what the
|
||||
# "never overwrite a human" rule needs.
|
||||
rusqlite = { version = "0.31", features = ["bundled"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
dirs = "6"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
# macOS hands the table of contents over as a `.TOC.plist` beside the audio
|
||||
# files. It is a BINARY plist in practice, so it is parsed rather than read.
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
plist = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
reqwest = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
Reference in new issue
Block a user