mirror of
https://codeberg.org/armin/tcd.git
synced 2026-09-01 05:50:46 +02:00
402 lines
16 KiB
Markdown
402 lines
16 KiB
Markdown
tcd - Transcode Detector
|
||
=========================
|
||
|
||
`tcd` analyses an audio file's frequency spectrum to determine whether it is a
|
||
genuine native encode or a *transcode* (a lossy → lossless re-encode). It can
|
||
also detect *upscaling* (a lossy file that has been re-encoded at a higher
|
||
bitrate by the same lossy codec, e.g. 128 → 320 kbps MP3).
|
||
|
||
---
|
||
|
||
Obligatory AI-slop disclaimer
|
||
-----------------------------
|
||
`tcd` is 98% vibe-coded (a.k.a. "ai slop"). If that's a problem for you, please
|
||
kindly just use a different tool. There is also absolutely *NO* guarantee this
|
||
will work reliably, be useful in any way, or even make any sense whatsoever.
|
||
|
||
Careful! Dragons ahead!
|
||
-----------------------
|
||
`tcd` *can* and absolutely *will* delete your data. Don't blindly use -a, and
|
||
please read at least the --help information and *understand* what -a does.
|
||
|
||
---
|
||
|
||
How it works (the short version)
|
||
--------------------------------
|
||
|
||
tcd decodes your audio file, converts it to the frequency domain (like a
|
||
graph showing how much energy exists at each frequency), then measures several
|
||
properties of that frequency graph. Each property is a clue about whether the
|
||
audio was produced by a lossy encoder. Combined, these clues give a verdict.
|
||
|
||
---
|
||
|
||
What tcd displays and what each number means
|
||
---------------------------------------------
|
||
|
||
Here is example output from an MP3 file:
|
||
|
||
```
|
||
File: ./08 You Got Me.mp3
|
||
Format: mp3
|
||
Bitrate: 320 kbps
|
||
Sample rate: 44100 Hz
|
||
Channels: 2
|
||
Windows: 13550
|
||
Peak: 47.5 dBFS
|
||
Cutoff: 20790 / 22050 Hz = 94.3%
|
||
Steepness: 20209 Hz
|
||
Roughness: 0.323
|
||
Band ratio: 0.555
|
||
Noise floor: -56.4 dB
|
||
Verdict: NATIVE
|
||
Verdict Info: bandwidth used=94.3% (20790/22050 Hz), expected≥90% for 320 kbps
|
||
```
|
||
|
||
Each metric is explained below.
|
||
|
||
---
|
||
|
||
### Cutoff (20790 / 22050 Hz = 94.3%)
|
||
|
||
**What it is:** The highest frequency where the audio still has measurable
|
||
energy. Everything above this point is silence or noise.
|
||
|
||
**The Nyquist ceiling:** Digital audio is made of snapshots (samples). For CD
|
||
quality (44100 snapshots per second), there is a hard limit: you cannot store
|
||
a frequency higher than half the snapshot rate = **22050 Hz**. This is called
|
||
the *Nyquist frequency*. It is a physical ceiling — higher frequencies simply
|
||
cannot exist.
|
||
|
||
**How lossy encoding changes it:** MP3 and other lossy codecs deliberately cut
|
||
off high frequencies to save space. The cutoff gets lower as the bitrate drops:
|
||
|
||
| Bitrate | Typical cutoff | Audio quality impact |
|
||
|---------|---------------|----------------------|
|
||
| 320 kbps | ≥20000 Hz (≥90%) | Keeps almost all audible high end |
|
||
| 256 kbps | ≥19000 Hz (≥86%) | Still very clean |
|
||
| 192 kbps | ~17500 Hz (~79%) | Moderate high-end roll-off |
|
||
| 128 kbps | ~16000 Hz (~73%) | Noticeable treble loss |
|
||
| 96 kbps | ~13000 Hz (~59%) | Significant high-end missing |
|
||
| 64 kbps | ~11000 Hz (~50%) | Sounds dull, heavily filtered |
|
||
|
||
**What 94.3% means for the example file:** 20790 / 22050 = 94.3%. The cutoff is very
|
||
close to the theoretical maximum. This is what we expect from a 320 kbps
|
||
encode. If this same file showed 54% (~12000 Hz), it would mean the treble
|
||
was chopped off by an aggressive low-bitrate encoder, and someone just
|
||
re-encoded it at 320 kbps — the cutoff is permanent and cannot be restored.
|
||
That would be an **UPSCALED** file.
|
||
|
||
---
|
||
|
||
### Steepness
|
||
|
||
**What it measures:** How abruptly the sound drops off *at* the cutoff point.
|
||
tcd measures this as the frequency gap between the −20 dB point (still loud)
|
||
and the −60 dB cutoff (essentially silent). A narrow gap = a sharp drop.
|
||
|
||
**The analogy:** Imagine the frequency graph as a mountain ridge. A lossless
|
||
recording rolls off like a natural hillside — gradual, smooth, taking
|
||
thousands of Hz to go from loud to silent. A lossy encoder's lowpass filter
|
||
creates a cliff — a near-vertical drop from audible signal to nothing.
|
||
|
||
**What the number means:** Steepness is the width (in Hz) of that drop zone.
|
||
The smaller the number, the sharper the cliff:
|
||
|
||
| Steepness | What it looks like | Likely origin |
|
||
|-----------|-------------------|---------------|
|
||
| <500 Hz | Brick-wall drop | Lossy encoder (MP3, AAC) |
|
||
| 500–2000 Hz | Fairly sharp | Could be lossy or aggressive production filter |
|
||
| 2000–5000 Hz | Moderate | Might be natural |
|
||
| >5000 Hz | Gentle slope | Natural acoustic roll-off (lossless) |
|
||
|
||
To understand steepness, imagine a guitar string being plucked. The sound
|
||
naturally fades across many frequencies — the harmonics near the top end of
|
||
your hearing get quieter and quieter over a broad range. This is a gentle
|
||
slope. Now imagine someone put a pair of scissors on the frequency spectrum
|
||
and cut everything above a certain note. That sharp edge — the difference
|
||
between "still audible" and "completely gone" in just a few hundred Hz — is
|
||
what lossy compression does. The steepness number tells you how sharp that
|
||
scissor cut was.
|
||
|
||
---
|
||
|
||
### Roughness (0.323)
|
||
|
||
**What it measures:** How "bumpy" or "irregular" the spectrum looks just
|
||
before the cutoff point.
|
||
|
||
**The analogy:** Lossy encoding introduces quantization noise — tiny
|
||
rounding errors that are unevenly distributed across frequencies. In the
|
||
frequency graph, this looks like a jagged, bumpy line instead of a smooth
|
||
one. Think of it like a dirt road vs a paved highway: lossless audio is
|
||
smooth, lossy audio is bumpy. Double-encoded audio (a transcode) is even
|
||
bumpier because the errors from two encodings stack on top of each other.
|
||
|
||
**What the number means:**
|
||
|
||
| Roughness | What it looks like | Likely origin |
|
||
|-----------|-------------------|---------------|
|
||
| <0.15 | Very smooth | Natural/lossless |
|
||
| 0.15–0.30 | Slightly bumpy | Could be lossy single encode |
|
||
| 0.30–0.50 | Clearly bumpy | Lossy single encode, or borderline transcode |
|
||
| >0.50 | Very jagged | Almost certainly a transcode |
|
||
|
||
---
|
||
|
||
### Band ratio (0.555)
|
||
|
||
**What it measures:** How much high-frequency energy (16–20 kHz) remains
|
||
compared to mid-high energy (12–16 kHz).
|
||
|
||
**Why it matters:** Human hearing is least sensitive above 16 kHz. Lossy
|
||
encoders exploit this by spending almost no bits on those frequencies. The
|
||
result is that the 16–20 kHz region is much quieter than the 12–16 kHz region.
|
||
In native recordings, this drop is modest; in lossy/transcoded material, it
|
||
is severe.
|
||
|
||
**What the number means:** Band ratio = energy in 16–20 kHz band ÷ energy in
|
||
12–16 kHz band. A ratio of 1.0 means both bands are equally loud. A ratio of
|
||
0.5 means the top band is half as loud.
|
||
|
||
| Band ratio | What it means |
|
||
|------------|---------------|
|
||
| >0.85 | Healthy high end — likely native lossless |
|
||
| 0.70–0.85 | Mild roll-off — could be lossy or natural |
|
||
| 0.50–0.70 | Significant high-end loss — likely lossy |
|
||
| <0.50 | Severe high-end loss — almost certainly lossy or transcoded |
|
||
|
||
---
|
||
|
||
### Noise floor (-56.4 dB)
|
||
|
||
**What it measures:** The average noise level in the highest quarter of the
|
||
frequency range (roughly 16500–22050 Hz).
|
||
|
||
**The analogy:** Imagine listening in a quiet room — the background hiss is
|
||
very low. Now imagine that same room with a fan running — the background
|
||
noise rises. A lossy encoder introduces quantization noise that raises the
|
||
"background hiss" in the high frequencies.
|
||
|
||
**What the number means:** This is measured in decibels (dB). More negative =
|
||
quieter (better). Less negative = noisier (worse):
|
||
|
||
| Noise floor | What it means |
|
||
|-------------|---------------|
|
||
| −90 to −110 dB | Very clean — native lossless |
|
||
| −70 to −90 dB | Moderate — could be lossy or quiet lossless |
|
||
| −50 to −70 dB | Noisy — likely lossy |
|
||
| >−50 dB | Very noisy — almost certainly lossy or transcoded |
|
||
|
||
---
|
||
|
||
How tcd combines these clues into a verdict
|
||
---------------------------------------------
|
||
|
||
tcd does not rely on any single metric. It combines them in stages, like a
|
||
detective building a case.
|
||
|
||
### Scenario 1: The input file is lossy (MP3, AAC, etc.)
|
||
|
||
The file already claims to be lossy. The question is: was it *originally*
|
||
encoded at the stated bitrate, or was it re-encoded from a lower bitrate?
|
||
|
||
**The check:** tcd compares the cutoff against what is expected for that
|
||
bitrate:
|
||
|
||
| Stated bitrate | Expected cutoff |
|
||
|----------------|-----------------|
|
||
| <192 kbps | ≥75% of Nyquist |
|
||
| 192–255 kbps | ≥85% of Nyquist |
|
||
| ≥256 kbps | ≥90% of Nyquist |
|
||
|
||
If the actual cutoff is **more than 8 percentage points lower** than expected,
|
||
the file is **UPSCALED**. For example, a file claiming 320 kbps (expecting
|
||
≥90%) but showing a cutoff of 70% (≈15400 Hz) would be flagged as upscaled
|
||
from ~96 kbps.
|
||
|
||
Otherwise it is **NATIVE** — a genuine single encode at this bitrate.
|
||
|
||
### Scenario 2: The input file is lossless (FLAC, WAV, ALAC, etc.)
|
||
|
||
The file claims to be lossless. The question is: was it actually created by
|
||
decoding a lossy file and re-encoding to lossless?
|
||
|
||
tcd uses a **two-layer** check:
|
||
|
||
**Layer 1 — Cutoff + Steepness (primary):**
|
||
|
||
| Cutoff range | Max steepness allowed | If exceeded → |
|
||
|-------------|----------------------|---------------|
|
||
| <50% of Nyquist | 4000 Hz | TRANSCODE |
|
||
| 50–70% | 3000 Hz | TRANSCODE |
|
||
| 70–80% | 2000 Hz | TRANSCODE |
|
||
| 80–90% | 1200 Hz | TRANSCODE |
|
||
| ≥90% | 500 Hz | TRANSCODE |
|
||
|
||
This works because lossy cutoffs are always sharp (low steepness). A lossless
|
||
recording that happens to have a low cutoff (e.g., a muddy recording with
|
||
little treble) would still have a *gradual* roll-off (high steepness) — you
|
||
need both a low cutoff **and** a sharp drop to convict.
|
||
|
||
**Layer 2 — Roughness + Band ratio (secondary):**
|
||
|
||
If Layer 1 did not trigger but the cutoff is above 80%, tcd checks roughness
|
||
and band ratio. This catches transcodes where the cutoff happens to be high
|
||
enough to pass Layer 1 but the spectrum is still bumpy and depleted in the
|
||
top band:
|
||
|
||
| Roughness | Band ratio | If matched → |
|
||
|-----------|------------|--------------|
|
||
| >0.40 | any | TRANSCODE |
|
||
| >0.30 | <0.90 | TRANSCODE |
|
||
| >0.20 | <0.85 | TRANSCODE |
|
||
|
||
If neither layer triggers, the file is **GENUINE** (native lossless).
|
||
|
||
---
|
||
|
||
### The verdicts at a glance
|
||
|
||
| Verdict | Input codec | What it means |
|
||
|---------|------------|---------------|
|
||
| **NATIVE** | lossy | Encoded once at the stated bitrate — genuine |
|
||
| **UPSCALED** | lossy | Originally encoded at a lower bitrate, then re-encoded higher |
|
||
| **GENUINE** | lossless | Appears to be native lossless — no evidence of lossy origin |
|
||
| **TRANSCODE** | lossless | Originated from a lossy source, decoded to lossless |
|
||
| **SILENT** | any | No detectable audio content |
|
||
|
||
---
|
||
|
||
Confidence score
|
||
----------------
|
||
|
||
A continuous **confidence** (0–100 %) is computed using the same metrics with
|
||
a sliding scale, providing a graded measure of how certain the tool is about
|
||
its verdict.
|
||
|
||
---
|
||
|
||
Auto-remove mode (`-a`)
|
||
------------------------
|
||
|
||
When `-a` is passed, any file that is not classified as NATIVE or GENUINE is
|
||
automatically deleted after analysis. This is useful for batch cleanup of
|
||
corrupt or transcoded libraries. Careful — this will eat data.
|
||
|
||
---
|
||
|
||
Why the method is (somewhat!) scientifically reliable
|
||
------------------------------------------------------
|
||
|
||
### 1. Lossy encoding leaves a permanent spectral fingerprint
|
||
|
||
Every lossy audio codec discards information. The most obvious form is a
|
||
**lowpass filter** — once applied, the frequencies above the cutoff are gone
|
||
forever. Decoding back to PCM and re-encoding to lossless cannot restore them.
|
||
This means a "lossless" FLAC file made from an MP3 will contain the MP3's
|
||
permanent spectral cutoff.
|
||
|
||
### 2. Steepness catches the filter shape
|
||
|
||
Lossy encoders use sharp digital filters (brick-wall style) that drop from
|
||
audible to silent in a few hundred Hz. Natural acoustic sources (voice,
|
||
instruments, room ambience) roll off gradually over many kHz. A drop steeper
|
||
than 2 kHz at any cutoff position is extremely unlikely to occur naturally.
|
||
|
||
- **MP3 (ISO/IEC 11172-3):** typical roll-off of several hundred Hz to ~2 kHz
|
||
- **AAC (ISO/IEC 13818-7):** sharper, often 200–800 Hz
|
||
- **Vorbis:** variable but always steeper than natural
|
||
|
||
### 3. Roughness detects double-encoding noise
|
||
|
||
When audio is lossy-encoded, quantization noise is shaped to be masked by the
|
||
music. Re-encoding adds a *second* layer of noise-shaping, creating
|
||
irregularities in the spectrum that are statistically unlikely in a single
|
||
encode.
|
||
|
||
### 4. Band ratio exploits the Fletcher–Munson curves
|
||
|
||
Human hearing is least sensitive above 16 kHz. Lossy encoders exploit this by
|
||
spending almost no bits there. In native recordings, the 16–20 kHz region is
|
||
typically only 2–6 dB quieter than 12–16 kHz (band ratio 0.5–1.0). In
|
||
transcoded material it is often 10–20 dB quieter (band ratio < 0.3).
|
||
|
||
### 5. Multiple independent metrics prevent false positives
|
||
|
||
No single metric is perfectly reliable. A low cutoff could occur in a genuine
|
||
recording that used an aggressive lowpass during mastering. By requiring
|
||
**both** a low cutoff **and** a sharp roll-off (primary), or **both** high
|
||
roughness **and** a low band ratio (secondary), the tool avoids false
|
||
positives.
|
||
|
||
### 6. The upscaling detector is conservative
|
||
|
||
For lossy files, an 8-percentage-point margin is subtracted before flagging a
|
||
file as upscaled. This accounts for encoder variability and prevents false
|
||
positives on legitimate encodes that simply use a conservative lowpass.
|
||
|
||
---
|
||
|
||
Usage
|
||
-----
|
||
|
||
```
|
||
tcd [options] <audio-file>
|
||
|
||
-t, --threshold PCT Overall detection sensitivity (1-99). Controls all
|
||
decision thresholds: transition bandwidth, roughness,
|
||
and band ratio. Maps to -40 dB cutoff level (1, least
|
||
sensitive) through -80 dB (99, most sensitive).
|
||
[default: 50]. 50 is neutral; lower = fewer detections,
|
||
higher = more detections. Adjust in small steps.
|
||
-f, --fft-size N FFT size (power of 2) [default: 4096]
|
||
-d, --duration SEC Max seconds to analyze [default: 60]
|
||
-r, --recursive Recurse into subdirectories
|
||
-F, --full Analyze entire file (overrides --duration)
|
||
-v, --verbose Verbose output
|
||
-s, --visual Graphical spectrum visualization (TUI)
|
||
-V Alias for -s
|
||
-a, --auto-remove Automatically remove non-native files
|
||
-h, --help Show this help
|
||
```
|
||
|
||
Exit codes:
|
||
|
||
| Code | Meaning |
|
||
|------|-----------------------------------|
|
||
| 0 | NATIVE or GENUINE (file is clean) |
|
||
| 1 | UPSCALED or TRANSCODE detected |
|
||
| 2 | SILENT (no detectable content) |
|
||
|
||
---
|
||
|
||
Limitations
|
||
-----------
|
||
|
||
- **Very short files** (< `fft_size` samples) cannot be analysed. Use `-f`
|
||
to reduce the FFT size.
|
||
- **Already-lowpass-filtered material** (e.g. deliberate 15 kHz LPF during
|
||
mastering) may trigger false positives. The confidence score helps assess
|
||
borderline cases.
|
||
- **High-bitrate lossy encodes** (320 kbps MP3, 256 kbps AAC) have cutoffs
|
||
very close to Nyquist and may not be distinguishable from lossless by
|
||
cutoff alone. The tool relies on roughness and band ratio in this regime.
|
||
- **Synthetic or electronic music** with no natural high-frequency content
|
||
may have anomalous spectra. Use the visual mode (`-s`) to inspect the
|
||
spectrum manually.
|
||
|
||
---
|
||
|
||
References
|
||
----------
|
||
|
||
- ISO/IEC 11172-3:1993 - Coding of moving pictures and associated audio for
|
||
digital storage media at up to about 1.5 Mbit/s, Part 3: Audio (MPEG-1
|
||
Audio Layer III, "MP3").
|
||
- ISO/IEC 13818-7:2006 - Generic coding of moving pictures and associated
|
||
audio information, Part 7: Advanced Audio Coding (AAC).
|
||
- Zwicker, E. & Fastl, H. - *Psychoacoustics: Facts and Models*, Springer,
|
||
1999 (Fletcher–Munson equal-loudness contours).
|
||
- Lerch, A. - *An Introduction to Audio Content Analysis*, Wiley, 2012
|
||
(spectral features for audio forensics).
|