Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
565 changes: 163 additions & 402 deletions README.md

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# HTTP API

<sub>[Back to the README](../README.md)</sub>

The endpoints the web app itself uses. They are not a stable public interface and can change between releases.

| Method | Path | Purpose |
|---|---|---|
| GET | `/api/health` | Server health and version info |
| POST | `/api/jobs` | JSON `{url, stems?}` or multipart `file + stems` → `{job_id}` |
| GET | `/api/jobs` | List completed (library) jobs |
| GET | `/api/jobs/{id}` | Job state snapshot |
| GET | `/api/jobs/{id}/events` | SSE stream of job state |
| POST | `/api/jobs/{id}/cancel` | Terminate active subprocess and cancel job |
| PATCH | `/api/jobs/{id}/sections` | Save waveform section markers for a job |
| GET | `/api/jobs/{id}/stems/{name}.wav` | Stream a single stem WAV file |
| GET | `/api/jobs/{id}/stems/{name}.mp3` | Transcode and stream a stem as MP3 |
| GET | `/api/jobs/{id}/video.mp4` | Mux the current mix with the source video (MP4 upload or YouTube) into an MP4 |
| DELETE | `/api/jobs/{id}` | Remove job dir from disk (terminal jobs only) |
141 changes: 141 additions & 0 deletions docs/build-from-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Build from source

<sub>[Back to the README](../README.md)</sub>

Every way to run StemDeck from this repository: the macOS app, a web server on any platform, Docker and Unraid.

### macOS Native App

Requires Rust, Node.js, and Python 3.12. Builds a self-contained `.app` that downloads its own runtime on first launch.

```sh
# First time only: add the cross-compilation targets
rustup target add aarch64-apple-darwin # Apple Silicon
rustup target add x86_64-apple-darwin # Intel

# Build Apple Silicon
ARCH=arm64 scripts/macos/make-runtime-pack.sh
ARCH=arm64 scripts/macos/make-app.sh
ARCH=arm64 scripts/macos/make-dmg.sh

# Build Intel (requires Rosetta 2 and an x86_64 Python)
ARCH=x64 scripts/macos/make-runtime-pack.sh
ARCH=x64 scripts/macos/make-app.sh
ARCH=x64 scripts/macos/make-dmg.sh
```

The `.app` lands at `desktop/src-tauri/target/<target>/release/bundle/macos/StemDeck.app`. The DMG lands at `.build/macos-dist/StemDeck-macOS-<arch>.dmg`.

To run a fresh build directly without the DMG:

```sh
open desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/StemDeck.app
```

If macOS blocks the app with a Gatekeeper prompt, run:

```sh
xattr -dr com.apple.quarantine desktop/src-tauri/target/aarch64-apple-darwin/release/bundle/macos/StemDeck.app
```

> **Note:** To test a clean first-launch during development, you can wipe previous app data first: `rm -rf ~/Library/Application\ Support/StemDeck`. Don't do this on a real install.

---

### Web Server (macOS / Linux / Windows with Python 3.12+)

#### Prerequisites

Python 3.12 or newer, `ffmpeg` on your PATH, and [uv](https://github.com/astral-sh/uv). Around 170 MB of free disk for the Demucs model, which downloads automatically on first run.

Optional, for song identification with an AcoustID key: an FFmpeg built with chromaprint (Debian and Ubuntu's `ffmpeg` is), or Chromaprint's `fpcalc` on your PATH (`brew install chromaprint`, `apt install libchromaprint-tools`, or your distro's `chromaprint` package). `./run.sh setup` installs it when your FFmpeg needs it. Without either, songs are identified by their tags only.

#### macOS / Linux (one-shot)

```sh
git clone https://github.com/stemdeckapp/stemdeck stemdeck && cd stemdeck
./run.sh setup # installs ffmpeg + uv, runs uv sync
./run.sh start
```

Open <http://localhost:8000>.

`setup` uses Homebrew on macOS and `apt-get` on Debian/Ubuntu. For other Linux distros, install `ffmpeg` and [uv](https://github.com/astral-sh/uv) manually, then run `uv sync` followed by `./run.sh start`.

#### Windows (PowerShell)

Install prerequisites:
- [uv](https://docs.astral.sh/uv/getting-started/installation/): `winget install astral-sh.uv`
- [ffmpeg](https://ffmpeg.org/download.html): `winget install Gyan.FFmpeg` (or Chocolatey: `choco install ffmpeg`)

```powershell
git clone https://github.com/stemdeckapp/stemdeck stemdeck; cd stemdeck
uv sync
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --timeout-graceful-shutdown 5
```

Open <http://localhost:8000>.

> `run.sh` is macOS/Linux only. On Windows use the PowerShell commands above, or run inside WSL.

**NVIDIA GPU (CUDA):** install the CUDA-enabled torch build before starting:

```powershell
uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
$env:STEMDECK_DEMUCS_DEVICE = "cuda"
uv run uvicorn app.main:app --host 127.0.0.1 --port 8000 --timeout-graceful-shutdown 5
```

---

#### Manual (any platform)

```sh
git clone https://github.com/stemdeckapp/stemdeck stemdeck && cd stemdeck
uv sync
uv run uvicorn app.main:app --reload --timeout-graceful-shutdown 5
```

> `--timeout-graceful-shutdown` bounds how long uvicorn waits for open
> connections when you stop it. StemDeck keeps a long-lived SSE stream open
> for the import queue while a browser tab is on the app, so without it
> Ctrl-C waits for that stream instead of exiting.

#### Docker

```sh
docker compose -f build/docker-compose.yml up --build
```

Stems land in `./jobs/` on the host. Demucs weights are cached in a named volume so they don't re-download on rebuild. Note: no GPU passthrough on macOS Docker.

A prebuilt image is published to GHCR. Tags: `edge` (rolling, rebuilt on every merge to main), `latest` (newest stable release), and `X.Y.Z` (pinned to a release).

```sh
docker run -d --name stemdeck -p 8000:8000 \
-v /path/to/jobs:/app/jobs \
-v /path/to/cache:/cache \
-e STEMDECK_PERSIST_LIBRARY=1 \
ghcr.io/stemdeckapp/stemdeck:edge
```

On a Linux host with an NVIDIA GPU (driver + NVIDIA Container Toolkit installed), add `--runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all` and StemDeck auto-detects CUDA. The image already bundles CUDA-enabled torch, so no separate CUDA install is needed.

#### Unraid

StemDeck is available in Unraid Community Applications: open **Apps**, search "StemDeck", and install. Map the two volumes to persistent appdata paths:

- `/app/jobs` -> `/mnt/user/appdata/stemdeck/jobs` (library + stems)
- `/cache` -> `/mnt/user/appdata/stemdeck/cache` (model weights)

The library is persistent by default (`STEMDECK_PERSIST_LIBRARY=1`), so tracks are never auto-deleted. For GPU acceleration, install the **Nvidia Driver** plugin, then set the container's Extra Parameters to `--runtime=nvidia` (the `NVIDIA_VISIBLE_DEVICES` and `NVIDIA_DRIVER_CAPABILITIES` variables are already in the template). CPU-only works with no extra configuration.

#### `run.sh` control script

```sh
./run.sh setup # one-shot: install ffmpeg + uv, then uv sync
./run.sh start # boots uvicorn in the background
./run.sh stop # graceful shutdown
./run.sh restart # stop + start
./run.sh status # is it running?
```
94 changes: 94 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Configuration

<sub>[Back to the README](../README.md)</sub>

| Variable | Default | Purpose |
|---|---|---|
| `STEMDECK_DEMUCS_DEVICE` | auto | Force Torch device: `cuda`, `mps`, or `cpu`. |
| `STEMDECK_DEMUCS_MODEL` | `htdemucs_6s` | Demucs model name. |
| `STEMDECK_JOBS_DIR` | `./jobs` | Where job directories land. |
| `STEMDECK_DATA_DIR` | (none) | Portable mode root; sets all sub-dirs below to live inside it. |
| `STEMDECK_CACHE_DIR` | `<data>/cache` | Torch model cache directory. |
| `STEMDECK_DOWNLOADS_DIR` | `<data>/downloads` | yt-dlp download scratch space. |
| `STEMDECK_MODELS_DIR` | `<data>/models` | Demucs model weights directory. |
| `STEMDECK_LOGS_DIR` | `<data>/logs` | Log file output directory. |
| `STEMDECK_FFMPEG_DIR` | (none) | Directory containing a bundled ffmpeg binary. |
| `STEMDECK_FFMPEG` | `ffmpeg` | Path to the ffmpeg executable. |
| `STEMDECK_FFPROBE` | `ffprobe` | Path to the ffprobe executable. |
| `STEMDECK_FPCALC` | `fpcalc` | Path to Chromaprint's fpcalc, used for song fingerprinting when FFmpeg has no chromaprint muxer. |
| `STEMDECK_MAX_DURATION_SEC` | `1200` | Reject audio longer than this (seconds). |
| `STEMDECK_JOB_TTL_SECONDS` | `86400` | How long to keep job dirs on disk. |
| `STEMDECK_MAX_PENDING_JOBS` | `3` | Max queued jobs before returning 503. |
| `STEMDECK_TIMEOUT_FFMPEG` | `300` | ffmpeg subprocess timeout (seconds). |
| `STEMDECK_TIMEOUT_ANALYZE` | `120` | Audio analysis timeout (seconds). |
| `STEMDECK_TIMEOUT_DEMUCS_STALL` | `1800` | Kill Demucs if no output for this many seconds. |
| `STEMDECK_SSL_CERT` | (none) | PEM certificate; set with the key below to serve https directly. |
| `STEMDECK_SSL_KEY` | (none) | PEM private key for the certificate above. |
| `STEMDECK_HTTPS_PORT` | (none) | Serve https on this port *in addition* to the main listener. Set by the desktop app; see below. |

`run.sh` also reads: `HOST` (default `127.0.0.1`), `PORT` (default `8765`), `RELOAD=1` (enable uvicorn auto-reload for development), `FOREGROUND=1` (run in foreground instead of backgrounding).

### Serving other devices: why https is not optional

Transpose is built on `AudioWorklet`, and browsers grant that only to a
**secure context**. `https://` and `localhost` qualify. A plain
`http://192.168.1.20:8000` does not, so a phone reaching StemDeck over plain
http gets working playback and a key control that cannot do anything. There is
no fallback worth shipping: driving the same DSP from a `ScriptProcessorNode`
measured around 5% of the audio missing, because that node type drops buffers
on its own at every size.

So a server that other devices will use terminates TLS, one of three ways:

1. **A reverse proxy** (SWAG, Nginx Proxy Manager, Traefik, Caddy). The usual
self-hosted shape, and the best one if you already run it. StemDeck reads
`X-Forwarded-Proto` and the RFC 7239 `Forwarded` header, so an https browser
over a plain-http upstream hop is recognised as secure and served normally.
2. **StemDeck itself**, by pointing `STEMDECK_SSL_CERT` and `STEMDECK_SSL_KEY`
at a certificate and key. uvicorn serves them directly; no extra package is
installed for this.
3. **A private overlay network** such as Tailscale, whose addresses are already
https.

Reaching a plaintext non-local origin with none of those in place is refused
with a 403 that explains this, rather than served as an app that is quietly
half-broken. Loopback is always served, so turning this on can never lock the
host out of its own server.

### The desktop app runs two listeners

The desktop app does the same thing without being configured, because it has
two audiences that need opposite things.

- **Plain http on `127.0.0.1`** for its own window. Loopback is already a
secure context, so nothing is lost, and it is the only scheme that works: a
self-signed certificate would raise a warning page the app window has no way
to click through.
- **https on the LAN**, port 8443 by default, for phones and other computers.
This is the address Settings shows and the QR code points at.

Both listeners serve the same process, so there is one library, one queue and
one Demucs worker either way.

The certificate is generated on your own machine the first time you enable
network access, and lives in `<data>/certs/` beside `jobs/` and
`settings.json`. Nothing is shipped in the download: a certificate in the
release would publish its private key to everyone who downloaded it, which is
worse than plain http because it looks secure. It is regenerated automatically
when your machine's addresses change or the certificate is close to expiring.

Because it is signed by nobody, **your phone will show a "your connection is
not private" warning the first time**. Tap Advanced, then Continue. Once per
device, per computer. Settings says so, in red, next to the toggle.

## Environment variables for the desktop app

These are for development and testing. Release builds only recognize the variables marked "release".

| Variable | Platform | Scope | Description |
|---|---|---|---|
| `STEMDECK_DATA_DIR` | all | release | Override the user data directory (default: platform-standard location) |
| `STEMDECK_ROOT` | all | release | Override the app root directory (default: derived from executable path) |
| `STEMDECK_PYTHON` | all | **debug builds only** | Override the Python executable path |
| `STEMDECK_FFMPEG_URL` | Windows, macOS | release | Override the FFmpeg download URL |
| `STEMDECK_FFPROBE_URL` | macOS | release | Override the ffprobe download URL |
40 changes: 40 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Troubleshooting

<sub>[Back to the README](../README.md)</sub>

**`ffmpeg: command not found`:** install ffmpeg and restart with `./run.sh restart`.

**`WARNING: [youtube] No supported JavaScript runtime`:** install deno (`brew install deno` on macOS) and restart. Downloads still work without it but may pick suboptimal formats.

**First separation is very slow:** Demucs downloads `htdemucs_6s` weights (~170 MB) on first run; cached afterwards.

**Demucs runs on CPU only:** check the startup log for `device=mps` or `device=cuda`. If you see `cpu`, your torch install may be CPU-only.

**Transpose is greyed out on another machine:** the pitch stage is built on `AudioWorklet`, which browsers only expose on a *secure context*. `https://` and `localhost` count. A plain `http://192.168.x.x` does not, so a client opening StemDeck over the network is never given the API and transpose cannot work there. Changing the speed still works on such a client, but it resamples instead, so the key moves with it.

Three ways to get a secure context, in order of least effort:

- **Tunnel to localhost.** On the client: `ssh -N -L 8000:localhost:8000 user@host`, then open `http://localhost:8000`. The origin is now localhost, so everything works, including transpose.
- **Tailscale Serve.** `tailscale serve 8000` on the host publishes StemDeck on your tailnet over real HTTPS with a genuine certificate, no warnings and nothing to install on the client beyond Tailscale itself. Note the plain Tailscale IP (`100.x.y.z`) is *not* a secure context; it has to go through `serve`.
- **Any HTTPS reverse proxy** in front of StemDeck: Caddy, nginx, or a tunnel like Cloudflare Tunnel.

**Page reloaded mid-job:** the job keeps running server-side. Wait for it to finish, then resubmit.

**`./run.sh: Permission denied`:** run `chmod +x run.sh`.

## Layout on disk

```
jobs/<job_id>/
└── stems/
├── vocals.wav # the 6 Demucs stems (always present)
├── drums.wav
├── bass.wav
├── guitar.wav
├── piano.wav
├── other.wav
├── original.wav # sum of un-selected stems (subset only)
└── mix.wav # ffmpeg amix of selected stems (subset only)
```

The library itself is one file, `jobs/registry.json`, so it survives a restart. A song's folder is only removed when you empty the Trash, or by the opt-in **Automatically delete finished tracks** setting, which is off unless you turn it on.
1 change: 1 addition & 0 deletions imgs/readme/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/download.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/export.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/eye.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/flag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/gauge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/globe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/grid.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions imgs/readme/os-apple.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading