4.9 KiB
api/app.py — FastAPI service
(no submodules)
Purpose
api/app.py wraps the AudioFingerprinter core into a FastAPI HTTP service. It is the network surface the TypeScript PythonAudioService client talks to from browsers (via the React-tier sibling) and from server-side JavaScript runtimes. The application exposes a health-check endpoint and two operational endpoints (POST /fingerprint, POST /match) that accept multipart file uploads and return JSON. It is intentionally a thin REST shell: every business decision (chromagram parameters, peak threshold, fan value, min matches) is driven by client-supplied form fields.
The service is deployment-tier code — it runs under uvicorn and is intended to live behind a reverse proxy in production. CORS is pre-configured for the two local development origins (http://localhost:3000 and http://localhost:5173); add production origins via your deployment-time CORSMiddleware override.
Features
GET /health-check returning{"status": "ok", "service": "audio-fingerprinting"}.POST /fingerprintaccepting an audio file, optionalpeak_thresholdandfan_value, returning fingerprint hashes and statistics.POST /matchaccepting two audio files, optionalpeak_threshold,fan_value,min_matches, returning the match decision, count, confidence, and time offset.- Temporary-file lifecycle: each upload is persisted to
NamedTemporaryFile, processed, and unlinked in afinallyblock — including on the failure path. - Uniform HTTP-500 error envelope: pipeline exceptions are caught and re-raised as
HTTPException(status_code=500, detail="Failed to ... : <message>"). - CORS pre-configured for
localhost:3000andlocalhost:5173.
Usage: Basic
# Start the service
micromamba run -n suaveplan-audio-fingerprint \
uvicorn src.api.app:app --host 0.0.0.0 --port 8000
# Health check
curl http://localhost:8000/
# Fingerprint a single file
curl -X POST http://localhost:8000/fingerprint -F "file=@track.mp3"
Usage: Advanced
# Tune the algorithm parameters at request time
curl -X POST http://localhost:8000/fingerprint \
-F "file=@track.flac" \
-F "peak_threshold=0.6" \
-F "fan_value=20"
# Match two files with a stricter min_matches threshold
curl -X POST http://localhost:8000/match \
-F "query_file=@query.wav" \
-F "reference_file=@reference.wav" \
-F "peak_threshold=0.5" \
-F "fan_value=15" \
-F "min_matches=25"
# Programmatic in-process testing via httpx ASGITransport
from httpx import ASGITransport, AsyncClient
from src.api.app import app
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
response = await ac.get("/")
assert response.json() == {"status": "ok", "service": "audio-fingerprinting"}
API Reference
| Method | Path | Form fields | Response |
|---|---|---|---|
GET |
/ |
— | {"status": "ok", "service": "audio-fingerprinting"} |
POST |
/fingerprint |
file (UploadFile, required), peak_threshold (float, default 0.5), fan_value (int, default 15) |
{filename, duration, peaks_count, fingerprints_count, fingerprints[{hash,time,delta}]} |
POST |
/match |
query_file (UploadFile, required), reference_file (UploadFile, required), peak_threshold (float, default 0.5), fan_value (int, default 15), min_matches (int, default 10) |
{query_file, reference_file, query_duration, reference_duration, matched, match_count, confidence, time_offset} |
Failure paths
- Missing required upload(s) -> HTTP 422 (FastAPI validation error envelope).
- Pipeline failure (unreadable audio, decode error) -> HTTP 500 with
detailstarting withFailed to process audio file:(for/fingerprint) orFailed to match audio files:(for/match).
Implementation Notes
- A single module-level
AudioFingerprinter()instance is reused across requests. It has no mutable per-request state; only its analysis parameters are shared, and those are configuration-stable. tempfile.NamedTemporaryFile(delete=False)is used so the descriptor can be closed while the file is still on disk for librosa to open by path. The unlink lives in afinallyclause and usesmissing_ok=Trueso the cleanup is idempotent even if librosa already moved or removed the file.- The
matchendpoint persists both uploads inside a single nestedwithblock; this guarantees both temp paths are bound before either pipeline call runs, simplifying the cleanup in thefinallyclause. CORSMiddlewareis wired withallow_credentials=True,allow_methods=["*"],allow_headers=["*"]. The TestClient suite exercises the preflight (OPTIONS) path forhttp://localhost:3000to ensure the configuration is live.- The
version="0.1.0"field on theFastAPI(...)constructor matches thepyproject.tomlandpackage.jsonversions; bump all three in lockstep at graduation to1.0.0.