mirror of
https://codeberg.org/armin/tcd.git
synced 2026-09-01 05:50:46 +02:00
re-format usage information output, update README accordingly
This commit is contained in:
parent
6c9fb5b7e9
commit
545ada8bc3
2 changed files with 146 additions and 42 deletions
162
README.md
162
README.md
|
|
@ -21,13 +21,32 @@ please read at least the --help information and *understand* what -a does.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
How it works (the short version)
|
How it works (precise description)
|
||||||
--------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
tcd decodes your audio file, converts it to the frequency domain (like a
|
tcd decodes the audio file to PCM via FFmpeg, applies a Hann window, and
|
||||||
graph showing how much energy exists at each frequency), then measures several
|
computes the FFT (default 4096-point) with 50% overlap for each windowed
|
||||||
properties of that frequency graph. Each property is a clue about whether the
|
frame. Magnitude spectra from all frames are accumulated and averaged into a
|
||||||
audio was produced by a lossy encoder. Combined, these clues give a verdict.
|
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](#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](#threshold-sensitivity)),
|
||||||
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -268,6 +287,77 @@ If neither layer triggers, the file is **GENUINE** (native lossless).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
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 4× – very strict, catches borderline cases |
|
||||||
|
|
||||||
|
The table of steepness thresholds used in the transcode verdict (see
|
||||||
|
[How tcd combines these clues](#how-tcd-combines-these-clues-into-a-verdict))
|
||||||
|
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
|
Confidence score
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
@ -341,33 +431,49 @@ positives on legitimate encodes that simply use a conservative lowpass.
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
```
|
tcd [options] <audio-file>
|
||||||
tcd [options] <audio-file>
|
|
||||||
|
When run, tcd prints an analysis summary. Example:
|
||||||
|
|
||||||
-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
|
|
||||||
```
|
```
|
||||||
|
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](#what-tcd-displays-and-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:
|
Exit codes:
|
||||||
|
|
||||||
| Code | Meaning |
|
| Code | Meaning |
|
||||||
|------|-----------------------------------|
|
|------|-------------------------------------|
|
||||||
| 0 | NATIVE or GENUINE (file is clean) |
|
| 0 | NATIVE or GENUINE (file is clean) |
|
||||||
| 1 | UPSCALED or TRANSCODE detected |
|
| 1 | UPSCALED or TRANSCODE detected |
|
||||||
| 2 | SILENT (no detectable content) |
|
| 2 | SILENT (no detectable content) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
24
tcd.c
24
tcd.c
|
|
@ -939,8 +939,7 @@ static void print_help(const char *prog)
|
||||||
{
|
{
|
||||||
char buf[256];
|
char buf[256];
|
||||||
snprintf(buf, sizeof(buf), ANSI_BOLD ANSI_CYAN "tcd" ANSI_RESET
|
snprintf(buf, sizeof(buf), ANSI_BOLD ANSI_CYAN "tcd" ANSI_RESET
|
||||||
" \xe2\x80\x94 Transcode Detector "
|
" (Transcode Detector / Psychoacoustic audio authenticity analysis tool)");
|
||||||
"Psychoacoustic audio authenticity analysis");
|
|
||||||
int v = vis_len(buf);
|
int v = vis_len(buf);
|
||||||
printf(" %s", buf);
|
printf(" %s", buf);
|
||||||
int pad = tw - 2 - v;
|
int pad = tw - 2 - v;
|
||||||
|
|
@ -967,30 +966,29 @@ static void print_help(const char *prog)
|
||||||
printf(" " ANSI_BOLD "Options:" ANSI_RESET);
|
printf(" " ANSI_BOLD "Options:" ANSI_RESET);
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
#define OPT_COL 28
|
||||||
|
|
||||||
#define OPT(fmt, desc) do { \
|
#define OPT(fmt, desc) do { \
|
||||||
printf(" " ANSI_GREEN fmt ANSI_RESET " %s", desc); \
|
int flen = vis_len(fmt); \
|
||||||
|
int pad = OPT_COL - flen; \
|
||||||
|
if (pad < 1) pad = 1; \
|
||||||
|
printf(" " ANSI_GREEN "%s" ANSI_RESET "%*s%s", fmt, pad, "", desc); \
|
||||||
printf("\n"); \
|
printf("\n"); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
{
|
{
|
||||||
char buf[80];
|
char buf[80];
|
||||||
snprintf(buf, sizeof(buf), "Detection sensitivity 1-99 [" ANSI_CYAN "%d" ANSI_RESET "]", THRESHOLD_DEFAULT);
|
snprintf(buf, sizeof(buf), "Detection sensitivity 1-99 [" ANSI_CYAN "%d" ANSI_RESET "]", THRESHOLD_DEFAULT);
|
||||||
OPT("-t, --threshold PCT", buf);
|
OPT("-t, --threshold PCT", buf);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
char buf[80];
|
char buf[80];
|
||||||
snprintf(buf, sizeof(buf), ANSI_DIM " Lower = fewer, higher = more" ANSI_RESET);
|
snprintf(buf, sizeof(buf), "FFT size, power of 2 [" ANSI_CYAN "%d" ANSI_RESET "]", FFT_SIZE_DEFAULT);
|
||||||
printf(" %s", buf);
|
OPT("-f, --fft-size N", buf);
|
||||||
printf("\n");
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
char buf[80];
|
char buf[80];
|
||||||
snprintf(buf, sizeof(buf), "FFT size, power of 2 [" ANSI_CYAN "%d" ANSI_RESET "]", FFT_SIZE_DEFAULT);
|
snprintf(buf, sizeof(buf), "Seconds to analyze [" ANSI_CYAN "%d" ANSI_RESET "]", MAX_ANALYSIS_SECS);
|
||||||
OPT("-f, --fft-size N", buf);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
char buf[80];
|
|
||||||
snprintf(buf, sizeof(buf), "Seconds to analyze [" ANSI_CYAN "%d" ANSI_RESET "]", MAX_ANALYSIS_SECS);
|
|
||||||
OPT("-d, --duration SEC", buf);
|
OPT("-d, --duration SEC", buf);
|
||||||
}
|
}
|
||||||
OPT("-r, --recursive", "Recurse into subdirectories");
|
OPT("-r, --recursive", "Recurse into subdirectories");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue