Skip to main content

Station Studio

An open-source radio station playlist generator. Describe how your station should sound (jingle every N tracks, no artist repeats inside a 10-track window, mix block of curated genres, weekend continuity from a separate source pool) — and Station Studio generates iTunes XML playlists you can drop straight into Music or iTunes.

Built for radio enthusiasts who run their own stations. Two real stations ship as examples; you can fork either or build your own from scratch.

Why this exists

If you've ever:

  • Curated a radio station for friends / a Twitch stream / your own shower
  • Wanted "shuffle, but smarter" — never the same artist twice in a row, weekend feels different from weekday, jingles at the right intervals, mix blocks of specific genres
  • Tried to build that in iTunes smart playlists and hit the wall

…Station Studio is that wall, removed. The engine handles the constraint logic; you describe what you want.

What it does

Given:

  • Your music, in iTunes XML format (export from Music / iTunes)
  • A station template — a small JSON file describing what plays when (or built visually via the web UI, coming soon)

It produces:

  • iTunes XML playlists — drop them back into Music / iTunes
  • Per-pick traceability — every track placement is explained ("this track was picked because cursor for NIGHT_1 is at position 847 and this is the 12th slot of that block")
  • Reproducibility — same template + same source files + same week number = same playlist, byte-for-byte

You can also visualise a station's template in the web UI — a chip-per-pick canvas grouped into sections, color-coded by source (or strategy), with click-through detail for every slot and macro. Click "🗺 Visualise template" on any station page. (Read-only today; in-canvas authoring is on the roadmap — see DESIGN.md §16.2.3+.)

Install

Four install channels, all first-class. Pick whichever matches what you've got installed:

Channel Prerequisites Best for
Download the app a Mac or Windows PC Using it without a terminal
Docker docker Trying it out, leaving it running on a NAS
Pip wheel Python 3.11+ Local-only / BYO-music, no Node setup
Dev clone Python 3.11+, Node 22+, make Hacking on Station Studio itself

All four serve the same UI + API and the same auto-seeded example stations. Pick one:

0. Download the app (Mac or Windows, no terminal)

From the newest Station Studio release on the releases page (tags studio-v…), under Assets:

  • Mac: StationStudio-<version>-mac-apple-silicon.dmg (M-chip Macs) or -mac-intel.dmg. Open it, drag Station Studio into Applications and open it from there. The first time, macOS says it can't check the app: System Settings › Privacy & Security › Open Anyway.
  • Windows: StationStudio-<version>-windows-setup.exe. If Windows says it protected your PC: More info › Run anyway. Installs for you only (no administrator password) and adds Station Studio to the Start menu, and the desktop if you tick it.

On a Mac, Station Studio is an app of its own, like Apple Music: its own window, Dock icon and menus (Go, Settings ⌘,, Check for Updates, Quit ⌘Q). Closing the window leaves Station Studio running; clicking its Dock icon brings the window back, and right-clicking it has New station and Source pools. On Windows, opening it opens http://127.0.0.1:8001 in a window of its own (when Chrome, Edge or Brave is installed; otherwise a browser tab); opening it again while it runs just opens it again. The bar at the bottom right of the page has Quit, and Update now when a newer version is out (it downloads the new version from the release, checks it against GitHub's SHA-256 for that file, and restarts into it).

Data lives in ~/.station-studio (Windows: %USERPROFILE%\.station-studio), the same folder the pip install uses, and updating or uninstalling never touches it. Exports go in its exports/ folder, where Station Player finds them. The app keeps a log there too (station-studio.log). Coming from Docker? Quit the app, docker compose down, then copy the volume across:

docker run --rm -v station-studio-data:/data -v ~/.station-studio:/dest \
  alpine cp -a /data/. /dest/

With the pip wheel installed, station-studio app does the same from a terminal (opens the browser, Quit on the page; updates stay with pip).

1. Docker (no Python, no Node)

git clone https://github.com/raby/station-studio.git
cd station-studio
docker compose up --build       # first run builds; later runs reuse

Open http://localhost:8001. App-home (your DB + imported pools + exports) lives in a docker-managed volume named station-studio-data, so state survives docker compose down. Inspect with docker volume inspect station-studio-data; back up with:

docker run --rm -v station-studio-data:/data -v $PWD:/backup \
  alpine tar czf /backup/data.tar.gz /data

The image is multi-stage (node-build → wheel-build → slim runtime, non-root, healthchecked), ~312MB. Same SvelteKit static SPA as the pip wheel — no divergent build path.

