flaccheck
What flaccheck is, why we built it, how detection works, and how we validated it against other tools.
flaccheck is an open-source Rust CLI (plus local web UI) that answers one question: is this “lossless” file actually lossless, or a lossy transcode in disguise?
It scans FLAC, ALAC, WAV, AIFF, and other decodable formats locally — no cloud upload — and returns a structured verdict with optional per-detector evidence.
The one-sentence mental model
flaccheck does not trust file extensions or tags. It decodes audio to PCM and looks for lossy codec fingerprints in the spectrum and time domain.
Public repo: github.com/dasunNimantha/flaccheck
What it is
| Piece | Role |
|---|---|
CLI (flaccheck scan) | Batch-scan files or directories; JSON/CSV/HTML/text output |
Web UI (flaccheck serve) | Drag-and-drop analysis on 127.0.0.1 |
| Detector library | Reusable Rust crates for spectral, MDCT, artifact, and hi-res checks |
| Optional ML tier | ONNX borderline classifier for ambiguous cases (--ml) |
Why we built it
Fake lossless is common in downloaded music libraries:
- A 128 kbps MP3 re-wrapped as FLAC does not recover quality — it only grows file size.
- “Hi-res” 24-bit releases are sometimes upsampled 16-bit PCM with no new information.
- Marketplaces and forum rips are not always honest about source format.
Manual tools like Spek show spectrograms but do not scale to whole libraries. Existing automated tools either false-positive on genuine masters (flagging narrow-band archival transfers) or miss transcodes on some codecs.
We wanted a tool that:
- Runs fully offline on Linux/macOS/Windows
- Abstains (
INCONCLUSIVE) on ambiguous band-limited content instead of guessing - Ships with reproducible benchmarks against alternatives (FLAC Detective, isflac, soundaudit, audiocheckr)
How detection works
Detector tiers
| Tier | Method | What it catches |
|---|---|---|
| 1 | Spectral cutoff & brick-wall fingerprint | MP3/AAC/Opus frequency cliffs |
| 2 | MDCT quantization residual | Lossy codec block structure |
| 3 | Pre-echo, phase, joint-stereo artifacts | MP3/AAC encoder signatures |
| 4 | Fake hi-res | 16-bit content in 24-bit containers |
| 5 | Abstention | 78 rpm transfers, AM radio, narrow masters |
Evidence from all tiers is fused into one transcode verdict and a confidence score. Use --explain to see per-detector notes in the report.
Technical pipeline
1. Decode to PCM
Every file is decoded to interleaved f32 PCM — tags and container format are ignored after decode.
| Path | When used |
|---|---|
| symphonia | FLAC, WAV, AIFF, ALAC, AAC, MP3, Vorbis, .m4a |
| ffmpeg pipe | APE, WavPack, Opus (when ffmpeg is in PATH) |
The decoder picks the first audio track, preserves sample rate and channel count, and hands a PcmBuffer to the detector pipeline.
2. Windowed analysis
Long tracks are not analyzed as one giant FFT. The PCM is split into time windows (configurable duration and count). Windows with RMS below 15% of the peak are dropped so silent gaps do not dilute the spectrum.
3. Tier 1 — Spectral (rustfft, 4096-point)
On each window:
- Convert to mono (left channel)
- Compute average power spectral density across windows
- Smooth the PSD in dB (5-bin moving average)
- Measure:
- Brick-wall cutoff — sharp drop to a digital noise floor below Nyquist
- Rolloff steepness — dB/octave at the cliff (MP3 tends to be steeper than AAC)
- Spectral edge — highest frequency with meaningful energy
- AAC shelf — gradual high-frequency shelf before cutoff
- SBR mirroring — HF band that mirrors energy from a lower band (HE-AAC hint)
Codec signature matching compares measured cutoff/edge against known MP3, AAC, and Opus bitrate tables (e.g. MP3 128k ≈ 16 kHz, AAC 256k ≈ 20 kHz). A match raises codec_guess and codec_certainty.
full_band is set when content reaches ~97% of Nyquist and there is no strong brick wall — a key input to abstention logic.
4. Tier 2 — MDCT / PQMF quantization
Searches for lossy codec block structure in the decoded samples:
- MP3 PQMF / granule hybrid filterbank patterns
- MDCT quantization residual from transform-codec frame boundaries
This tier is gated by scan mode and spectral suspicion — it is skipped on clean full-band files in balanced mode because PQMF heuristics false-positive on live/acoustic material. Quant evidence can corroborate a spectral cutoff but never alone flag full-bandwidth audio as transcoded.
5. Tier 3 — Time-domain artifacts
Looks for encoder-specific signatures in the waveform:
| Signal | What it detects |
|---|---|
| Pre-echo | Smear before transients (MDCT pre-echo) |
| Phase discontinuity | Frame-boundary phase jumps |
| Joint stereo | Mid/side correlation patterns |
| Noise floor | Digital silence above the cutoff frequency |
fast mode runs a light artifact pass; balanced / max run the full set when spectral suspicion is elevated.
6. Tier 4 — Fake hi-res
Separate from transcode detection:
- Upsampled — 16 kHz / 44.1 kHz content in a 48 kHz / 96 kHz container
- Padded depth — 16-bit samples zero-padded into 24-bit words
A genuine transcode verdict can be downgraded to SUSPICIOUS if hi-res fraud is detected.
7. Evidence fusion & abstention
Each detector emits weighted evidence records (signal, value, weight, note). fuse_evidence() in flaccheck-core combines them:
weighted_score = Σ (value × weight) / Σ weightKey rules:
- Strong cliff (
brick_wall≥ threshold) → transcode fingerprint even on bass-heavy tracks with little HF energy - Strong AAC shelf + cutoff below Nyquist → transcode even without MP3-style brick wall
- Band-limited content (edge below ~18 kHz, not full-band) →
INCONCLUSIVEunless a strong cliff overrides - Full-band + low artifact suspicion → quant tier cannot promote to
TRANSCODEDon its own
Optional ML tier (--ml, ONNX): refines borderline cases only; does not replace the rule-based fusion.
Scan modes
| Mode | Spectral | Quant | Artifacts |
|---|---|---|---|
fast | ✓ | skip | light |
balanced (default) | ✓ | coarse on suspects | full on suspects |
max | ✓ | exhaustive | full always |
Verdicts
| Verdict | Meaning |
|---|---|
GENUINE | No lossy fingerprint detected in decoded PCM |
TRANSCODED | Strong evidence of lossy→lossless transcode |
SUSPICIOUS | Some lossy indicators; not conclusive |
INCONCLUSIVE | Band-limited or ambiguous — tool refuses to guess |
GENUINE ≠ provenance
GENUINE means no lossy encoding fingerprint, not “ripped from original CD.” A native AAC file analyzed for transcode fingerprints is a different question than “is the container ALAC?” — see below.
ALAC and .m4a files
flaccheck can scan .m4a and .alac files (decoded via symphonia). Two questions often get conflated:
| Question | Tool |
|---|---|
| What codec is in the container? (ALAC vs AAC) | ffprobe -show_entries stream=codec_name |
| Was lossy audio re-encoded as fake lossless? | flaccheck scan |
Example: a streaming AAC .m4a is legitimately lossy — not “fake ALAC.” flaccheck analyzes the audio content, not the shopping label.
Quick usage
git clone https://github.com/dasunNimantha/flaccheck.git
cd flaccheck
cargo build --release -p flaccheck
./target/release/flaccheck scan ~/Music --format json --explain
./target/release/flaccheck serve # local web UIRequirements: Rust stable. Optional ffmpeg for APE, WavPack, and Opus decode.
Native decode: FLAC, WAV, AIFF, ALAC, AAC, MP3, Vorbis, .m4a · With ffmpeg: APE, WavPack, Opus
What we optimized for
Precision on genuine masters
Do not call real lossless sources fake. flaccheck abstains on narrow-band content rather than marking 78 rpm transfers as transcodes.
Recall on obvious transcodes
MP3→FLAC is the easy case (~98% recall on combined corpora). High-bitrate AAC and MP3 V0 are intentionally harder.
Reproducibility
Labeled ffmpeg transcode corpora, comparison scripts, and chart JSON in the repo — not hand-waved blog numbers.
Benchmarks & validation
We validate flaccheck against labeled real-music corpora — not synthetic sine waves — by transcoding genuine sources with ffmpeg and comparing tool outputs to ground truth.
Charts and raw JSON: docs/benchmarks/comparison.json
Why benchmark this way
| Approach | Problem |
|---|---|
| Synthetic sine/noise only | No brick walls, no real encoder signatures |
| Trust vendor claims | “100% accurate” with no corpus |
| Single album hand-test | Not reproducible |
Our approach:
- Start from genuine lossless sources (archival, classical, live, netlabel)
- Transcode with ffmpeg to MP3/AAC/Opus/Vorbis, wrap back to FLAC
- Label each file
genuineortranscodedinmanifest.json - Run flaccheck and competitors on identical paths
- Score precision, recall, and false positives on genuine references
Recall definition
A transcode counted as a miss if the tool says INCONCLUSIVE or GENUINE. flaccheck deliberately abstains on band-limited files — those count against recall but protect precision.
Corpora
| Corpus | Files | Genuine | Transcodes | Notes |
|---|---|---|---|---|
| v1 (realistic) | 121 | 11 | 110 | Archival/classical/live sources via generate_realistic.sh |
| v2 (validation) | 132 | 12 | 120 | Independent etree/netlabel sources via download_benchmark_v2.sh |
| Combined | 253 | 23 | 230 | benchmark_compare.py --combine |
Tools compared
| Tool | Version |
|---|---|
| flaccheck | 0.1.x balanced |
| FLAC Detective | 1.7 |
| isflac | 0.1.4 |
| soundaudit | 0.1.2 |
| audiocheckr | 0.3.7 |
Combined results (253 files)
| Tool | Precision | Recall | False positives on genuine |
|---|---|---|---|
| flaccheck | 99% | 85% | 1 / 23 |
| audiocheckr | 93% | 82% | 15 / 23 |
| isflac | 95% | 57% | 7 / 23 |
| FLAC Detective | 97% | 47% | 4 / 23 |
| soundaudit | — | 0% | 0% |
flaccheck leads on precision (one false positive across 23 genuine references). audiocheckr catches more borderline transcodes but flags most genuine files as fake.
Recall by codec (flaccheck)
| Codec | Recall |
|---|---|
| MP3 | 98% |
| AAC | 55% |
| Opus | 98% |
| Vorbis | 85% |
AAC at 128–256 kbps is the hard case — gentle shelf instead of a hard brick wall.
Reproduce benchmarks
Build corpora:
./datasets/generate_realistic.sh /path/to/sources datasets/output/realistic
./datasets/download_benchmark_v2.sh
./datasets/generate_realistic.sh datasets/benchmark_v2_sources datasets/output/benchmark_v2Run scanners per corpus:
cargo build --release -p flaccheck
./target/release/flaccheck scan datasets/output/realistic \
--format json --quiet -o benchmarks/flaccheck_per_file.json --workers 8
flac-detective --format json --output benchmarks/flac_detective.json datasets/output/realisticCompare and merge:
python3 scripts/benchmark_compare.py --collect # per corpus
python3 scripts/benchmark_compare.py --combine # → docs/benchmarks/Research references
Detection draws on published work on lossy fingerprinting: D'Alessandro & Shi (ACM MM&Sec 2009), Derrien (JAES 2019), Lacroix et al. (AES 2015).