It's like mopidy but good.
Go to file
Test User 1db88e0f5a cbd-web: end the volume slider where the server clamps
The slider was `max="1.5"` while the engine clamps to 1.1, so its right
third was unreachable — the fill stopped at 73% of the track and the rest
stayed empty however far the thumb was dragged.

`max` is now a named `MAX_VOLUME` that has to track
`PlayerEngine::set_volume`'s clamp. It cannot be imported: `audio-player`
is native-only and this client is wasm, so a comment on each end keeps
the pair honest. Raising the engine clamp was the alternative, but 1.1 is
deliberate headroom and more gain risks clipping, so the slider moved.

The tooltip now reports the level as a percentage too — the web
counterpart of the TUI's `Volume: 85%`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 09:18:20 +02:00
.gitea Clean up a bit 2023-06-13 10:12:26 +02:00
absdy docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
architecture seek: put seek and track-skip on two keys, and make the web gauge clickable 2026-07-26 14:29:30 +02:00
audio-player server: report the real volume, mute and position 2026-07-27 09:13:53 +02:00
cbd build: put every provider, opus, and the spectrum behind cargo features 2026-07-25 03:15:32 +02:00
cbd-cli seek: move 15 seconds inside the playing track with Ctrl-b / Ctrl-f 2026-07-26 14:16:08 +02:00
cbd-tui tui: show the output level in the now-playing pane 2026-07-27 09:07:32 +02:00
cbd-web cbd-web: end the volume slider where the server clamps 2026-07-27 09:18:20 +02:00
crabidy-core seek: move 15 seconds inside the playing track with Ctrl-b / Ctrl-f 2026-07-26 14:16:08 +02:00
crabidy-server server: report the real volume, mute and position 2026-07-27 09:13:53 +02:00
docs cbd-web: end the volume slider where the server clamps 2026-07-27 09:18:20 +02:00
fsdy docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
fyyd docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
jamendody docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
plan cbd-web: end the volume slider where the server clamps 2026-07-27 09:18:20 +02:00
quality seek: put seek and track-skip on two keys, and make the web gauge clickable 2026-07-26 14:29:30 +02:00
rssdy rss: don't list image enclosures as episodes 2026-07-26 14:16:25 +02:00
soundclouddy docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
tidaldy docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
ytdy docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
.editorconfig Add cross compile release flow 2023-06-12 09:38:19 +02:00
.gitignore Architecture: audiobookshelf (`/abs`) provider design 2026-07-23 23:09:54 +02:00
Cargo.lock rss: subscribe to podcast feeds at /rss, including premium ones 2026-07-26 12:36:30 +02:00
Cargo.toml rss: subscribe to podcast feeds at /rss, including premium ones 2026-07-26 12:36:30 +02:00
Cross.toml Generate x86_64 target 2023-06-12 18:21:58 +02:00
README.md rss: subscribe to podcast feeds at /rss, including premium ones 2026-07-26 12:36:30 +02:00
aarch64-unknown-linux-gnu-Dockerfile nix, cross: give the builds cmake so the bundled libopus compiles 2026-07-25 11:37:01 +02:00
armv7-unknown-linux-gnueabihf-Dockerfile nix, cross: give the builds cmake so the bundled libopus compiles 2026-07-25 11:37:01 +02:00
devenv-docs.nix docs: bring the book and every README up to date 2026-07-25 11:04:25 +02:00
devenv-rust.nix Fix sccache coflict 2026-07-25 13:29:26 +02:00
devenv.lock Add devenv-based dev environment and README 2026-07-19 21:29:23 +02:00
devenv.nix rss: subscribe to podcast feeds at /rss, including premium ones 2026-07-26 12:36:30 +02:00
devenv.yaml Add devenv-based dev environment and README 2026-07-19 21:29:23 +02:00
flake.lock Add a crane flake: native packages + static aarch64 cross build 2026-07-23 13:13:08 +02:00
flake.nix nix, cross: give the builds cmake so the bundled libopus compiles 2026-07-25 11:37:01 +02:00
rust-toolchain.toml Add devenv-based dev environment and README 2026-07-19 21:29:23 +02:00
x86_64-unknown-linux-gnu-Dockerfile nix, cross: give the builds cmake so the bundled libopus compiles 2026-07-25 11:37:01 +02:00

README.md

crabidy

A client/server music player. A headless gRPC server owns the library, the play queue, and audio output; a terminal UI connects to it over localhost (or the network). Media comes from pluggable providers, each mounted as a subtree of one library:

/
├── crabidy      your saves: queues, bookmarks (`w`), and captures (`W`),
│                managed by the server
├── abs          audiobookshelf audiobooks    (absdy/README.md)
├── fs           a local music folder          (fsdy/README.md)
├── fyyd         podcast search                (fyyd/README.md)
├── jamendo      Creative-Commons music        (jamendody/README.md)
├── rss          podcast subscriptions         (rssdy/README.md)
├── soundcloud   SoundCloud                    (soundclouddy/README.md)
├── tidal        Tidal streaming               (tidaldy/README.md)
├── youtube      YouTube search & playlists    (ytdy/README.md)
└── orphans      store audio no save references any more — rename, delete,
                 or queue it

Full documentation — every provider, how to log in to each, every config option, and the architecture — is the book in docs/:

devenv shell -- docs        # serve it locally

Binaries

  • crabidy-server — the server: providers, queue, playback, gRPC on 0.0.0.0:50051. Also serves the web client at that address (see below).
  • cbd-tui — the terminal client. Press ? inside for all key bindings.
  • cbd — both in one process: starts the server, waits until it accepts connections, then runs the TUI. Adopts an already-running server instead of failing on an occupied port.
  • cbd-web — the browser client (Leptos/WASM), with the same functionality as the TUI. Not run directly: it is built to a bundle and embedded into crabidy-server (see cbd-web/README.md).

Quick start

The toolchain is managed by devenv:

devenv shell          # provides rust, yt-dlp, and friends
cargo run -p cbd      # server + TUI in one process

Or run the halves separately: cargo run -p crabidy-server and, in another terminal, cargo run -p cbd-tui.

Tailored builds

Every provider — plus Opus decoding, the spectrum bars, the embedded web UI, and the TUI's desktop notifications — sits behind a Cargo feature, all on by default. Drop what you do not need and the dependencies go with it:

# a local-files appliance: no network providers, no web UI, no FFT
cargo build --release -p crabidy-server --no-default-features --features fs,opus

crabidy-server features prints what a binary was built with. See docs/src/build-features.md for the full table, what fs takes with it, and more examples.

Note: --no-default-features on its own drops every provider (it compiles and runs, but plays nothing). Always name what you want.

Configuration

All configuration lives in ~/.config/crabidy/ (the platform config directory). Every file is optional; missing providers simply do not mount. Files are created/rewritten on first start with their defaults filled in.

File Configures Login
abs.toml audiobookshelf URL + API key
fsdy.toml local files
fyyd.toml podcasts none needed
jamendo.toml Jamendo none (key shipped)
rss.toml podcast feeds the feed URLs
soundcloud.toml SoundCloud optional token
tidaly.toml Tidal device login
ytdy.toml YouTube optional cookies
cbd-tui.toml cbd-tui server role + pw
cbd.toml cbd server role + pw
crabidy-server.toml the server its [auth] hashes

Each provider's README — linked from the library tree at the top — explains how to log in and documents every option in its file. The same material is in the book under docs/src/providers/; the client and server files are covered below.

Provider credentials and the client password are stored in cleartext — these are config files, not a keyring. Keep ~/.config/crabidy/ private. Crabidy redacts secrets from logs, errors, and config dumps.

The server-managed crabidy provider does not live under ~/.config. Its track-file tree (saved queues, bookmarks, and captures) lives in ~/.local/state/crabidy/ (the platform state directory) and the audio it captures lives in a single content-addressed store under ~/.local/share/crabidy/ (the data directory), shared and de-duplicated across saves. Neither needs configuration; see docs/src/store.md.

cbd-tui.toml and cbd.toml

Client configuration. cbd-tui (the standalone terminal client) reads cbd-tui.toml; cbd (server + TUI in one process) reads its own cbd.toml. They are separate files with the same options so the two can run side by side on one machine — a common setup is cbd playing locally against its in-process server while cbd-tui points at a remote server (e.g. a Raspberry Pi). A shared file would force one to follow the other's address.

[server]
# Where to find the server. Default (both files): localhost, which is
# what cbd's own in-process server listens on. Point cbd-tui.toml at a
# remote server to use it as a remote control.
address = "http://127.0.0.1:50051"

# Credentials, when the server has [auth] configured (see below).
# `user` is the role name; leave both empty against an open server.
# The password is stored in plaintext — keep this file private.
user = ""
password = ""

# Show the frequency-spectrum bars under the track progress. Default true.
spectrum = true

Every option is also available as a command-line flag before the subcommand (cbd-tui --address ... --user owner, cbd --spectrum false); a flag overrides the file value. To write the credentials into the config once, use the auth subcommand (see below) instead of editing the file by hand:

cbd-tui auth owner 'my-password'                 # sets user + password
cbd-tui auth queue-owner 'pw' --address http://pi:50051

crabidy-server.toml — providers and rights

On first start the server writes this file with every provider enabled:

providers = [
    "tidal", "youtube", "fyyd", "abs", "soundcloud", "jamendo", "rss",
    "fs", "crabidy", "orphans",
]