2. Pip wheel (Python only, BYO-music)

If you have Python 3.11+ and want a single-process app on localhost, build a wheel and pip-install it:

git clone https://github.com/raby/station-studio.git
cd station-studio
make wheel                                # builds web/build + dist/*.whl
pip install dist/station_studio-*.whl
station-studio serve --host 127.0.0.1 --port 8001

make wheel runs the SvelteKit static build internally — you don't need Node installed separately at runtime, only at build time. On first run the CLI creates ~/.station-studio/ (override with $STATION_STUDIO_HOME), copies the example stations, and runs Alembic.

Once the first tagged release ships, this becomes:

pip install station-studio              # straight from PyPI, no clone
station-studio serve

The PyPI publish workflow is wired (.github/workflows/publish.yml, trusted-publishing via OIDC, test.pypi rehearsal before real PyPI); the first release lands when project trust is configured on PyPI. See CONTRIBUTING.md for the cut-a-release procedure.

3. Dev clone (hack on it)

If you want to modify Station Studio or contribute upstream:

git clone https://github.com/raby/station-studio.git
cd station-studio
make install                  # creates .venv, installs Python + Node deps
make dev                      # FastAPI :8001 + SvelteKit :5173 with HMR

Open http://localhost:5173. Hot-reload on both backend and frontend; see CONTRIBUTING.md for the slice pattern + test discipline.

Single-worker for now. The async-generation job tracker (§16.11) holds job state in-memory per uvicorn worker. All three channels above use a single worker; if you reconfigure to --workers N (N>1), the frontend's polling request can land on a worker that doesn't know about the job and you'll get spurious 404s. Stick to single-worker until DB-backed job state lands (§16.21+).

Loading your music

All four example stations (Starter + Stateful Starter + Rebirth Classics + Rebirth Radio) are seeded automatically on first boot — pick one in the sidebar. The two starter stations' three pools (STARTER_CONTENT, STARTER_JINGLES, STATEFUL_CONTENT) also auto-import from the bundled synthetic XMLs, so both starters work end-to-end without any setup.

To import your own iTunes XMLs as source pools, open the Sources page (or click "Source pools →" from the landing). Drag-and-drop or pick an XML file, name the pool, choose a role (content / reference), and hit Import. Re-import to refresh, Delete to remove (§16.25).

