tcdweb/index.html

158 lines
12 KiB
HTML
Raw Normal View History

2026-07-05 00:44:08 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2026-07-05 01:19:07 +02:00
<title>tcd - Audio Transcode Detector</title>
2026-07-05 00:44:08 +02:00
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<input type="file" id="fileInput" accept=".mp3,.flac,.wav,.aiff,.aif,.ogg,.opus,.m4a,.wma,.ac3,.eac3,.aac,.alac,.wv,.mp2,.mp1,.ape,.dsf,.dff,audio/*" hidden>
<div id="errorMsg"></div>
<div id="results">
<section id="playerSection">
<div class="section-header">
<h2>Player &amp; Realtime Analysis</h2>
<div class="section-brand"><span class="logo">tcd</span> <span class="subtitle">Audio Authenticity Analyzer</span><br><span class="tagline">Psychoacoustic transcode detection via spectral analysis</span></div>
</div>
<div class="player-controls">
<button id="loadBtn" class="player-btn load-btn" title="Load Audio File">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
Load File
</button>
2026-07-05 00:44:08 +02:00
<button id="playBtn" class="player-btn" title="Play/Pause"></button>
<button id="stopBtn" class="player-btn" title="Stop"></button>
<button id="holdBtn" class="player-btn" title="Hold Max Cutoff"></button>
<button id="resetBtn" class="player-btn" title="Reset Peaks"></button>
<span id="playTime" class="player-time">0:00.0 / 0:00.0</span>
<select id="windowSelect" class="window-select" title="Spectrum window function"><option value="none">None</option><option value="hanning" selected>Hanning</option><option value="blackman">Blackman-Harris</option><option value="flatTop">Flat Top</option></select>
</div>
<div class="chart-container" style="position:relative">
<canvas id="waveformChart"></canvas>
<div id="loadingOverlay" class="loading-overlay">
<div class="spinner"></div>
<div id="loadingMsg">Processing...</div>
</div>
</div>
<div class="chart-container" style="margin-top:10px;position:relative">
<canvas id="liveSpectrum"></canvas>
<canvas id="liveSpectrumOverlay" style="position:absolute;top:0;left:0;pointer-events:none"></canvas>
</div>
<div class="metrics-grid" style="margin-top:10px">
<div class="metric-card">
<div class="metric-label">Live Peak</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="livePeak">-</div>
2026-07-05 00:44:08 +02:00
</div>
<div class="metric-card">
<div class="metric-label">Live Cutoff</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="liveCutoff">-</div>
2026-07-05 00:44:08 +02:00
</div>
</div>
</section>
<section id="fileInfo">
<h2>File Information</h2>
<div class="info-grid">
2026-07-05 01:19:07 +02:00
<div class="info-item fullwidth"><span class="info-label">Filename</span><span id="fileName" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Format</span><span id="fileFormat" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Sample Rate</span><span id="fileSampleRate" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Channels</span><span class="info-value"><span id="fileChannels">-</span> <span id="fileStereoMode" class="info-value-tag"></span></span></div>
<div class="info-item"><span class="info-label">Duration</span><span id="fileDuration" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">Bitrate</span><span class="info-value"><span id="fileBitrate">-</span> <span id="fileBitrateMode" class="info-value-tag"></span></span></div>
<div class="info-item"><span class="info-label">File Size</span><span id="fileSize" class="info-value">-</span></div>
<div class="info-item"><span class="info-label">BPM</span><span class="info-value bpm-value"><span id="fileBPM">-</span><span class="bpm-buttons"><button class="bpm-btn" id="bpmHalf" title="Halve">÷2</button><button class="bpm-btn" id="bpmDouble" title="Double">×2</button></span></span></div>
2026-07-05 00:44:08 +02:00
</div>
</section>
<section id="metadataSection">
<h2>Metadata Tags</h2>
<div id="metadataGrid" class="metadata-grid"></div>
<div id="metadataEmpty" class="metadata-empty">No metadata tags found</div>
</section>
<section id="spectrumSection">
<h2>Power Spectrum</h2>
<div class="chart-container">
<canvas id="spectrumChart"></canvas>
</div>
</section>
<section id="metricsSection">
<h2>Key Metrics</h2>
<div class="metrics-grid">
2026-07-05 01:19:07 +02:00
<div class="metric-card" title="Peak Magnitude - highest spectral amplitude in dBFS (0 dBFS = full scale).&#10;&#10;How to read:&#10;• > -1 dBFS: Heavily limited/mastered, extremely loud&#10;• -6 to -1 dBFS: Typical modern mastering&#10;• -12 to -6 dBFS: Conservative, good headroom&#10;• < -12 dBFS: Quiet or attenuated&#10;&#10;Significance: Combined with noise floor, indicates the effective dynamic range of the audio.">
2026-07-05 00:44:08 +02:00
<div class="metric-label">Peak</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="mPeak">-</div>
2026-07-05 00:44:08 +02:00
</div>
2026-07-05 01:19:07 +02:00
<div class="metric-card" title="Spectral Cutoff - highest frequency bin with significant energy above floor, as Hz and % of Nyquist (sample rate / 2).&#10;&#10;How to read:&#10;• ≥ 20 kHz (≥ 90%): Full bandwidth - lossless or high-bitrate lossy (320k MP3, 256k AAC)&#10;• 16-19 kHz (70-85%): ~192k MP3 / ~128k AAC&#10;• 14-16 kHz (60-70%): ~128k MP3&#10;• 10-14 kHz (45-60%): ~64-96k lossy&#10;• < 10 kHz (< 45%): Very low bitrate (< 64k)&#10;&#10;Significance: The single strongest indicator of lossy compression. Lower cutoff = more aggressive compression.">
2026-07-05 00:44:08 +02:00
<div class="metric-label">Cutoff</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="mCutoff">-</div>
2026-07-05 00:44:08 +02:00
</div>
2026-07-05 01:19:07 +02:00
<div class="metric-card" title="Transition Bandwidth (Steepness) - frequency gap between the -20 dBFS and -60 dBFS points. Measures how abruptly high-frequencies drop off.&#10;&#10;How to read:&#10;• < 500 Hz: Brick-wall cutoff - transcode or low-bitrate lossy&#10;• 500-1500 Hz: Moderately sharp - suspicious, may be higher-bitrate lossy&#10;• 1500-3000 Hz: Gradual rolloff - typical of genuine lossless&#10;• > 3000 Hz: Very gradual - natural rolloff from analog sources&#10;&#10;Significance: The strongest differentiator between genuine lossless and transcodes. Lossy encoders produce artificially steep lowpass filters.">
2026-07-05 00:44:08 +02:00
<div class="metric-label">Steepness</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="mSteepness">-</div>
2026-07-05 00:44:08 +02:00
</div>
2026-07-05 01:19:07 +02:00
<div class="metric-card" title="Noise Floor - average magnitude in the top quarter of the spectrum (> 3/4 Nyquist), in dB relative to peak.&#10;&#10;How to read:&#10;• < -90 dB: Excellent - extremely clean, high dynamic range&#10;• -80 to -90 dB: Very good - typical of quality lossless&#10;• -70 to -80 dB: Moderate - some quantization noise present&#10;• -60 to -70 dB: Elevated - suggests lossy compression or analog hiss&#10;• > -60 dB: High - almost certainly lossy compressed&#10;&#10;Significance: Lossy codecs raise the noise floor through quantization. High noise floor + sharp cutoff = strong transcode indicator.">
2026-07-05 00:44:08 +02:00
<div class="metric-label">Noise Floor</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="mNoise">-</div>
2026-07-05 00:44:08 +02:00
</div>
2026-07-05 01:19:07 +02:00
<div class="metric-card" title="Spectral Roughness (CoV) - coefficient of variation (σ/μ) of spectral magnitudes in the 60-95% region below cutoff. Measures how uneven/scalloped the spectrum is.&#10;&#10;How to read:&#10;• > 0.40: Strong spectral scalloping - clear lossy encoding signature&#10;• 0.30-0.40: Moderate scalloping - suspicious, may be high-bitrate lossy&#10;• 0.15-0.30: Mild unevenness - needs band-ratio correlation&#10;• < 0.15: Smooth spectrum - typical of genuine lossless&#10;&#10;Significance: Lossy codecs create a comb-filter pattern via frequency-bin quantization. High roughness = high probability of lossy encoding.">
2026-07-05 00:44:08 +02:00
<div class="metric-label">Roughness</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="mRoughness">-</div>
2026-07-05 00:44:08 +02:00
</div>
2026-07-05 01:19:07 +02:00
<div class="metric-card" title="Band Ratio - ratio of average spectral energy in 16-20 kHz band to 12-16 kHz band. Detects unnatural high-frequency tilt.&#10;&#10;How to read:&#10;• < 0.5: Normal natural rolloff in top octave&#10;• 0.5-0.9: Typical for most recordings&#10;• 0.9-1.0: Flat HF - possible spectral reshaping from lossy encoding&#10;• > 1.0: Inverted tilt (more energy at 16-20 kHz than below) - strong transcode signature&#10;&#10;Significance: Used as secondary criterion when cutoff is high but roughness is ambiguous. Transcodes often show unnatural energy distribution in the top octaves.">
2026-07-05 00:44:08 +02:00
<div class="metric-label">Band Ratio</div>
2026-07-05 01:19:07 +02:00
<div class="metric-value" id="mBandRatio">-</div>
2026-07-05 00:44:08 +02:00
</div>
</div>
<div class="analysis-info">
2026-07-05 01:19:07 +02:00
<span><strong>Windows:</strong> <span id="windowsCount">-</span></span>
<span><strong>FFT:</strong> <span id="fftSize">-</span></span>
2026-07-05 00:44:08 +02:00
</div>
</section>
2026-07-05 01:19:07 +02:00
<section id="decisionSection" title="Verdict - the overall classification of the audio file based on spectral analysis.&#10;&#10;NATIVE: File matches its declared lossy format at a consistent bitrate (single encode).&#10;UPSCALED: Lossy file re-encoded to a higher bitrate than the original source.&#10;GENUINE: File appears to be native lossless (no transcode artifacts detected).&#10;TRANSCODE: Lossy source re-encoded to a lossless format (e.g. 128k MP3 → FLAC).&#10;&#10;The verdict is decided by: cutoff ratio, transition bandwidth (steepness), roughness (CoV), and band-ratio analysis against adaptive thresholds.">
2026-07-05 00:44:08 +02:00
<h2>Decision</h2>
2026-07-05 01:19:07 +02:00
<div id="decision" class="decision-text">-</div>
<div class="confidence-container" title="Validity Confidence - how strongly the evidence supports the verdict, from 0% (random chance) to 100% (certain).&#10;&#10;85-100%: Very strong evidence - multiple criteria agree decisively.&#10;70-84%: Strong evidence - most criteria support the verdict.&#10;50-69%: Moderate evidence - some criteria triggered but not all.&#10;30-49%: Weak evidence - marginal, borderline detection.&#10;0-29%: Inconclusive - possible false positive, or audio is ambiguous.&#10;&#10;For lossy files: confidence is based on how far below the expected cutoff ratio the file sits.&#10;For lossless candidates: confidence blends cutoff ratio margin, steepness margin, and (above 80% Nyquist) secondary roughness + band-ratio criteria.">
2026-07-05 00:44:08 +02:00
<div class="confidence-label">Validity Confidence</div>
<div class="confidence-bar-bg">
<div id="confidenceBar" class="confidence-bar-fill" style="width:0%">0%</div>
</div>
2026-07-05 01:19:07 +02:00
<div id="confidenceLabel" class="confidence-label-small">-</div>
2026-07-05 00:44:08 +02:00
</div>
</section>
2026-07-05 01:19:07 +02:00
<section id="factors" title="Factor Analysis - step-by-step breakdown of the detection criteria used to reach the verdict.&#10;&#10;① PRIMARY criteria (applied to ALL files):&#10; Compares transition bandwidth (steepness) to an adaptive threshold based on cutoff ratio.&#10; If steepness is BELOW the threshold, the spectrum has an artificially sharp cutoff → transcode flagged.&#10; Files with cutoff_ratio ≥ 0.99 (full spectrum) bypass this check.&#10;&#10;② SECONDARY criteria (applied when cutoff_ratio > 0.80):&#10; Checks spectral roughness (CoV) and band ratio for additional transcode signatures.&#10; High roughness (> 0.40) alone is sufficient to confirm. Moderate roughness needs band-ratio corroboration.&#10;&#10;Lossy files: checked only for cutoff ratio vs expected range for the bitrate.&#10;✓ = criterion met (supports transcode/upscaled), ✗ = criterion not met (supports genuine/native).">
2026-07-05 00:44:08 +02:00
<section id="dataSection">
<h2>Analysis Data</h2>
<div class="table-container">
<table id="dataTable">
<thead>
<tr><th>Parameter</th><th>Value</th></tr>
</thead>
<tbody></tbody>
</table>
</div>
</section>
</div>
<footer>
2026-07-05 01:19:07 +02:00
<p>Based on <a href="https://codeberg.org/armin/tcd" target="_blank">tcd</a> - Psychoacoustic audio authenticity analysis</p>
2026-07-05 00:44:08 +02:00
</footer>
</div>
<script src="js/metadata.js"></script>
<script src="js/analyzer.js"></script>
<script src="js/app.js"></script>
</body>
</html>