Identify any song from your microphone or an audio file.
Validated audio pipeline · Multi-backend matching · Flask web UI · Terminal output
Identify songs from your microphone or an audio file with validated multi-backend matching
DIY Shazam captures audio from the CLI microphone/file path or the Flask browser UI, normalizes it through one bounded audio pipeline, and identifies tracks using RapidAPI/Shazam, AcoustID, AudD, or local spectrogram peaks and constellation hash pairs. FFT output is a diagnostic visualization only; it is not the recognition algorithm. Flask serves the complete browser UI and JSON API from one origin.
| Metric | Result |
|---|---|
| Average recognition time (RapidAPI backend) | See the imported benchmark report below |
| Average recognition time (AudD backend) | See the imported benchmark report below |
| Test set accuracy | See the imported benchmark report below |
| Minimum audio duration | 1 s by default (configurable) |
| Maximum audio duration | 30 s by default (configurable) |
| Maximum upload size | 10 MiB by default (configurable) |
| Platforms tested | Not established by this branch's validation |
No complete real-world benchmark has been imported. Run the documented evaluation only after assembling a legally reusable corpus and supplying the operator metadata and provider configuration.
flowchart LR
A[CLI / Flask Browser UI] --> B[Audio Input\n(mic or upload)]
B --> C[Validate and normalize\nmono float32 / internal rate]
C --> D[FFT diagnostic only]
C --> E[Write 16-bit PCM WAV temp]
E --> F{Matcher Backends}
F -->|RapidAPI| G[Shazam]
F -->|AcoustID| H[AcoustID]
F -->|AudD| I[AudD]
F -->|Local hashes| J[Peak/hash index]
G & H & I & J --> K[Normalized Result]
K --> L[Display (CLI) / JSON (Web)]
classDef blue fill:#ffffff,stroke:#1E90FF,stroke-width:2px,color:#1E90FF;
class A,B,C,D,E,F,G,H,I,J,K,L blue;
Theme: black / white / blue — white nodes with a professional DodgerBlue accent (#1E90FF). The browser UI is served directly by Flask; there is no separate browser bundle.
- Create and activate a venv:
python -m venv .venv
.\.venv\Scripts\Activate.ps1- Install runtime dependencies and copy configuration:
python -m pip install -r requirements.txt
Copy-Item .env.example .env- Add provider values to
.envif recognition is needed. Verify host tools when using non-WAV uploads or AcoustID:
ffmpeg -version
fpcalc -version- Run the Flask development server:
python web/app.py
# open http://127.0.0.1:5000- Run CLI when terminal recognition is needed:
python main.pypython3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
cp .env.example .env
python web/app.pyInstall host tools outside Docker when needed: macOS uses brew install ffmpeg chromaprint;
Debian/Ubuntu uses sudo apt-get install ffmpeg libchromaprint-tools. These commands are
only for local host setup; the production image installs the same runtime tools itself.
Run the CLI with python main.py. On macOS/Linux, a production-style local WSGI process is:
APP_ENV=production PORT=8000 gunicorn --config gunicorn.conf.py web.app:appProduction mode requires the server-only Supabase quota configuration and at least one recognition backend. It fails closed with HTTP 503 when those requirements are unavailable.
The reproducible production image installs Python, Gunicorn, FFmpeg, Chromaprint/fpcalc,
audio libraries, and curl for health checks. It runs as UID/GID 10001, uses Gunicorn, and
reads the platform-provided PORT.
Copy-Item .env.example .env
docker compose up --buildThe compose service uses Gunicorn with a read-only root filesystem and a bounded 64 MiB
/tmp tmpfs. Its default APP_ENV=development keeps the local quickstart usable without
Supabase; configure .env and set APP_ENV=production when testing the fail-closed
production path. A direct production start is:
docker build -t audio-recognition .
docker run --rm -p 5000:5000 --env-file .env audio-recognitionThe same commands work from macOS/Linux after replacing Copy-Item with cp.
The canonical Flask development command remains:
python web/app.py
# open http://127.0.0.1:5000Core source modules now live under shazam_project/:
shazam_project/config.pyshazam_project/recorder.pyshazam_project/fft_analyze.pyshazam_project/matcher.pyshazam_project/display.py
Entrypoints remain:
main.py(CLI)web/app.py(canonical Flask browser app and API)web/templates/index.htmlandweb/static/(same-origin browser assets)
Supported env vars (see shazam_project.config.load_config()): AUDD_API_TOKEN, ACOUSTID_API_KEY, FP_CALC_PATH, RAPIDAPI_KEY, and optional LOCAL_FINGERPRINT_INDEX (with FINGERPRINT_INDEX_PATH accepted as a legacy alias). The shared audio contract is controlled by INTERNAL_SAMPLE_RATE, MIN_AUDIO_SECONDS, MAX_AUDIO_SECONDS, MAX_UPLOAD_BYTES, and FFMPEG_TIMEOUT_SECONDS; provider WAVs are always fixed 16-bit PCM. Matcher order is RapidAPI → AcoustID → AudD → local fingerprint index.
python web/app.py serves /, /static/*, /api/match, and /api/status from the same origin. CLI file mode accepts WAV/PCM files. Web uploads support WAV, MP3, M4A, AAC, OGG, FLAC, and WEBM; non-WAV web uploads require FFmpeg on PATH and are converted before decoding. The browser also supports microphone recording, manual stop, waveform visualization, loading/error/no-match states, light/dark theme persistence, and session-only recognition history.
Every input is downmixed to mono float32 samples in [-1, 1] and resampled to 44,100 Hz by default. Provider adapters receive temporary mono 16-bit PCM WAV files. Inputs shorter than 1 second, longer than 30 seconds, or larger than 10 MiB are rejected by default; all three limits are configurable.
The deployment endpoints are:
/healthzis a dependency-free process liveness check and returns HTTP 200 when Flask is serving./readyzchecks production configuration, writable temporary storage, FFmpeg, fpcalc when AcoustID is enabled, Supabase quota availability, and at least one recognition backend. It returns HTTP 503 with stable check names when not ready; it never returns secrets, paths, exception text, or database details./api/statusreports non-secret backend/tool flags, quota mode, limits, and audio settings.
Gunicorn defaults to two workers, a 45-second request timeout, a 15-second graceful shutdown
window, and a five-second keep-alive. Provider requests and FFmpeg conversion remain bounded
by their existing 15-second timeouts. MAX_UPLOAD_BYTES, duration limits, Flask's request
limit, and temporary-file cleanup bound upload and conversion resource use.
Run the Python tests and coverage locally:
python -m pytest -q
python -m coverage run --branch --source=shazam_project,web,scripts -m pytest -q
python -m coverage report --fail-under=70 --show-missing
ruff format --check .
ruff check .
pip-audit -r requirements.txt -r requirements-dev.txt --progress-spinner offtests/ covers configuration loading and backend combinations, mocked microphone failures and cleanup, normalized audio, mocked provider flows and fallback, Flask routes, safe incomplete metadata rendering contracts, rate limits, FFmpeg failures, Supabase failures, and a generated WAV end-to-end test. The generated WAV is created in memory and contains no third-party recording.
GitHub Actions runs pytest on every push and pull request across Python 3.10, 3.11, and 3.12, Ruff formatting and lint, a Python 3.12 branch-coverage gate at 70%, pip-audit, and Gitleaks secret scanning. The coverage job publishes coverage.xml as an artifact. Provider network calls, Supabase credentials, fpcalc, FFmpeg, and microphone hardware are mocked or tested through stable failure paths in CI; they are excluded from the release gate because they require external credentials or host devices.
Direct Python dependencies use reviewed major-compatible ranges in requirements.txt and requirements-dev.txt. To update one, review its release notes and Python 3.10–3.12 compatibility, edit its range, install from both requirement files, then run the full pytest, coverage, Ruff, compile, diff, and pip-audit checks. Do not add credentials or resolve updates from a developer's private environment.
For the real-world comparison, see evaluation/README.md. It validates a source catalog, records resumable speaker-to-microphone clips at 4, 8, and 15 seconds, caches deterministic backend results without credentials, builds the local landmark-hash index from clean source tracks, and compares the local backend against all three provider backends.
Recognition is not guaranteed outside the happy path. The main failure modes are:
- Background noise and recording quality: speech, room echo, speaker distortion, very low volume, clipping, or music mixed with other sounds can hide the spectral peaks used by fingerprinting.
- Catalog coverage: a provider can only return tracks in its database, while the local matcher can only identify tracks present in its local fingerprint index. A
no_matchresult does not prove that the audio is invalid. - Language and regional catalog differences: the fingerprinting itself is not English-specific, but provider metadata and catalog coverage vary by language, region, release, and recording availability.
- Live, cover, remix, and alternate versions: crowd noise, changed instrumentation, tempo or pitch, medleys, and different arrangements may fail to match or may be returned as the closest studio recording rather than the exact performance.
The evaluation dataset is designed to measure these cases separately. Until that dataset is recorded and run through all configured backends, the README does not claim a general accuracy percentage.
Production quota enforcement uses the exposed-but-restricted public.check_api_quota preflight and public.consume_api_quota RPCs created by supabase/migrations/20260801145213_production_rate_limits.sql. The preflight is read-only and runs before upload saving; the final operation locks one HMAC-keyed usage row and checks cooldown, daily, and monthly limits before incrementing both counters atomically after valid audio decoding. Both functions are SECURITY INVOKER, use an explicit safe search path, and are executable only by service_role. The public.api_usage table has RLS enabled, no public policies, and no grants to anon or authenticated; the private schema is not exposed.
Required server-only configuration:
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY— never put this value in JavaScript, HTML, API responses, logs, or screenshots.CLIENT_ID_HMAC_SECRET— a separate secret used to derive the stored client identifier; raw IP addresses are never stored.
The local development quickstart uses APP_ENV=development, so local Flask matching works without Supabase and uses a bounded, expiring in-memory limiter. To exercise production behavior, set APP_ENV=production and provide all three server-only values above; missing configuration or a quota-service failure returns HTTP 503 rather than assuming zero usage. APP_ENV=production should be configured separately in the deployment environment, never copied blindly into a local .env.
The migration workflow is:
supabase start
supabase db reset
supabase db advisors --local --type all --fail-on warn
supabase migration list --localFor a disposable linked development project, verify with supabase link --project-ref <project-ref>, supabase db push --dry-run, supabase db push, supabase db advisors --linked --type all --fail-on warn, and supabase migration list --linked. Never run supabase db reset --linked against production. The migration was created with supabase migration new production_rate_limits.
/api/status reports the quota mode, configured daily/monthly limits, cooldown, and whether production-grade quotas are enabled; it never reports client hashes or usage rows.
INTERNAL_API_SECRET, when configured, prevents the current browser UI from calling /api/match unless a deliberate server-side authentication design supplies X-API-Secret; the secret is never placed in JavaScript. An allowed Origin or Referer can never authenticate a request. Forwarded client addresses are ignored unless both TRUSTED_PROXY_COUNT and an allowlisted TRUSTED_PROXY_IPS chain are configured; every trusted proxy hop is validated when more than one hop is configured. Flask debug mode is enabled only when APP_ENV=development.
- Record in a quiet space and keep the mic near the audio source.
- For AcoustID, install Chromaprint (
fpcalc): macOSbrew install chromaprint, Debian/Ubuntuapt install libchromaprint-tools. - FFmpeg is required for non-WAV web uploads and is not required for WAV uploads.
Pull requests are welcome. For major changes, open an issue first.
Run python -m pytest -q and python -m coverage report before submitting.
Listen via microphone or load a file? (mic/file): mic
Recording for 8 seconds...
Recognition uses the normalized recording and configured matcher backends.
Song: Blinding Lights
Artist: The Weeknd
[Album art opens in image viewer]
FFT spectrum for a sample clip:
This image is diagnostic output from shazam_project.fft_analyze.analyze_audio; the canonical browser page is served by python web/app.py at /.
| Endpoint | Method | Description |
|---|---|---|
/api/match |
POST | Upload an audio file for recognition. Returns JSON. |
/api/status |
GET | Reports configured backends, ffmpeg, fpcalc status. |
/healthz |
GET | Dependency-free process liveness check. |
/readyz |
GET | Configuration and dependency readiness check. |
Example — cURL:
curl -X POST http://localhost:5000/api/match \
-F "file=@song.wav"Example — Response:
{
"status": "matched",
"title": "Blinding Lights",
"artist": "The Weeknd",
"album": "After Hours",
"image": "https://..."
}Public status is one of: matched · no_match · not_configured · invalid_audio · rate_limited · error. Provider attempts contain only backend/status/error codes and generic safe messages; raw provider payloads, credentials, local paths, and stack traces are not public response data.
- Record in a quiet environment for best accuracy
- CLI mic mode requires a working input device; the web UI uses browser microphone
- File mode (CLI) accepts WAV/PCM only; the web UI supports the documented formats and requires FFmpeg for non-WAV uploads.
- Windows, macOS, and Linux support has not been independently verified by this branch's evidence.
- Add CI — run pytest and coverage on every push and pull request
- CLI flags:
--mode,--duration,--filefor unattended/scripted use - Local match history saved as JSON
-
--no-open-imageflag for headless environments - Structured logging in
shazam_project/matcher.pyfor easier debugging - Flask integration tests for the browser entry point, static assets, API status, and upload outcomes
MIT — free to use and modify.