Remove a name to disable that provider — it no longer mounts and does not appear in the library. Deleting the whole providers line re-enables everything (a fresh install with no file behaves the same). Disabling crabidy also drops orphans, which is a view over the store. A disabled provider's own config file (tidaly.toml, etc.) is simply left unread.

The list can only offer what the binary was built with (see Tailored builds): the default list names only the providers this build has, and naming one it lacks logs a warning at startup instead of failing.

Roles and rights

By default the server is open: everyone who can reach the port has full control. Setting password hashes in [auth] locks the server from the top down — each password you set lowers what a no-credential caller may do, while a matching password elevates a caller to that role:

  • owner — everything (the normal user).
  • queue-owner — anything on the queue and playback, but no library writes: no bookmarks (w), captures (W), queue saving, renames or deletes.
  • queue-appender — may browse/search and append tracks to the queue; nothing else.

A caller with no credentials gets the highest role you left unguarded: nothing guarded → owner (the open default); guard owner → anonymous is queue-owner; guard owner + queue_owner → anonymous is queue-appender; guard all three → credentials required for everything.

[auth]
# One PHC hash per role. Guard from the top down. Generate and store a
# hash with:  crabidy-server guard <role>  (see the CLI section).
owner = "$argon2id$v=19$m=19456,t=2,p=1$..."
queue_owner = "$argon2id$..."
queue_appender = "$argon2id$..."

Clients authenticate with the role name as the basic-auth user (see cbd-tui.toml above). A malformed crabidy-server.toml — or one that guards a lower role while a higher one is still open — aborts server startup rather than silently running open. Note that the transport is plain HTTP/2: fine on a trusted home network, but anything exposed further needs TLS termination (reverse proxy, VPN) in front.

Audio output device

By default the server plays to the system default output device. On a Raspberry Pi that is often HDMI, so playback runs but you hear nothing on the headphone jack or a USB/DAC. List the devices the server can see:

$ crabidy-server audio-devices
Audio output devices (* = selected by the current config):
    hdmi:CARD=vc4hdmi,DEV=0
    sysdefault:CARD=Headphones
    ...

Then pin one by passing it to the same command — the value is matched case-insensitively as a substring of the name, so a memorable fragment is enough — and restart the server:

$ crabidy-server audio-devices Headphones
Set [audio] device = "Headphones" in .../crabidy-server.toml

That writes [audio] device for you; you can also edit it by hand:

[audio]
device = "Headphones"

If the name matches nothing, both the command and the server warn, and the server falls back to the system default.

Command line

Every binary is a clap CLI: run it with --help (and any subcommand with --help) for the full surface. Running a binary with no subcommand behaves as it always has — crabidy-server runs the server, cbd-tui runs the TUI, cbd runs the in-process server + TUI.

The library, queue, and global subcommands are available on all three binaries and act as a remote control over gRPC (they connect to a running server, honouring the same [auth] credentials as the TUI):

crabidy-server library list /tidal      # browse a node
cbd-tui --address http://pi:50051 queue append /fs/album
cbd global play                          # toggle play/pause
cbd global volume -- -0.1                # lower the volume

Connection flags (--address/--user/--password) go before the subcommand; omitted, they fall back to the client config file.

Server-only subcommands (crabidy-server, and cbd):

  • guard <role> [password] — hash a role password (argon2id), print the PHC string, and (unless --no-config) write it into crabidy-server.toml's [auth]. Roles: owner, queue-owner, queue-appender.
  • scan <path> [--capture|--move] — walk a folder and drop a .cbd-track.toml beside every audio file so it browses under /fs. --capture copies each file into the content store (the toml points there); --move moves it instead of copying.
  • audio-devices [device] — list the audio output devices, or pin one into [audio] device (see above).
  • features — print the build features this binary has (see Tailored builds).

Client-only subcommand (cbd-tui, and cbd):

  • auth <role> [password] [--address ADDR] — write the role name and cleartext password (and address) into the client config.

Password caveat. A password given as a command-line argument is visible in the process list (e.g. ps). Omit it and guard reads the password from stdin instead, which keeps it out of argv and is pipe-friendly:

printf '%s' 'my-password' | crabidy-server guard owner

The client config stores the password in plaintext, so keep the file private.

Completions and man pages

cbd-tui completions bash                 # print a completion script
devenv shell -- gen-cli-assets           # write dist/completions + dist/man

