Files
2026-05-27 23:29:53 +01:00

68 lines
5.6 KiB
Markdown

# Migrate `suaveplan-audio-fingerprint` (Python tier) into genesis
**Package**: `@suaveplan/audio-fingerprint`
**Status**: PROPOSED
**Type**: new-package (migration from upstream Python tier)
- **Design**: ./design.md
- **Spec delta**: ./specs/audio-fingerprint/spec.md
## Why
`@suaveplan/react-audio-fingerprint` already ships a browser-tier TypeScript implementation of Shazam-style audio fingerprinting (peak detection on a magnitude spectrogram). For corpus-scale identification, recognised broadcast monitoring, and noise-robust matching of compressed or modified audio, the browser implementation is intentionally lightweight. Upstream `/mnt/s/web/suaveplan/packages/python/audio-fingerprint/` carries the high-accuracy backend: a librosa + scipy chromagram pipeline wrapped in a FastAPI service, plus a typed HTTP client (`PythonAudioService`) that browser code calls.
The React sibling already reserves a `python-backend` subpath for this service. Without the Python tier the high-accuracy path is unreachable. This change migrates the Python service plus the TypeScript HTTP client to `packages/media/audio/audio-fingerprint/` so the React package can stop deferring the path and downstream apps can run a real fingerprint backend behind their browser surface.
## What changes
- ADDED: `@suaveplan/audio-fingerprint` package at `packages/media/audio/audio-fingerprint/` (Python tier + TS HTTP client bridge).
- ADDED: `src/fingerprint.py` — `AudioFingerprinter` class (chromagram + 2D peak detection + constellation hashing + Jaccard matching + median time-offset estimation).
- ADDED: `src/api/app.py` — FastAPI application exposing `GET /`, `POST /fingerprint`, `POST /match` with CORS for `localhost:3000` + `localhost:5173`.
- ADDED: `src/client/python-audio-service.ts` — typed `PythonAudioService` HTTP client (`healthCheck` / `fingerprint` / `match`) with `AbortController`-based timeouts.
- ADDED: `src/types/audio-fingerprint.types.ts` — `HealthCheckResponse`, `AudioFingerprint`, `FingerprintRequest` / `Response`, `MatchRequest` / `Response`, `ErrorResponse`, `AudioFingerprintConfig`.
- ADDED: `pyproject.toml` + `environment.yml` for the Python runtime (librosa, scipy, numpy, fastapi, uvicorn, soundfile, python-multipart; dev: pytest, pytest-cov, pytest-asyncio, httpx, ruff, mypy).
- ADDED: `pytest --cov --cov-fail-under=100` Python test suite at `tests/test_fingerprint.py` + `tests/test_api.py`.
- ADDED: TS bridge vitest suite at `tests/python-audio-service.test.ts` using a real `fetch` mock against an in-memory ASGI surface emulator and `AbortController` for timeout semantics.
## Out of scope
- Production deployment manifests (Dockerfile, Helm chart) — handled per-app deploy, not in the library.
- Wiring the React sibling's `python-backend` subpath to consume this client — separate follow-up so the React change has its own spec delta.
- ChromaPrint / AcoustID interoperability — different algorithm; would be a peer package, not a feature here.
- Native (non-FastAPI) RPC transport — the upstream uses HTTP; we preserve parity. A gRPC adapter is a future change.
## Source mapping
This change derives from `/mnt/s/web/suaveplan/packages/python/audio-fingerprint/`. There is no `enterprise/audio-fingerprint.md` audit on main (the upstream is a Python-tier service rather than a TS library), so the source mapping references the upstream source tree directly.
| Requirement | Source lines | Notes |
|---|---|---|
| Chromagram fingerprinting | `src/fingerprint.py` L67-L90 | verbatim algorithm, repackaged |
| 2D peak detection | `src/fingerprint.py` L92-L132 | verbatim (`scipy.ndimage.maximum_filter` (3,5)) |
| Constellation hashing | `src/fingerprint.py` L134-L171 | verbatim fan-pairing |
| Full-file pipeline | `src/fingerprint.py` L173-L213 | verbatim |
| Jaccard matching + time offset | `src/fingerprint.py` L215-L267 | verbatim |
| FastAPI `/` + `/fingerprint` + `/match` | `src/api/app.py` L31-L171 | verbatim endpoints, CORS preserved |
| TS HTTP client | `src/PythonAudioService.ts` L45-L175 | renamed file to kebab-case for genesis convention; surface preserved |
| Response / request types | `types/index.ts` L7-L111 | preserved field-for-field |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| `client.timeout` propagates `AbortController` reasons | Upstream throws on timeout but the message tells callers nothing actionable; genesis surfaces `"Request timeout after Nms: <endpoint>"` already-present in the upstream client (kept verbatim, listed here because it appears as its own spec scenario). |
## Verification approach
- Python: `pytest --cov=src --cov-fail-under=100` from the package directory; covers `fingerprint.py` and `api/app.py` end-to-end including FastAPI `TestClient` and `httpx.ASGITransport` async paths.
- TS: vitest suite at `tests/python-audio-service.test.ts` using runner-neutral imports from `@suaveplan/testing/runner`, real `fetch` stubbed via a script-injected handler (no `vi.mock`), pinning every public method, the timeout path, the error-response parsing path, and the unknown-error fallback path.
- Both gates exit with 100% line + branch coverage on the touched modules.
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/audio-fingerprint` to enumerate. The package is new; the only logical dependent is `@suaveplan/react-audio-fingerprint`, whose `python-backend` subpath is intentionally not wired in this change.
- `@suaveplan/react-audio-fingerprint` — [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: the `python-backend` subpath wiring is deferred to its own change; this migration is additive and does not modify the React surface)