Document the providers and their configuration in READMEs

The root README covers the binaries, quick start, the config directory,
and links per-provider READMEs; each provider README explains how the
provider works, how it is used from the TUI, and its config file with
every option and default (tidaly.toml, fsdy.toml, ytdy.toml,
cbd-tui.toml). The fsdy README doubles as the reference for the
.cbd-track.toml on-disk format shared by queues, bookmarks, and
captures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Test User 2026-07-21 19:34:06 +02:00
parent c84dec9ca2
commit 383173046d
4 changed files with 301 additions and 0 deletions

100
README.md
View File

@ -1 +1,101 @@
# crabidy # 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:
```text
/
├── tidal Tidal streaming (see tidaldy/README.md)
├── youtube YouTube search & playlists (see ytdy/README.md)
├── fs a local music folder (see fsdy/README.md)
├── queues saved play queues (managed by the server)
├── bookmarks link snapshots of library subtrees (`w`)
└── captures downloaded snapshots with local audio (`W`)
```
## Binaries
- `crabidy-server` — the server: providers, queue, playback, gRPC on
`0.0.0.0:50051`.
- `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.
## Quick start
The toolchain is managed by [devenv](https://devenv.sh):
```sh
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`.
## 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 | Component | Documentation |
| -------------- | --------- | -------------------------------------- |
| `tidaly.toml` | Tidal | [tidaldy/README.md](tidaldy/README.md) |
| `ytdy.toml` | YouTube | [ytdy/README.md](ytdy/README.md) |
| `fsdy.toml` | local fs | [fsdy/README.md](fsdy/README.md) |
| `cbd-tui.toml` | TUI / cbd | below |
The server-managed folders (`queues/`, `bookmarks/`, `captures/`) also
live in `~/.config/crabidy/`; they need no configuration and hold plain
folders of track files in the format documented in
[fsdy/README.md](fsdy/README.md).
### `cbd-tui.toml`
Configuration of the TUI (and the TUI half of `cbd`):
```toml
# Where to find the server. Default shown.
address = "http://127.0.0.1:50051"
```
Every option is also available as a command-line flag
(`cbd-tui --address ...`).
## 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.
- `w` saves the selection as a **bookmark** (links; needs the source
provider to replay) or, in the queue pane, saves the queue.
- `W` **captures** the selection: the subtree is mirrored under
`/captures/<name>` with every track's audio downloaded next to its
metadata — fully local playback afterwards. Captures are incremental:
re-capturing the same name resumes and completes it; tracks whose
source cannot be captured are recorded as *skipped* (red in the UI,
skipped by playback). Download captures can take long; progress is
shown in the library pane.
Press `?` for the full binding table.
## 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
Design documents live in `architecture/`, per-feature quality gates in
`quality/`, and implementation plans in `plan/`. See `CLAUDE.md` /
`AGENTS.md` for the development workflow and coding rules.

61
fsdy/README.md Normal file
View File

@ -0,0 +1,61 @@
# fsdy — the filesystem provider
Mounts a local directory at **`/fs`** in the crabidy library. The same
engine also powers the server-managed mounts `/queues`, `/bookmarks`,
and `/captures` — everything below applies to their on-disk format too.
## How it works
The provider walks one configured root directory. Every **directory**
becomes a queueable library node; every file ending in
**`.cbd-track.toml`** becomes a track; everything else (audio files,
covers, hidden entries) is invisible to the library. Listing order is
case-insensitive by file name — prefix files with numbers to fix an
order (the capture/bookmark writers use `0001`-style prefixes for
exactly this reason).
A track file carries the track's metadata plus a reference to the
playable thing:
```toml
title = "We Will Rock You" # required
artist = "Queen" # optional
duration = 122 # optional, seconds
[album] # optional
title = "News of the World"
release_date = "1977-10-28"
# Exactly ONE of the following four:
[playable]
file = "we-will-rock-you.flac" # local audio; absolute, or relative to
# this file's directory (relocatable)
# url = "https://example.org/radio.mp3" # http(s) stream
# link = "/tidal/artists/1/2/3" # another provider's track
# skipped = true # no audio: a capture recorded its
# source as uncapturable; shown red,
# skipped by playback
```
Malformed track files are skipped with a warning; they never break the
listing. `link` playables resolve exactly one hop (a link to a link
fails at play time), which keeps cycles impossible.
## Configuration — `~/.config/crabidy/fsdy.toml`
```toml
# Absolute path of the directory to expose under /fs.
# Default: the platform music directory (e.g. ~/Music). When neither is
# available the /fs mount is disabled — the rest of the server runs on.
# root = "/home/me/music"
```
## The server-managed instances
`/queues`, `/bookmarks`, and `/captures` are fsdy instances over
`~/.config/crabidy/{queues,bookmarks,captures}/`, written by the server
(queue persistence, `w`, `W`) and readable/editable with any file
manager — they are just folders of the track files described above.
Their top-level folders can be renamed (`e`) and deleted (`d`) from the
TUI; `/queues/current` is the continuously persisted play queue and is
protected.

61
tidaldy/README.md Normal file
View File

@ -0,0 +1,61 @@
# tidaldy — the Tidal provider
Mounts your Tidal account at **`/tidal`** in the crabidy library, using
Tidal's web API.
## How it works
On first start the provider runs Tidal's OAuth **device login**: the
server prints a `link.tidal.com` verification URL to its stdout (for
`cbd`, check the log file) — open it in a browser, authorize, and the
provider finishes logging in by itself. The obtained tokens are written
back into the config file and refreshed automatically from then on; you
should not need to log in again.
The tree offers your playlists, favorite artists (with their albums and
tracks), and a **search** node: press `%` under `/tidal/search` to
create a search term; the term becomes a persistent child node holding
its results (tracks, artists, albums). Terms are renamable (`e`,
re-runs the search) and deletable (`d`).
Everything queueable is also capturable: `w` bookmarks a subtree as
links, `W` downloads a subtree's audio into `/captures/<name>`.
## Configuration — `~/.config/crabidy/tidaly.toml`
The file is rewritten on every server start with the current values
(including refreshed tokens), so **it contains your credentials — keep
it private**. All options with their defaults:
```toml
# Tidal API endpoints. Change only if you know why.
base_url = "https://api.tidal.com/v1"
hifi_url = "https://api.tidalhifi.com/v1"
# Stream quality: "Low" | "High" | "Lossless" | "HiRes".
audio_quality = "Lossless"
# Managed by the provider: filled in by the device login and refreshed
# automatically. Delete this whole section to force a fresh login.
[login]
# device_code = ...
# user_id = ...
# country_code = ...
# access_token = ...
# refresh_token = ...
# expires_after = ...
# OAuth client identity used for the device flow. Working defaults are
# built in; override only to use your own client registration.
[oauth]
# client_id = ...
# client_secret = ...
base_url = "https://auth.tidal.com/v1/oauth2"
```
Notes:
- A missing or empty file is fine: defaults are used and the device
login runs on the next start.
- If Tidal ever invalidates the session (long offline periods), delete
the `[login]` section and restart.

79
ytdy/README.md Normal file
View File

@ -0,0 +1,79 @@
# ytdy — the YouTube provider
Mounts YouTube at **`/youtube`** in the crabidy library.
## How it works
Metadata comes from the pure-Rust [rustypipe] Innertube client:
- **`/youtube/search`** works without any login. Press `%` to create a
search term; the term becomes a persistent child node listing the top
video results as tracks. Terms are renamable (`e`, re-runs the
search) and deletable (`d`). Terms live in memory — a server restart
forgets them, but navigating to an old term path recreates it.
- **`/youtube/playlists`** appears when a cookie login is configured
and accepted: it lists the account's saved playlists.
**Stream URLs** are resolved through a minimal `yt-dlp` sidecar when
the binary is available (one bounded subprocess call per played or
captured track — no other functionality uses it). This is a deliberate
concession: YouTube currently caps tokenless stream URLs at their
leading ~1 MiB, and `yt-dlp` is the only maintained solver for the
cipher challenges that lift the cap; its URLs stream whole files at a
throttled ~32 KB/s (still ~2× audio bitrate). Without the binary the
provider still works, but playback of a track stops after roughly a
minute. Streams prefer `audio/mp4` (AAC), the format the built-in
player decodes; captures therefore store `.m4a` files. See
`architecture/youtube-rustypipe.md` for the full analysis.
Search results and playlists are downloadable: `W` captures them with
audio into `/captures/<name>` (slow under the throttle, but captures
are resumable — re-capture the same name to continue).
## Configuration — `~/.config/crabidy/ytdy.toml`
All options with their defaults:
```toml
# Netscape cookies.txt export for the logged-in features (saved
# playlists). Export with a browser extension like "Get cookies.txt
# LOCALLY", ideally from an Incognito session you close afterwards.
# The provider caches the rotated session cookie under
# ~/.config/crabidy/rustypipe/, so the export only needs to be valid
# once. Default: unset (logged out; search still works).
# cookies = "/home/me/youtube-cookies.txt"
# Results per search term. Default: 20.
# search_results = 20
# Per-request timeout in seconds (Innertube calls; the yt-dlp sidecar
# gets at least 60 s). Default: 30.
# call_timeout_secs = 30
# The yt-dlp binary used ONLY for stream URLs. A bare name resolves
# via PATH. Default: "yt-dlp". Keep it fresh — a stale yt-dlp is the
# first suspect when YouTube playback breaks.
# binary = "yt-dlp"
# Optional rustypipe-botguard binary for PO-token attestation (also
# auto-detected on PATH). Currently ineffective upstream, but once
# rustypipe's web-client deciphering is fixed this makes streams work
# without yt-dlp. Default: unset.
# botguard_bin = "/home/me/.cargo/bin/rustypipe-botguard"
```
Secrets: the cookies file and the rustypipe cache grant access to your
YouTube session — keep both private. The provider never logs their
contents (paths only), and stream URLs are redacted from logs.
## Failure behavior
- No network / broken client at startup: only `/youtube` is disabled,
the server runs on.
- Rejected or missing cookies: degrades to logged-out with a warning.
- Missing `yt-dlp`: warning at startup; streams stop after ~1 MiB.
- `403 Forbidden` on streams in the log: YouTube's per-IP/per-video
enforcement fluctuates; usually transient. If it persists, update
`yt-dlp`.
[rustypipe]: https://crates.io/crates/rustypipe