gen-cli-assets builds the binaries with CBD_ASSET_DIR=$PWD/dist, so dist/completions/** (bash/zsh/fish) and dist/man/*.1 are produced for all three binaries. Every ordinary build also emits them into the crate's OUT_DIR.

Using the library

Navigation is vim-style: j/k select, l enters the selected folder, h goes to the parent, Tab switches between library and queue, Enter replaces the queue with the selection. % creates a node where the pane title shows % to add (e.g. a search term), e renames, d deletes. / filters the current pane (library or queue) live as you type — Enter keeps the filter, Esc clears it.

s marks the selected row; v (or V) enters visual mode, where movement marks or unmarks everything you sweep over, vim-style. The sweep is anchored where you entered it, so moving back reverses it. Esc (or any non-movement key) leaves visual mode. Both panes have marks and visual mode.

In the queue this feeds a vim-style register: y yanks the selection into it, d deletes the marked rows into it, and c/C fill it with whatever they clear — so an accidental clear is recoverable. p pastes it after the cursor and P before it, which makes d then P an exact undo and dp a move. The register is per client, in memory, one slot, and holds paths — so a paste re-resolves (a yanked album expands to its tracks). Note p no longer inserts the library selection: that flow is now y on the left, then p on the right.

  • w saves the selection (a library subtree, or in the queue pane the queue) as a new folder under /crabidy/<name> of link files — needs the source provider to replay. On a name that already exists the save is refused with a warning; delete the old folder and save again.
  • W captures the selection into /crabidy/<name>: same as w, but every track's audio is fetched into the shared content store under ~/.local/share/crabidy/ and the saved tomls link to it — fully local playback afterwards. Works on a library subtree and on the queue (no need to save it first). Audio is de-duplicated: capturing the same track again (from a playlist, a search, another save) reuses the stored file instead of downloading it twice, matched first by provider id and then by content hash. Tracks already local (from /fs) are copied into the store rather than re-downloaded. A source that genuinely cannot be captured is recorded as skipped (red in the UI, skipped by playback). Download captures can take long; progress is shown in the library pane.
  • Captured rows are marked with a trailing (down-arrow) at the end of the row — visible even while browsing another provider, so you can see what you already have.
  • Inside /crabidy, d deletes a folder or track immediately (no confirmation): it removes only the metadata toml, never the shared store audio, which other saves may reference.

Press ? for the full binding table.

A row of frequency-spectrum bars is drawn under the track progress while audio plays (the server taps its own output, runs the FFT, and streams the bars, so it works whether the server is local or remote). Toggle it at runtime with f, or set the startup default with spectrum = false in the client config. Servers built without the spectrum feature simply never send bars.

Web client

crabidy-server serves a browser client with the same functionality as the TUI at its own address (http://<server>:50051/) — same navigation, same keys (j/k/h/l, %, e, d, w, W, queue and playback controls, ? for help), plus clickable equivalents and a light/dark theme toggle. It talks gRPC-web to the same service the TUI uses, so it honors the same [auth] roles (it shows a login form when the server requires credentials).

It is compiled to a WASM bundle and embedded into the server binary, behind the default-on web-ui cargo feature. A plain cargo build needs no WASM toolchain — it embeds a "not built" placeholder page until you build the bundle:

devenv shell -- build-web        # writes cbd-web/dist
cargo build -p crabidy-server    # embeds it

For a headless, gRPC-only binary, build with every feature except web-ui — e.g. --no-default-features --features all-providers,opus,spectrum. See cbd-web/README.md for the dev loop and details.

Nix packages and cross-compiling

flake.nix (built with crane) packages the binaries as Nix derivations — for your own machines, and cross-compiled for a Raspberry Pi. No Docker required.

Native — install on any machine with Nix:

nix run .#cbd-tui                 # run without installing
nix build .#crabidy              # cbd, cbd-tui, crabidy-server → ./result/bin
nix profile install .#crabidy    # or github:OWNER/crabidy once pushed

Raspberry Pi (or any aarch64 Linux) — a fully static musl binary, so it has no glibc-version or loader dependency and runs on stock Raspberry Pi OS (bookworm and newer):

nix build .#crabidy-server-aarch64
scp ./result/bin/crabidy-server pi:/usr/local/bin/

Nix cross-compiles the Rust and the C dependencies (ALSA, aws-lc) hermetically on an x86_64 host — the isolated build avoids the host-linker pitfalls of cross-compiling in a plain shell. The aarch64 server includes the embedded web UI: the flake builds the cbd-web wasm bundle with trunk (pinning a wasm-bindgen CLI that matches the crate) and stages it into the server's web-ui feature. The native .#crabidy package stays headless; use a normal cargo build there if you want the bundle.

A container-based cross setup also exists (Cross.toml + the *-Dockerfiles) for building against Debian's glibc, but the flake is the recommended path.

Logs

cbd and cbd-tui log to ~/.local/state/crabidy/ (daily files); crabidy-server logs to stderr. Stream URLs and credentials are redacted from logs by design.

Development

devenv shell -- bash -lc 'cargo test --workspace --exclude cbd-web'
devenv shell -- check-features    # the build-feature matrix
devenv shell -- docs              # serve the documentation book

AGENTS.md (and CLAUDE.md) carry the coding rules and the toolchain conventions. The reference documentation for the system itself lives in docs/.