1
0
Fork
You've already forked tcd
0
forked from armin/tcd
tcd - Transcode Detector
  • C 99.4%
  • Makefile 0.6%
2026年07月04日 21:22:36 +02:00
.gitignore init 2026年07月03日 12:15:47 +02:00
AGENTS.md enhance AGENTS.md 2026年07月04日 20:15:35 +02:00
LICENSE add LICENSE 2026年07月04日 20:20:42 +02:00
Makefile init 2026年07月03日 12:15:47 +02:00
README.md add logo 2026年07月04日 21:04:24 +02:00
tcd.c re-format usage information output, update README accordingly 2026年07月04日 20:55:45 +02:00
tcd.png update logo 2026年07月04日 21:22:36 +02:00
tcd3.png update logo 2026年07月04日 21:17:01 +02:00
tcd3.png-autosave.kra update logo 2026年07月04日 21:18:56 +02:00

tcd

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 (precise description)

tcd decodes the audio file to PCM via FFmpeg, applies a Hann window, and computes the FFT (default 4096-point) with 50% overlap for each windowed frame. Magnitude spectra from all frames are accumulated and averaged into a single power spectrum. From this averaged spectrum, five metrics are computed:

  • Cutoff: highest frequency bin whose magnitude ≥ peak ×ばつ 10^(threshold_db/20), controlled by the sensitivity setting (see Threshold sensitivity).
  • Steepness: transition bandwidth between the −20 dB and −60 dB points (relative to peak), measuring how sharply the spectrum rolls off.
  • Noise floor: average magnitude in the top quartile of the spectrum (≈16500–22050 Hz at 44.1 kHz sample rate), expressed in dB relative to peak.
  • Roughness: coefficient of variation (standard deviation / mean) of magnitude values in the 60%–95% band below cutoff. This quantifies spectral irregularity introduced by quantization noise.
  • Band ratio: ratio of mean magnitude in 16–20 kHz to mean magnitude in 12–16 kHz. Lossy codecs under-allocate bits above 16 kHz, producing a characteristic dip in this region.

The verdict is determined by evaluating these metrics against empirically derived thresholds (which are scaled by the sensitivity setting), first using cutoff + steepness, then secondarily using roughness + band ratio for high-cutoff cases. For lossy input formats, cutoff is compared against bitrate-specific expectations to detect upscaling.


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

Threshold sensitivity (-t)

The -t parameter (1–99, default 50) controls how aggressively tcd detects transcodes and upscaled files. It works at two levels.

1. Cutoff detection threshold

The cutoff frequency is defined as the highest bin whose magnitude is at least peak ×ばつ 10^(threshold_db / 20). The percentage is mapped linearly to dB:

threshold_db = −(40 + (pct − 1) ×ばつ 40 / 98)
-t value threshold_db Magnitude threshold Behaviour
1 −40.0 dB 1.0 % of peak Least sensitive – only the strongest signal counts
50 −60.0 dB 0.1 % of peak Default – balances sensitivity and specificity
99 −80.0 dB 0.01 % of peak Most sensitive – detects cutoff deep in the noise floor

A higher -t (more negative dB) detects the cutoff at a higher frequency because it can follow the spectrum deeper into the noise floor. This makes the tool stricter: small spectral remnants above a true lossy cutoff are still recognised as "signal."

2. Verdict-threshold scaling

The same percentage also produces a continuous sensitivity factor:

sensitivity = (pct − 1) / 98.0
-t value sensitivity Effect on verdict thresholds
1 0.00 Doubles the allowed steepness – very permissive, few false positives
50 0.50 Base thresholds used as published above
99 1.00 Steepness limits multiplied by 0.25; roughness thresholds lowered by up to ×ばつ – very strict, catches borderline cases

The table of steepness thresholds used in the transcode verdict (see How tcd combines these clues) is scaled by:

bw_factor = max(2.0 ×ばつ (1.0 − sensitivity), 0.25)
allowed_steepness = base_max_bw ×ばつ bw_factor

The roughness thresholds (r1 = 0.40, r2 = 0.30, r3 = 0.20) are likewise scaled:

r1 = 0.40 ×ばつ (1.0 + (0.5 − sensitivity) ×ばつ 1.5)
r2 = 0.30 ×ばつ (1.0 + (0.5 − sensitivity) ×ばつ 1.5)
r3 = 0.20 ×ばつ (1.0 + (0.5 − sensitivity) ×ばつ 1.5)

3. Practical guidance

  • Default (50): well-tuned for most material. Use this unless you have a specific reason to change it.
  • Lower values (1–40): reduce false positives on already-filtered material (e.g. deliberate lowpass during mastering). Rarely needed.
  • Higher values (60–99): catch transcodes that leave very little spectral evidence. Useful for batch cleaning of a library, but may increase false positives on quiet or synthetic 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>

When run, tcd prints an analysis summary. Example:

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 field is explained in the What each number means section.

Options:

-t, --threshold PCT Detection sensitivity 1-99 [50]
-f, --fft-size N FFT size, power of 2 [4096]
-d, --duration SEC Seconds to analyze [120]
-r, --recursive Recurse into subdirectories
-F, --full Analyze entire file (no duration limit)
-v, --verbose Verbose output with decision log
-s, --visual Graphical spectrum TUI visualization
-V Alias for -s
-a, --auto-remove Automatically delete detected transcodes
-h, --help Show this help screen

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 (freely available)