One local MP3
Drag, drop, or choose a file. Recommended limit: 50 MB and 15 minutes. Validate both extension and decoded media—not only the browser-provided MIME type.
Private audio analysis
A focused web app that accepts one MP3 and returns its musical key, tempo, and tuning reference—then forgets the file ever existed.
Keep version one deliberately narrow: a single track goes in, a small and honest result comes out.
Drag, drop, or choose a file. Recommended limit: 50 MB and 15 minutes. Validate both extension and decoded media—not only the browser-provided MIME type.
Key and mode (C♯ minor), tempo (128 BPM), and tuning reference (A4 ≈ 447.8 Hz). Add a confidence label to every estimate.
No history, login, playlists, cloud storage, metadata editing, sharing links, payment system, or uploaded-file playback from the server in v1.
Server-side processing is the best first version: it behaves consistently across phones and desktops, keeps DSP code straightforward, and fits cleanly in one Coolify container.
The browser streams a multipart upload. The API writes to a unique temporary directory, FFmpeg decodes it to mono PCM, Python analyzes it, and the directory is removed before the request completes.
WebAssembly could remove the upload entirely, but MP3 decoding, memory pressure, worker setup, and behavior on lower-powered mobile devices make it a riskier MVP. Treat it as a later privacy/performance experiment.
Nothing is written to a volume. Container logs must never include filenames, audio bytes, or request bodies.
Make this a uv project with a committed pyproject.toml, uv.lock, and supported Python version. Use Ruff for formatting and linting, and Ty for static type checking.
The same checks run locally and in CI before a change is mergeable. Runtime and development dependencies are declared in the project metadata; the lockfile is updated intentionally with dependency changes.
Tuning and key are coupled. Correcting the pitch-class representation with the estimated tuning reference makes the later key estimate more reliable.
The interface should feel like a utility, not a dashboard. Make privacy visible at the moment of upload.
| State | What the user sees | Behavior |
|---|---|---|
| Empty | Large drop zone, “Choose MP3,” size limit, and “Your file is deleted immediately after analysis.” | Accept one file; keyboard and touch accessible. |
| Ready | Filename, human-readable size, Analyze button, Remove button. | Client-side type/size check saves an unnecessary upload. |
| Analyzing | Indeterminate progress with rotating messages: uploading, decoding, finding tempo, measuring tuning. | Disable duplicate submits; allow cancellation with AbortController. |
| Result | Three large result cards plus confidence, cents offset, and “Analyze another.” | Do not put results in the URL or browser persistence. |
| Error | Plain-language reason: too large, unsupported/corrupt file, timed out, or not enough musical pitch. | Keep the selected file only when retrying makes sense. |
Two endpoints are enough. Avoid job queues until real traffic or request-duration limits prove they are necessary.
Request: multipart/form-data with a single file field. Response:
Return 200 {"status":"ok"}. Do not run a sample analysis in the health check; it should only confirm that the HTTP process is ready.
Use 400 for malformed requests, 413 for size limits, 415 for unsupported content, 422 for analyzable audio with no reliable musical estimate, 429 for throttling, and 500 for unexpected failures.
Privacy is an implementation property, not only a sentence in the interface.
A vertical slice first, then accuracy and hardening. The sequence keeps every phase runnable.
Audio algorithms fail gracefully only when the test set includes awkward music, not just clean electronic loops.
Connect the Git repository as a Dockerfile application. The service must listen on 0.0.0.0, expose its internal port, and require no persistent storage.
Install locked Python dependencies with uv, install FFmpeg in the runtime stage, run as an unprivileged user, and keep build tools out of the final image.
Start with 1–2 analysis workers, a 60–120 second timeout, and explicit CPU/memory limits. Scale horizontally only after observing timings.
Expose the app port, attach the domain and HTTPS, and configure /health. Ensure curl or wget exists if the container health check uses it.
| Setting | Starting value | Reason |
|---|---|---|
| Upload size | 50 MB | Enough for common compressed tracks while containing abuse. |
| Track duration | 15 minutes | Reject unusually expensive inputs after probing metadata. |
| Analysis timeout | 90 seconds | Fail predictably instead of pinning a worker indefinitely. |
| Concurrent analyses | 2 per container | DSP is CPU-heavy; tune after measuring the actual host. |
| Retention | 0 seconds after request | Cleanup runs immediately on every exit path; no scheduled sweeper is the primary control. |
Primary documentation supporting the analysis and deployment choices.
Commit pyproject.toml and uv.lock. Dependency upgrades, Ruff configuration, and Ty configuration should land through tested pull requests.
A user can upload a valid MP3, receive understandable key/BPM/tuning results and confidence, and analyze another track. All paths—including cancellation and failure—remove temporary audio. The app passes Ruff and Ty checks plus the reference corpus, runs within declared limits, and deploys from one repository to Coolify with no database or persistent volume.