Want to learn the manual import flow first? Go to the Sources page and delete STARTER_CONTENT, then re-import it by uploading stations/starter-content.itunes.xml. The starter pools work as a real-shaped iTunes XML you can practice on before importing your own library. (The starter station will silently re-import on next boot if you leave both pools deleted, so it can't get stuck broken.)

In Music (macOS), export a playlist as XML via: File → Library → Export Playlist… → choose "XML" format. (In iTunes on Windows, same path.)

Advanced: many pools from a manifest (CLI). The batch path still ships for power users — drop a JSON manifest at the shape of tests/fixtures/sample_pool_manifest.json and run (from the dev clone):

make import-weekly-pools manifest=path/to/your-manifest.json

Useful when you have a fixed pool list to import in CI / on a fresh box, or when re-bootstrapping a station from scratch. The UI path above is the recommended hobbyist flow.

Example stations included

Starter Station — stations/starter.json

  • 168 slots / week (one per hour, Mon → Sun)
  • Stateless cursors — no cross-week state
  • Two strategies: weighted (content) + jingle (every 8 slots)
  • Companion synthetic pools (starter-content.itunes.xml, starter-jingles.itunes.xml) — 50 synthetic tracks across 10 artists + 5 jingle tracks. Auto-imported on first boot so the station works out of the box with no external data.
  • Read this first. Smallest, most approachable shape; perfect for "how does the slot+strategy model work?" before you fork anything bigger.

Stateful Starter — stations/stateful-starter.json

  • 336 slots / week (one every 30 min, Mon → Sun)
  • One persistent cursor advancing 24 positions/week, cycling every 14 weeks → exact pool coverage per cycle
  • Two strategies: sequential (most slots, cursor-driven) + prev_ref (sprinkled every 12 slots, reads last week's playlist at the same position)
  • Companion pool (stateful-starter-content.itunes.xml) — 336 synthetic tracks across 14 artists, also auto-imported on first boot.
  • Read this second — your second station, with state. Demonstrates cursor advance / cycle math + cross-week prev_ref semantics on a focused teaching surface, with no row-template addressing to learn yet.

Rebirth Classics — stations/rebirth-classics.json

  • 3,010 slots / week, 7 rows × 2 strategy blocks
  • Single stateless cursor advancing 60 positions/week, cycling at 16
  • Strategy: window only (the simplest production case)
  • Read this third — first row_template station, single-cursor mechanics in the per-day shape (vs the starters' flat_slots flat list).

Rebirth Radio — stations/rebirth-radio.json

  • 2,196 slots / week, all five strategies (jingle, weighted, mix, prev_ref, sequential), nine stateful cursors with cross-week advance
  • Imported from weekly/ (a bespoke 1-station generator that Station Studio replaces)
  • Read this fourth (or as your reference for "can the engine do X?"). Forking from here is forking a complete production station — be ready for it.

Roll your own

The visual block editor (§16.2) will let you build a template by dragging chips onto a timeline. Until that ships, the cleanest starting point is stations/starter.json — copy it under a new filename, adjust the slot count, swap in your own source names, and drop it in ~/.station-studio/stations/. Restart serve and your station appears in the sidebar. Read DESIGN.md §14.32 / §14.34 for the full template grammar reference.

How it works

your iTunes XMLs ──► import into source pools
station template ──► describes what plays when
   │
   └──► engine.generator(template, pools, week, seed)
            │
            ├──► picks: ordered list of (slot, track)
            ├──► trace: per-slot explanation
            └──► persistence: Playlist + Trace rows
                       │
                       └──► export → iTunes XML

Seven strategies for placing a track at a slot:

  • window — take N consecutive tracks from a source at a cursor position
  • pick — one track from a source at a cursor position
  • jingle — random pick from genre-tagged subset (e.g. "Daytime jingle")
  • weighted — pick from a source with artist-dedup + recent-tracks shuffle
  • mix — pick from a genre-tagged subset, excluding tracks used in last N weeks
  • prev_ref — read this position's track from last week's playlist
  • sequential — read N consecutive tracks at a named per-station cursor

Each strategy is a small pure-function module under engine/strategies/.

Project status

Engine Stable. Byte-equality verified across 30-week chains for two structurally distinct stations.
Backend API Stable. FastAPI; full CRUD on stations + sources + rules.
Install Wheel + docker-compose both ship and smoke-pass (§16.13). PyPI publish queued (§16.13.t1).
CLI station-studio serve / import / seed / generate / export / backup ships with the wheel — full hobbyist subcommand set, no repo checkout required. Makefile targets remain for in-repo dev work (refresh, byte-equality fixtures, etc.).
Web UI Wizard + trace viewer + Sources page + read-only template visualiser ship; in-canvas template editor in design (§16.2 — flagship feature).
Docs Engineering docs in DESIGN.md; user-facing docs are growing.
License MIT — fork freely.

Contributing

Yes please. See CONTRIBUTING.md for slice pattern, test discipline, and how to propose new station shapes.

Architecture deep-dive

Engineering details — engine internals, persistence model, slice discipline, byte-equality test methodology — live in DESIGN.md. It's a long document; the table of contents at the top lists every §14.x slice that shipped.

If you're contributing engine work, also see engine/divergence.py (slot-level diff helpers) and engine/prior_seed.py (the prior-seed validator + the import-prior contract) for how the port acceptance tests catch regressions. tests/byte_equality.py is a thin re-export shim for backward compatibility.

Acknowledgements

Station Studio is the next-generation replacement for weekly/ (Rebirth Radio's bespoke playlist generator, ~2017-present). The strategies, template grammar, and most of the engine semantics are ports of weekly/'s design — re-shaped to be expressive enough for any station, not just Rebirth's specific shape.

Metadata

Release files for station-studio 1.16.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for station-studio 1.16.0
File Interpreter ABI Platform Actions
station_studio-1.16.0-py3-none-any.whl Python 3 none any Details for station_studio-1.16.0-py3-none-any.whl

Release files / station_studio-1.16.0-py3-none-any.whl

Download URL station_studio-1.16.0-py3-none-any.whl
Size 546.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
894b9c57d1dcde2451f284a9f4337ab4243d0bfc04c4406620bc4f0e0002cd82
BLAKE2b-256 checksum
How to use checksums
b1da667c1700be08990097a144c80ec9dab5b2a77eb097b1986c110e6683816e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Security for station-studio 1.16.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 1 file in this release has been uploaded with provenance metadata.

Attestation for station_studio-1.16.0-py3-none-any.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page