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:
parent
c84dec9ca2
commit
383173046d
100
README.md
100
README.md
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue