Skip to content

Repository files navigation

CI

Identify any song from your microphone or an audio file.
Validated audio pipeline · Multi-backend matching · Flask web UI · Terminal output


Audio Recognition

Identify songs from your microphone or an audio file with validated multi-backend matching

Overview

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.

Performance

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.

Architecture

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;
Loading

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.

Quickstart

Windows PowerShell

  1. Create and activate a venv:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
  1. Install runtime dependencies and copy configuration:
python -m pip install -r requirements.txt
Copy-Item .env.example .env
  1. Add provider values to .env if recognition is needed. Verify host tools when using non-WAV uploads or AcoustID:
ffmpeg -version
fpcalc -version
  1. Run the Flask development server:
python web/app.py
# open http://127.0.0.1:5000
  1. Run CLI when terminal recognition is needed:
python main.py

macOS/Linux

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
cp .env.example .env
python web/app.py

Install 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:app

Production 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.

Docker

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 --build

The 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-recognition

The 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:5000

Project Structure

Core source modules now live under shazam_project/:

  • shazam_project/config.py
  • shazam_project/recorder.py
  • shazam_project/fft_analyze.py
  • shazam_project/matcher.py
  • shazam_project/display.py

Entrypoints remain:

  • main.py (CLI)
  • web/app.py (canonical Flask browser app and API)
  • web/templates/index.html and web/static/ (same-origin browser assets)

Configuration

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.

Web UI

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:

  • /healthz is a dependency-free process liveness check and returns HTTP 200 when Flask is serving.
  • /readyz checks 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/status reports 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.

Testing

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 off

tests/ 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.

Limitations

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_match result 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 rate limits

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_URL
  • SUPABASE_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 --local

For 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.

Notes & Tips

  • Record in a quiet space and keep the mic near the audio source.
  • For AcoustID, install Chromaprint (fpcalc): macOS brew install chromaprint, Debian/Ubuntu apt install libchromaprint-tools.
  • FFmpeg is required for non-WAV web uploads and is not required for WAV uploads.

Contributing

Pull requests are welcome. For major changes, open an issue first. Run python -m pytest -q and python -m coverage report before submitting.


Example Output

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:

FFT spectrum output

This image is diagnostic output from shazam_project.fft_analyze.analyze_audio; the canonical browser page is served by python web/app.py at /.


Web API Reference

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.


Notes

  • 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.

Roadmap

  • Add CI — run pytest and coverage on every push and pull request
  • CLI flags: --mode, --duration, --file for unattended/scripted use
  • Local match history saved as JSON
  • --no-open-image flag for headless environments
  • Structured logging in shazam_project/matcher.py for easier debugging
  • Flask integration tests for the browser entry point, static assets, API status, and upload outcomes

License

MIT — free to use and modify.

About

A Python DIY Shazam clone that records audio via mic or file, runs FFT analysis to visualise frequency spectrum, and identifies songs using RapidAPI. Returning the title, artist, and album art as output.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages