docs: bring the book and every README up to date

The docs drifted behind three changes: the /queues + /bookmarks +
/captures split folding into one /crabidy provider, three providers
arriving (soundcloud, jamendo, abs) with nothing written about them, and
the spectrum toggle moving off v to f when library visual mode took v/V.

- The book gains a page per undocumented provider — /soundcloud,
  /jamendo, /abs — each with its tree, its playback path, every config
  option, and how to log in. The providers index and intro list all nine
  roots in the order the server actually serves them.
- Every provider now documents its login: Tidal's device flow (and that
  a broken tidaly.toml is the one fatal provider config), audiobookshelf
  API keys, the optional SoundCloud token and where to read it out of a
  browser, YouTube cookie exports, Jamendo's shipped key, and "nothing
  to do" for fyyd and /fs.
- fsdy's README described three server-managed mounts under
  ~/.config/crabidy that have not existed for a while; it now describes
  /crabidy over the state dir plus the shared content store, and how
  deletes there never touch store audio.
- Stale /captures/<name> save paths in the tidaldy and ytdy READMEs are
  /crabidy/<name>. The TUI key table, the README walkthrough, and the
  spectrum section use f, and visual mode (v/V) is documented.
- config.md and the README list all seven provider config files, say
  plainly that credentials are stored in cleartext, and cover the audio
  output device; the CLI page documents audio-devices and features.
- No README or docs page references architecture/, quality/, or plan/
  any more: the book describes the system as it is, and points at the
  crate READMEs for usage and config.
- devenv-docs.nix was never committed even though devenv.nix imports it,
  so a fresh clone could not enter the shell at all. It is in now, which
  also makes the README's `devenv shell -- docs` work.

Also fixes two ./store.md links in providers/fs.md that pointed one
directory too shallow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test User 2026-07-25 11:02:47 +02:00
parent af04573b72
commit ab3bd7c63a
24 changed files with 894 additions and 175 deletions

110
README.md
View File

@ -7,15 +7,24 @@ each mounted as a subtree of one library:
```text ```text
/ /
├── tidal Tidal streaming (see tidaldy/README.md)
├── youtube YouTube search & playlists (see ytdy/README.md)
├── fyyd podcast search (see fyyd/README.md)
├── abs audiobookshelf audiobooks (see absdy/README.md)
├── fs a local music folder (see fsdy/README.md)
├── crabidy your saves: queues, bookmarks (`w`), and captures (`W`), ├── crabidy your saves: queues, bookmarks (`w`), and captures (`W`),
│ managed by the server (see architecture/crabidy-store.md) │ 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)
├── 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, └── orphans store audio no save references any more — rename, delete,
or queue it (see architecture/orphans.md) or queue it
```
Full documentation — every provider, how to log in to each, every config
option, and the architecture — is the **book in [`docs/`](docs/src/)**:
```sh
devenv shell -- docs # serve it locally
``` ```
## Binaries ## Binaries
@ -58,10 +67,11 @@ cargo build --release -p crabidy-server --no-default-features --features fs,opus
``` ```
`crabidy-server features` prints what a binary was built with. See `crabidy-server features` prints what a binary was built with. See
[docs/src/build-features.md](docs/src/build-features.md) for the full [docs/src/build-features.md](docs/src/build-features.md) for the full table,
table (and what `fs` takes with it), and what `fs` takes with it, and more examples.
[architecture/build-features.md](architecture/build-features.md) for the
design. **Note:** `--no-default-features` on its own drops **every** provider (it
compiles and runs, but plays nothing). Always name what you want.
## Configuration ## Configuration
@ -70,16 +80,27 @@ directory). Every file is optional; missing providers simply do not
mount. Files are created/rewritten on first start with their defaults mount. Files are created/rewritten on first start with their defaults
filled in. filled in.
| File | Component | Documentation | | File | Configures | Login |
| -------------------- | ---------- | -------------------------------------- | | --------------------- | ---------------- | ------------------ |
| `tidaly.toml` | Tidal | [tidaldy/README.md](tidaldy/README.md) | | `abs.toml` | audiobookshelf | URL + API key |
| `ytdy.toml` | YouTube | [ytdy/README.md](ytdy/README.md) | | `fsdy.toml` | local files | — |
| `fyyd.toml` | podcasts | [fyyd/README.md](fyyd/README.md) | | `fyyd.toml` | podcasts | none needed |
| `abs.toml` | audiobooks | [absdy/README.md](absdy/README.md) | | `jamendo.toml` | Jamendo | none (key shipped) |
| `fsdy.toml` | local fs | [fsdy/README.md](fsdy/README.md) | | `soundcloud.toml` | SoundCloud | optional token |
| `cbd-tui.toml` | `cbd-tui` | below | | `tidaly.toml` | Tidal | device login |
| `cbd.toml` | `cbd` | below (same options as `cbd-tui.toml`) | | `ytdy.toml` | YouTube | optional cookies |
| `crabidy-server.toml`| server | below (providers + auth) | | `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/`](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 The server-managed `crabidy` provider does not live under `~/.config`. Its
track-file tree (saved queues, bookmarks, and captures) lives in track-file tree (saved queues, bookmarks, and captures) lives in
@ -87,7 +108,7 @@ track-file tree (saved queues, bookmarks, and captures) lives in
captures lives in a single content-addressed store under captures lives in a single content-addressed store under
`~/.local/share/crabidy/` (the *data* directory), shared and de-duplicated `~/.local/share/crabidy/` (the *data* directory), shared and de-duplicated
across saves. Neither needs configuration; see across saves. Neither needs configuration; see
[architecture/crabidy-store.md](architecture/crabidy-store.md). [docs/src/store.md](docs/src/store.md).
### `cbd-tui.toml` and `cbd.toml` ### `cbd-tui.toml` and `cbd.toml`
@ -132,7 +153,10 @@ cbd-tui auth queue-owner 'pw' --address http://pi:50051
On first start the server writes this file with every provider enabled: On first start the server writes this file with every provider enabled:
```toml ```toml
providers = ["tidal", "youtube", "fyyd", "abs", "fs", "crabidy", "orphans"] providers = [
"tidal", "youtube", "fyyd", "abs", "soundcloud", "jamendo",
"fs", "crabidy", "orphans",
]
``` ```
**Remove a name to disable that provider** — it no longer mounts and does **Remove a name to disable that provider** — it no longer mounts and does
@ -141,13 +165,18 @@ everything (a fresh install with no file behaves the same). Disabling
`crabidy` also drops `orphans`, which is a view over the store. A disabled `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. 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](#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 #### Roles and rights
By default the server is open: everyone who can reach the port has By default the server is open: everyone who can reach the port has
full control. Setting password hashes in `[auth]` locks the server full control. Setting password hashes in `[auth]` locks the server
**from the top down** — each password you set lowers what a **from the top down** — each password you set lowers what a
no-credential caller may do, while a matching password elevates a no-credential caller may do, while a matching password elevates a
caller to that role (see `architecture/roles-auth.md`): caller to that role:
- **owner** — everything (the normal user). - **owner** — everything (the normal user).
- **queue-owner** — anything on the queue and playback, but no - **queue-owner** — anything on the queue and playback, but no
@ -241,6 +270,10 @@ Server-only subcommands (`crabidy-server`, and `cbd`):
`.cbd-track.toml` beside every audio file so it browses under `/fs`. `.cbd-track.toml` beside every audio file so it browses under `/fs`.
`--capture` copies each file into the content store (the toml points `--capture` copies each file into the content store (the toml points
there); `--move` moves it instead of copying. 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](#tailored-builds)).
Client-only subcommand (`cbd-tui`, and `cbd`): Client-only subcommand (`cbd-tui`, and `cbd`):
@ -280,6 +313,11 @@ node where the pane title shows `% to add` (e.g. a search term), `e`
renames, `d` deletes. `/` filters the current pane (library or queue) renames, `d` deletes. `/` filters the current pane (library or queue)
live as you type — `Enter` keeps the filter, `Esc` clears it. 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.
- `w` saves the selection (a library subtree, or in the queue pane the - `w` saves the selection (a library subtree, or in the queue pane the
queue) as a new folder under `/crabidy/<name>` of **link** files — queue) as a new folder under `/crabidy/<name>` of **link** files —
needs the source provider to replay. On a name that already exists the needs the source provider to replay. On a name that already exists the
@ -307,8 +345,9 @@ Press `?` for the full binding table.
A row of frequency-spectrum bars is drawn under the track progress 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 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). streams the bars, so it works whether the server is local or remote).
Toggle it at runtime with `v`, or set the startup default with Toggle it at runtime with `f`, or set the startup default with
`spectrum = false` in the client config. `spectrum = false` in the client config. Servers built without the
`spectrum` feature simply never send bars.
## Web client ## Web client
@ -330,9 +369,10 @@ devenv shell -- build-web # writes cbd-web/dist
cargo build -p crabidy-server # embeds it cargo build -p crabidy-server # embeds it
``` ```
Build the server with `--no-default-features` for a headless, For a headless, gRPC-only binary, build with every feature *except*
gRPC-only binary. See [cbd-web/README.md](cbd-web/README.md) for the `web-ui` — e.g. `--no-default-features --features
dev loop and details. all-providers,opus,spectrum`. See [cbd-web/README.md](cbd-web/README.md) for
the dev loop and details.
## Nix packages and cross-compiling ## Nix packages and cross-compiling
@ -377,6 +417,12 @@ redacted from logs by design.
## Development ## Development
Design documents live in `architecture/`, per-feature quality gates in ```sh
`quality/`, and implementation plans in `plan/`. See `CLAUDE.md` / devenv shell -- bash -lc 'cargo test --workspace --exclude cbd-web'
`AGENTS.md` for the development workflow and coding rules. 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/`](docs/src/).

View File

@ -2,14 +2,24 @@
Mounts a self-hosted [audiobookshelf](https://www.audiobookshelf.org/) (ABS) Mounts a self-hosted [audiobookshelf](https://www.audiobookshelf.org/) (ABS)
server at `/abs`, so you can browse, search, and play your audiobooks from server at `/abs`, so you can browse, search, and play your audiobooks from
crabidy. See [the design doc](../architecture/audiobookshelf-provider.md) for crabidy. Audiobooks only for now — podcast libraries on the ABS server are
the design and decisions. not listed.
## Logging in
The provider needs your server URL and an **API key**; without both, `/abs`
does not mount (the rest of the server is unaffected).
1. Open the audiobookshelf web UI as the user whose libraries you want.
2. **Settings → Users → (your user) → API Keys** (older versions:
**Settings → API Keys**) and create a key.
3. Put it in `abs.toml` (below) and restart the server.
There is no interactive login and no token to refresh — the API key is a
long-lived bearer token. Revoke it in the same screen to cut access.
## Configuration — `~/.config/crabidy/abs.toml` ## Configuration — `~/.config/crabidy/abs.toml`
The provider needs your server URL and an API key. Without both, `/abs` does
not mount (the rest of the server is unaffected).
```toml ```toml
# Required. # Required.
base_url = "https://audiobookshelf.example.com" base_url = "https://audiobookshelf.example.com"
@ -21,10 +31,8 @@ search_results = 50 # books listed per search term
call_timeout_secs = 30 # per-request timeout call_timeout_secs = 30 # per-request timeout
``` ```
Create an API key in audiobookshelf under **Settings → Users → (your user) → The key is a secret: it is redacted from logs and never printed, but keep
API Keys** (or **Settings → API Keys** on older versions). The key is a `abs.toml` private anyway (it is stored in cleartext).
secret: it is redacted from logs and never printed, but keep `abs.toml`
private anyway.
## The library tree ## The library tree
@ -52,3 +60,7 @@ private anyway.
HTTP range requests — no transcoding session. HTTP range requests — no transcoding session.
- Listings are capped (see the config) and fetched fresh; only your typed - Listings are capped (see the config) and fetched fresh; only your typed
search terms are remembered, in memory, until the server restarts. search terms are remembered, in memory, until the server restarts.
- Many audiobookshelf libraries store Opus audio, which needs the server's
`opus` cargo feature (on by default).
- Build the server without the `abs` cargo feature to leave this provider out
of the binary entirely.

View File

@ -1,8 +1,7 @@
# cbd-web — the browser client # cbd-web — the browser client
A [Leptos](https://leptos.dev) client-side WASM app with the same A [Leptos](https://leptos.dev) client-side WASM app with the same
functionality as `cbd-tui`, served by `crabidy-server` itself. See functionality as `cbd-tui`, served by `crabidy-server` itself.
`architecture/web-client.md` for the design.
## How it works ## How it works
@ -10,8 +9,8 @@ functionality as `cbd-tui`, served by `crabidy-server` itself. See
generated client and proto types the TUI uses (`crabidy-core`). No generated client and proto types the TUI uses (`crabidy-core`). No
second API surface — feature parity is structural. The server wraps second API surface — feature parity is structural. The server wraps
its existing gRPC service in `tonic-web`, so the browser and the TUI its existing gRPC service in `tonic-web`, so the browser and the TUI
hit identical `/crabidy.v1.CrabidyService/…` paths, and the role hit identical `/crabidy.v1.CrabidyService/…` paths, and the same role
auth layer (`architecture/roles-auth.md`) gates both. auth layer gates both.
- **Serving**: the built bundle (`cbd-web/dist`) is embedded into - **Serving**: the built bundle (`cbd-web/dist`) is embedded into
`crabidy-server` at compile time behind the default-on `web-ui` `crabidy-server` at compile time behind the default-on `web-ui`
feature and served as the fallback route on port 50051. gRPC and feature and served as the fallback route on port 50051. gRPC and
@ -27,13 +26,12 @@ functionality as `cbd-tui`, served by `crabidy-server` itself. See
## Functionality ## Functionality
Everything the TUI does: browse the library (`j`/`k`/`h`/`l`, click), Everything the TUI does: browse the library (`j`/`k`/`h`/`l`, click),
marks, create/rename/delete nodes (`%`/`e`/`d`, with the capture-delete marks, create/rename/delete nodes (`%`/`e`/`d`), bookmark and capture
`y/N` confirmation), bookmark and capture (`w`/`W`, with live progress (`w`/`W`, with live progress lines and skipped-track marking), the full queue
lines and skipped-track marking), the full queue and playback controls, and playback controls, volume, shuffle/repeat, and a `?` help overlay listing
volume, shuffle/repeat, and a `?` help overlay listing the keys. Keys the keys. Keys mirror the TUI; every key also has a clickable control. A
mirror the TUI; every key also has a clickable control. A light/dark light/dark theme follows the OS and can be toggled (persisted). The accent
theme follows the OS and can be toggled (persisted). The accent color color is the crab orange-red.
is the crab orange-red.
When the server requires credentials, a login form collects the role When the server requires credentials, a login form collects the role
(`owner` / `queue-owner` / `queue-appender`) and password; they are (`owner` / `queue-owner` / `queue-appender`) and password; they are
@ -54,9 +52,10 @@ cargo build -p crabidy-server # embeds cbd-web/dist
mold linker, which `rust-lld` (the wasm linker) cannot parse. mold linker, which `rust-lld` (the wasm linker) cannot parse.
Building `crabidy-server` without a `cbd-web/dist` present is fine — it Building `crabidy-server` without a `cbd-web/dist` present is fine — it
embeds a placeholder page telling you to run `build-web`. Build the embeds a placeholder page telling you to run `build-web`. To drop the web
server `--no-default-features` to drop the web client (and the client (and the `tonic-web` layer) entirely, build the server without its
`tonic-web` layer) entirely. `web-ui` cargo feature — e.g. `--no-default-features --features
all-providers,opus,spectrum`; see `docs/src/build-features.md`.
## Dev loop ## Dev loop

38
devenv-docs.nix Normal file
View File

@ -0,0 +1,38 @@
{ pkgs, ... }:
let
mdbook-admonish = pkgs.mdbook-admonish.overrideAttrs (old: rec {
version = "1.20.0-unstable-pr235";
src = pkgs.fetchFromGitHub {
owner = "tommilligan";
repo = "mdbook-admonish";
rev = "f67dc47c24bc48dada3ae4decf055fdd6ba4a4ed";
hash = "sha256-vD71Hhjn43NnqUg+sluybC2wejjNsY6cvj/PsPvIUnw=";
};
cargoDeps = pkgs.rustPlatform.fetchCargoVendor {
inherit src;
name = "mdbook-admonish-${version}-vendor";
hash = "sha256-FQo58eT9SyO5bhuoRQOAfBcAi1acBOPjYH6WUtiJPIE=";
};
});
in
{
packages = with pkgs; [
mdbook
mdbook-admonish
mdbook-d2
mdbook-footnote
mdbook-toc
d2
];
scripts.docs.exec = ''
export D2_NO_XML_TAG="''${D2_NO_XML_TAG:-true}"
mdbook serve docs --hostname "''${MDBOOK_HOST:-127.0.0.1}" --port "''${MDBOOK_PORT:-3000}" "$@"
'';
scripts.docs-build.exec = ''
export D2_NO_XML_TAG="''${D2_NO_XML_TAG:-true}"
mdbook build docs "$@"
'';
}

View File

@ -7,6 +7,9 @@
- [Filesystem — /fs](./providers/fs.md) - [Filesystem — /fs](./providers/fs.md)
- [Tidal — /tidal](./providers/tidal.md) - [Tidal — /tidal](./providers/tidal.md)
- [YouTube — /youtube](./providers/youtube.md) - [YouTube — /youtube](./providers/youtube.md)
- [SoundCloud — /soundcloud](./providers/soundcloud.md)
- [Jamendo — /jamendo](./providers/jamendo.md)
- [audiobookshelf — /abs](./providers/abs.md)
- [fyyd — /fyyd](./providers/fyyd.md) - [fyyd — /fyyd](./providers/fyyd.md)
- [Search](./providers/search.md) - [Search](./providers/search.md)
- [The crabidy store](./store.md) - [The crabidy store](./store.md)

View File

@ -135,20 +135,27 @@ queue-persistence task.
The orchestrator is the root of the library tree. It serves the The orchestrator is the root of the library tree. It serves the
synthetic root `/` itself — a node whose children are the mounted synthetic root `/` itself — a node whose children are the mounted
providers — and routes everything else by the **first path segment**: providers — and routes everything else by the **first path segment**.
`/tidal/...` to Tidal, `/youtube/...` to YouTube, `/fs/...` to the local
filesystem provider, and `/crabidy/...` to the crabidy store's tree.
Each of `get_lib_node`, `resolve_tracks_into`, `get_urls_for_track`,
`get_metadata_for_track`, and the create/rename/delete/capture calls
picks the owning provider the same way; a path no provider owns is
rejected as malformed (or, for editing calls, unsupported) rather than
guessed at.
Every provider except Tidal is optional: if its config or backing store It holds one **mount** per provider: the library root it owns (`/tidal`,
fails to initialize, the orchestrator logs a warning and simply omits `/fs`, …), the name shown in the root listing, and the client itself behind a
that child from the root, and the server runs without it. Because the trait object. Every routed call — `get_lib_node`, `resolve_tracks_into`,
orchestrator sits behind an `Arc`, a slow library walk is spawned onto `get_urls_for_track`, `get_metadata_for_track`, and the
its own task while the loop keeps serving other commands. create/rename/delete/capture calls — looks up the one mount that owns the
path and forwards to it. Ownership stops at a path separator, so `/fsx` is
not `/fs`. A path no mount owns is rejected as malformed (or, for editing
calls, unsupported) rather than guessed at. The mounts are sorted once, so
the root always lists `crabidy` first, `orphans` last, and the rest
alphabetically.
Mounting is the only place a provider is named, which is what makes a
provider droppable in three independent ways: not compiled in (see [Tailored
builds](./build-features.md)), not enabled in `crabidy-server.toml`, or
failed to initialize. A failure to initialize logs a warning and omits that
child; the server runs without it. Tidal is the one exception — a Tidal
config that exists but cannot be loaded aborts startup. Because the
orchestrator sits behind an `Arc`, a slow library walk is spawned onto its
own task while the loop keeps serving other commands.
```admonish note ```admonish note
Routing is by the *resolved* track path. When a track's playable is a Routing is by the *resolved* track path. When a track's playable is a

View File

@ -48,7 +48,7 @@ cbd global volume -- -0.1 # lower the volume
- `global play`/`stop`/`next`/`prev`/`restart`/`mute` and `global - `global play`/`stop`/`next`/`prev`/`restart`/`mute` and `global
volume <DELTA>` control playback. volume <DELTA>` control playback.
## Server commands: `guard` and `scan` ## Server commands
These live on `crabidy-server` (and `cbd`). They act on the server's These live on `crabidy-server` (and `cbd`). They act on the server's
own config and content store, not over the wire. own config and content store, not over the wire.
@ -78,6 +78,30 @@ crabidy-server scan ~/Music # index in place
crabidy-server scan ~/Downloads --capture # copy audio into the store crabidy-server scan ~/Downloads --capture # copy audio into the store
``` ```
`audio-devices [device]` lists the audio output devices the server can see,
marking the one the current config selects. Given a name (or a fragment of
one) it writes `[audio] device` into `crabidy-server.toml` and then lists, so
one command both configures and confirms — see [The audio output
device](../config.md#the-audio-output-device).
```sh
crabidy-server audio-devices # list
crabidy-server audio-devices Headphones # pin one, then list
```
`features` prints the build features this binary was compiled with — the
providers it can mount, then the extras (`opus`, `spectrum`, `web-ui`) — one
per line. It answers "why is `/tidal` missing?" without guesswork; see
[Tailored builds](../build-features.md).
```console
$ crabidy-server features
fs
crabidy
orphans
opus
```
## Client command: `auth` ## Client command: `auth`
`auth <role> [password] [--address ADDR]` lives on `cbd-tui` (and `auth <role> [password] [--address ADDR]` lives on `cbd-tui` (and

View File

@ -48,6 +48,12 @@ it, and while it is open every other key is inert.
- `s` toggles a **mark** on the selected item. Marks live on the full - `s` toggles a **mark** on the selected item. Marks live on the full
item list (they survive filtering) so a marked-but-hidden row still item list (they survive filtering) so a marked-but-hidden row still
counts when you queue or save. counts when you queue or save.
- `v` (or `V`) enters **visual mode** in the library: movement then marks
or unmarks the rows you sweep over, like vim's visual selection. The
selection is anchored where you entered it, so moving back over a row
reverses it. `Esc` leaves visual mode (keeping the marks and any `/`
filter), and so does any non-movement action. The pane title shows
`— VISUAL` while it is on.
- `w` **saves** the selection — a library subtree, or in the queue pane - `w` **saves** the selection — a library subtree, or in the queue pane
the whole queue — as a new folder of link files under the whole queue — as a new folder of link files under
`/crabidy/<name>`; playback replays it from the source provider. `/crabidy/<name>`; playback replays it from the source provider.
@ -99,7 +105,7 @@ them on the update stream like every other bit of live state. So the
bars work whether the server is local or on another machine, and when bars work whether the server is local or on another machine, and when
nothing is playing they fall to the floor. nothing is playing they fall to the floor.
Press `v` to show or hide them at runtime; `spectrum` in the client Press `f` to show or hide them at runtime; `spectrum` in the client
config sets the startup default (`spectrum = true` is the default; config sets the startup default (`spectrum = true` is the default;
`false` starts hidden). Either way it is only a display choice — the `false` starts hidden). Either way it is only a display choice — the
server always computes and streams the bars while audio flows. server always computes and streams the bars while audio flows.
@ -124,7 +130,7 @@ pane).
| Global | `x` | Toggle repeat | | Global | `x` | Toggle repeat |
| Global | `Ctrl-n` | Next track | | Global | `Ctrl-n` | Next track |
| Global | `Ctrl-p` | Previous track | | Global | `Ctrl-p` | Previous track |
| Global | `v` | Toggle the frequency spectrum | | Global | `f` | Toggle the frequency spectrum |
| Library | `j` / `k` | Select next / previous item | | Library | `j` / `k` | Select next / previous item |
| Library | `g` / `G` | Select first / last item | | Library | `g` / `G` | Select first / last item |
| Library | `Ctrl-d` | Jump 15 items down | | Library | `Ctrl-d` | Jump 15 items down |
@ -132,6 +138,7 @@ pane).
| Library | `h` | Go to parent folder | | Library | `h` | Go to parent folder |
| Library | `l` | Enter selected folder | | Library | `l` | Enter selected folder |
| Library | `s` | Mark / unmark selection | | Library | `s` | Mark / unmark selection |
| Library | `v` / `V` | Visual mode: movement toggles marks |
| Library | `Enter` | Replace queue with selection | | Library | `Enter` | Replace queue with selection |
| Library | `a` | Append selection to queue | | Library | `a` | Append selection to queue |
| Library | `L` | Queue selection after current track | | Library | `L` | Queue selection after current track |

View File

@ -15,22 +15,34 @@ which the server now also writes on first start.
## The config files ## The config files
| File | Component | Auto-created | | File | Component | Credentials needed |
| --------------------- | ---------- | ------------ | | --------------------- | ---------------- | --------------------------- |
| `tidaly.toml` | Tidal | yes | | `abs.toml` | audiobookshelf | server URL + API key |
| `ytdy.toml` | YouTube | yes | | `fsdy.toml` | local files | none |
| `fyyd.toml` | podcasts | yes | | `fyyd.toml` | podcasts | none |
| `fsdy.toml` | local fs | yes | | `jamendo.toml` | Jamendo | none (a key is shipped) |
| `cbd-tui.toml` | `cbd-tui` | yes | | `soundcloud.toml` | SoundCloud | none (token optional) |
| `cbd.toml` | `cbd` | yes | | `tidaly.toml` | Tidal | device login (interactive) |
| `crabidy-server.toml` | server | yes | | `ytdy.toml` | YouTube | none (cookies optional) |
| `cbd-tui.toml` | `cbd-tui` | server role + password |
| `cbd.toml` | `cbd` | server role + password |
| `crabidy-server.toml` | server | its own `[auth]` hashes |
- `tidaly.toml`, `ytdy.toml`, `fyyd.toml`, and `fsdy.toml` configure the Every file is auto-created with defaults on first start.
media providers — Tidal, YouTube, fyyd podcasts, and a local music
folder (its filesystem root). See [Providers](./providers.md). - The provider files each get a full option reference — **including how to log
in** — on that provider's page under [Providers](./providers.md).
- `cbd-tui.toml` and `cbd.toml` are client configs (below). - `cbd-tui.toml` and `cbd.toml` are client configs (below).
- `crabidy-server.toml` holds the enabled-providers list (below) and server - `crabidy-server.toml` holds the enabled-providers list (below), the audio
auth (see [Roles and authorization](./auth.md)). output device (below), and server auth (see [Roles and
authorization](./auth.md)).
```admonish warning
Provider credentials and client passwords are stored in **cleartext** — the
files are configuration, not a keyring. Keep `~/.config/crabidy/` private
(`chmod 700` is a reasonable habit). Crabidy redacts secrets from its logs,
error messages, and config dumps.
```
## Enabling and disabling providers ## Enabling and disabling providers
@ -38,7 +50,17 @@ On first start the server writes `crabidy-server.toml` with every provider
enabled: enabled:
```toml ```toml
providers = ["tidal", "youtube", "fyyd", "fs", "crabidy", "orphans"] providers = [
"tidal",
"youtube",
"fyyd",
"abs",
"soundcloud",
"jamendo",
"fs",
"crabidy",
"orphans",
]
``` ```
**Remove a name to disable that provider** — it no longer mounts and drops **Remove a name to disable that provider** — it no longer mounts and drops
@ -123,3 +145,41 @@ de-duplication work.
Client and TUI log files also go to `~/.local/state/crabidy/`. The Client and TUI log files also go to `~/.local/state/crabidy/`. The
provider ignores non-track files there, so the logs do not show up in provider ignores non-track files there, so the logs do not show up in
the library. the library.
```admonish note
A binary built without the `fs` feature has no content store at all: no
`/crabidy`, no bookmarks or captures, and the queue is not persisted across
restarts. See [Tailored builds](./build-features.md).
```
## The 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 nothing comes out of the
headphone jack or a USB DAC. Ask the server what it can see:
```console
$ 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, and restart the server:
```console
$ crabidy-server audio-devices Headphones
Set [audio] device = "Headphones" in .../crabidy-server.toml
```
The value is matched case-insensitively as a **substring** of the device
name, so a memorable fragment is enough. It writes:
```toml
[audio]
device = "Headphones"
```
If the fragment matches nothing, both the command and the server warn, and
the server falls back to the system default.

View File

@ -7,9 +7,9 @@ a terminal UI on your laptop, a browser tab, a shell script — and they all
see the same queue and playback state live. see the same queue and playback state live.
This book describes **how the current version works**: the pieces, the This book describes **how the current version works**: the pieces, the
boundaries between them, and the behaviour you can rely on. It is reference boundaries between them, the behaviour you can rely on, and every
documentation, not a design log — the "why we chose X over Y" discussions configuration option. It is reference documentation — it describes the system
live in the `architecture/` folder in the source tree. as it is, not the history of how it got there.
```admonish note ```admonish note
Crabidy is a personal, single-server music player for a trusted home Crabidy is a personal, single-server music player for a trusted home
@ -36,9 +36,9 @@ server: crabidy-server {
} }
media: Media { media: Media {
tidal: Tidal streaming: "Tidal · YouTube\nSoundCloud · Jamendo"
youtube: YouTube spoken: "audiobookshelf · fyyd"
fs: Local files fs: "Local files\n+ the crabidy store"
} }
clients -> server: gRPC (commands + update stream) clients -> server: gRPC (commands + update stream)
@ -59,12 +59,20 @@ segment is a **provider** mounted as a subtree:
```text ```text
/ /
├── crabidy your saves: queues, bookmarks, and captures
├── abs audiobookshelf audiobooks
├── fs a local music folder
├── fyyd podcast search
├── jamendo Creative-Commons music
├── soundcloud SoundCloud search, likes, and playlists
├── tidal Tidal streaming ├── tidal Tidal streaming
├── youtube YouTube search & playlists ├── youtube YouTube search & playlists
├── fs a local music folder └── orphans store audio no save references any more
└── crabidy your saves: queues, bookmarks, and captures
``` ```
Which of these you see depends on what your binary was built with, what you
enabled, and which providers came up — see [Providers](./providers.md).
Providers share one node/track model (see [The library Providers share one node/track model (see [The library
model](./library.md)) so a client browses `/tidal` and `/fs` with the same model](./library.md)) so a client browses `/tidal` and `/fs` with the same
keys and the same code. The `crabidy` provider is special: it is where the keys and the same code. The `crabidy` provider is special: it is where the
@ -93,5 +101,8 @@ mode.
- [The crabidy store](./store.md) — saves, captures, and de-duplication. - [The crabidy store](./store.md) — saves, captures, and de-duplication.
- [Queue and playback](./queue.md) — the queue, playback, and persistence. - [Queue and playback](./queue.md) — the queue, playback, and persistence.
- [Clients](./clients.md) — the TUI, the web client, and the CLI. - [Clients](./clients.md) — the TUI, the web client, and the CLI.
- [Configuration](./config.md) and - [Configuration](./config.md) — every config file and option, including
[Roles and authorization](./auth.md). how to log in to each provider.
- [Tailored builds](./build-features.md) — dropping providers and features
you do not need at compile time.
- [Roles and authorization](./auth.md) — locking a server down.

View File

@ -16,19 +16,30 @@ root: "/ (orchestrator)" {
shape: rectangle shape: rectangle
} }
crabidy: "/crabidy — your saves & captures"
abs: "/abs — audiobookshelf audiobooks"
fs: "/fs — a local music folder"
fyyd: "/fyyd — podcast search"
jamendo: "/jamendo — Creative-Commons catalogue"
soundcloud: "/soundcloud — SoundCloud"
tidal: "/tidal — Tidal streaming" tidal: "/tidal — Tidal streaming"
youtube: "/youtube — YouTube search & playlists" youtube: "/youtube — YouTube search & playlists"
fyyd: "/fyyd — podcast search" orphans: "/orphans — unreferenced store audio"
fs: "/fs — a local music folder"
crabidy: "/crabidy — your saves & captures"
root -> crabidy: "route /crabidy/*"
root -> abs: "route /abs/*"
root -> fs: "route /fs/*"
root -> fyyd: "route /fyyd/*"
root -> jamendo: "route /jamendo/*"
root -> soundcloud: "route /soundcloud/*"
root -> tidal: "route /tidal/*" root -> tidal: "route /tidal/*"
root -> youtube: "route /youtube/*" root -> youtube: "route /youtube/*"
root -> fyyd: "route /fyyd/*" root -> orphans: "route /orphans/*"
root -> fs: "route /fs/*"
root -> crabidy: "route /crabidy/*"
``` ```
The root lists its children in a fixed order: `crabidy` first, `orphans`
last, and everything else alphabetically in between.
Because routing is purely by prefix, the providers are independent of Because routing is purely by prefix, the providers are independent of
one another. They all speak the same node/track model (see [The library one another. They all speak the same node/track model (see [The library
model](./library.md)), so a client browses `/tidal` and `/fs` with the model](./library.md)), so a client browses `/tidal` and `/fs` with the
@ -37,43 +48,63 @@ can be queued alongside a track from another.
## Each provider is optional ## Each provider is optional
A provider is only mounted when its client initializes successfully. The A provider appears in the library only if it got that far, and three
orchestrator holds each one as an optional handle and only adds its child independent things decide it:
to the root listing when the client exists.
1. **Was it built in?** Every provider sits behind a Cargo feature (all on by
default). A binary built without one can never mount it — see [Tailored
builds](../build-features.md), and `crabidy-server features` to see what a
binary has.
2. **Is it enabled?** The `providers` list in `crabidy-server.toml` turns
compiled-in providers on and off without a rebuild — see
[Configuration](../config.md#enabling-and-disabling-providers).
3. **Did it initialize?** Only a client that came up successfully is mounted.
```admonish note ```admonish note
Initialization failure is **non-fatal**. A broken config file, missing Initialization failure is **non-fatal**. A broken config file, missing
credentials, or a missing helper binary drops that one subtree with a credentials, or a missing helper binary drops that one subtree with a
warning — the server and every other provider keep running. The warning — the server and every other provider keep running. The only
orchestrator never fails to start because a single provider could not. exception is Tidal: a Tidal config that exists but cannot be loaded aborts
startup rather than silently dropping your account.
``` ```
This means the exact set of top-level entries you see depends on your So the exact set of top-level entries depends on your build *and* your
configuration: `/tidal` needs a login, `/youtube` needs its client to configuration: `/tidal` needs a login, `/abs` needs
build, `/fs` needs a readable root directory, and `/crabidy` is always credentials, `/youtube` needs its client to build, `/fs` needs a readable
present because the server owns it. root directory, and `/crabidy` needs a writable state directory.
## Today's providers ## Today's providers
- **[`/tidal`](./providers/tidal.md)** — streams from a Tidal account Listed in the order the library root serves them.
via the `tidaldy` crate: playlists, favorite artists and their albums,
mixes, and search. Credentials live in `tidaly.toml`. - **`/crabidy`** — where the server writes everything you save: saved queues,
- **[`/youtube`](./providers/youtube.md)** — YouTube search and the bookmarks, and downloaded captures, backed by a content-addressed audio
logged-in account's playlists via the `ytdy` crate. Metadata is store. It has its own page: [The crabidy store](./store.md).
extracted in-process; config lives in `ytdy.toml`. - **[`/abs`](./providers/abs.md)** — audiobooks from a self-hosted
- **[`/fyyd`](./providers/fyyd.md)** — podcast search via the `fyyd` audiobookshelf server. Needs a server URL and an API key (`abs.toml`).
crate and fyyd's keyless public API: find a podcast, drill into its - **[`/fs`](./providers/fs.md)** — a local music folder: directories become
episodes, and play them. Config (all optional) lives in `fyyd.toml`. nodes, `*.cbd-track.toml` files become tracks. No login (`fsdy.toml`).
- **[`/fs`](./providers/fs.md)** — serves a local music folder. Folders - **[`/fyyd`](./providers/fyyd.md)** — podcast search over fyyd's public
become nodes and `*.cbd-track.toml` files become tracks. Config lives API: find a podcast, drill into its episodes, play them. No account of any
in `fsdy.toml`. kind (`fyyd.toml`).
- **`/crabidy`** — where the server writes the things you save (saved - **[`/jamendo`](./providers/jamendo.md)** — Jamendo's catalogue of
queues, bookmarks, and downloaded captures), backed by a Creative-Commons music, by search or album. Works out of the box on a
content-addressed store. It has its own page: [The crabidy shipped app key; bring your own for your own rate limit (`jamendo.toml`).
store](./store.md). - **[`/soundcloud`](./providers/soundcloud.md)** — SoundCloud search, link
- **`/orphans`** — store audio that no save references any more, surfaced resolving, and playback with no credentials at all; an optional token adds
for manual reclamation (rename, delete, or queue). See [Reclaiming your likes and playlists (`soundcloud.toml`).
orphans](./store.md#reclaiming-orphans--orphans). - **[`/tidal`](./providers/tidal.md)** — a Tidal account: playlists,
favorite artists and their albums, mixes, and search. Interactive device
login on first start (`tidaly.toml`).
- **[`/youtube`](./providers/youtube.md)** — YouTube search, plus an
account's saved playlists when cookies are configured (`ytdy.toml`).
- **`/orphans`** — store audio that no save references any more, surfaced so
you can reclaim it (rename it into a save, queue it, or delete it). It is a
view over the store, not a separate source, so it comes and goes with
`/crabidy`.
Each provider page documents its tree, how to log in where that applies, and
every option in its config file.
Several providers expose a **search** subtree in which you create nodes Several providers expose a **search** subtree in which you create nodes
whose titles are your search terms; see [Search](./providers/search.md) whose titles are your search terms; see [Search](./providers/search.md)

79
docs/src/providers/abs.md Normal file
View File

@ -0,0 +1,79 @@
# audiobookshelf — /abs
<!-- toc -->
Mounts a self-hosted [audiobookshelf](https://www.audiobookshelf.org/)
server at `/abs`, so your audiobooks browse, queue, and play like any other
part of the library. Audiobooks only for now — podcast libraries on the ABS
server are not listed.
## Logging in
The provider needs two things in `abs.toml`: your server's base URL and an
**API key**. Without both, `/abs` does not mount and the rest of the server
carries on.
1. Open your audiobookshelf web UI as the user whose libraries you want.
2. Go to **Settings → Users → (your user) → API Keys** (on older versions:
**Settings → API Keys**) and create a key.
3. Put it in `~/.config/crabidy/abs.toml`:
```toml
base_url = "https://audiobookshelf.example.com"
api_key = "<the key you just created>"
```
4. Restart the server. `/abs` appears with one child per book library.
There is no interactive login and no token to refresh: the API key is a
long-lived bearer token. Revoke it in the same screen to cut access.
```admonish warning
The API key grants access to your audiobookshelf account. Crabidy redacts it
from logs and never prints it, but keep `abs.toml` private — it is stored in
cleartext, like every other provider credential.
```
## The tree
```text
/abs
└── <library> one node per "book" library
├── search create a term with `%`
│ └── <term> books matching the term
│ └── <book> the book's audio files as tracks
└── <book> an audiobook
└── <file> one audio file = one track
```
- A **library** lists its books plus a creatable `search` node. Search is
scoped per library: a term created under one library is not visible under
another. See [Search](./search.md).
- A **book** lists its audio files as tracks, in the server's order. Queue or
capture the whole book, or a single file.
- An item with no audio (an ebook-only entry) is listed but not queueable.
## Playback
Each file streams directly from audiobookshelf over HTTP with range
requests — no transcoding session is opened, and the audio is whatever the
server stores (commonly Opus or MP3; Opus needs the `opus` build feature, see
[Tailored builds](../build-features.md)). Captures (`W`) download the same
files into the content store.
## Configuration — `abs.toml`
```toml
# Required — without both of these the provider does not mount.
base_url = "https://audiobookshelf.example.com"
api_key = "<your audiobookshelf API key>"
# Optional, defaults shown.
items_per_library = 200 # books listed per library
search_results = 50 # books listed per search term
call_timeout_secs = 30 # per-request timeout
```
Listings are fetched fresh on every visit and capped by the values above.
Only your typed search terms are remembered, and only in memory until the
server restarts.

View File

@ -58,7 +58,7 @@ The `[playable]` table must set **exactly one** of five fields:
- `link` — an absolute crabidy path owned by another provider, e.g. - `link` — an absolute crabidy path owned by another provider, e.g.
`/tidal/artists/3634161/536243361`. `/tidal/artists/3634161/536243361`.
- `store` — the bare name of an entry in the content-addressed store, - `store` — the bare name of an entry in the content-addressed store,
written by download captures (see [The crabidy store](./store.md)). written by download captures (see [The crabidy store](../store.md)).
- `skipped = true` — the track has no playable audio; playback skips it. - `skipped = true` — the track has no playable audio; playback skips it.
(`skipped = false` counts as unset.) (`skipped = false` counts as unset.)
@ -115,4 +115,4 @@ skipped during listing, so they cannot lead out of the root either.
Creating track files through the library is not supported here; they are Creating track files through the library is not supported here; they are
made and edited with normal file tools. (Other instances of the same made and edited with normal file tools. (Other instances of the same
crate — the `/crabidy` saves — do support renaming and deleting; see crate — the `/crabidy` saves — do support renaming and deleting; see
[The crabidy store](./store.md).) [The crabidy store](../store.md).)

View File

@ -74,5 +74,5 @@ Podcasts and their episode lists are downloadable: `W` captures a
podcast's episodes into `/crabidy`, the same way it works for any other podcast's episodes into `/crabidy`, the same way it works for any other
provider. provider.
For the tree shape and design decisions, see Everything the provider needs is public: fyyd requires no account, no key,
`architecture/fyyd-provider.md` in the source tree. and no login of any kind.

View File

@ -0,0 +1,79 @@
# Jamendo — /jamendo
<!-- toc -->
Mounts [Jamendo](https://www.jamendo.com) at `/jamendo`: search a large
catalogue of Creative-Commons licensed music, play it, and browse the album a
track belongs to.
## Logging in
There is nothing to do. Jamendo has no user login — its public catalogue
needs only an **app key** (`client_id`) that identifies the *application*, not
you, and crabidy ships a working default. `/jamendo` mounts out of the box,
and the key it used is written into `jamendo.toml` so you can always see which
one is in force.
You may still want your own key, because Jamendo's rate limit is **per key**:
a shipped default is a shared budget, and a heavy user is better off with
their own.
1. Register at [devportal.jamendo.com](https://devportal.jamendo.com) and
create an application.
2. Copy its **Client ID**.
3. Put it in `~/.config/crabidy/jamendo.toml`:
```toml
client_id = "<your jamendo client id>"
```
4. Restart the server. A configured key always wins over the default.
No token refresh, no expiry, nothing to maintain. The key is treated as a
semi-secret: it is redacted from logs, but keep the file private.
## The tree
```text
/jamendo
├── search create a search term with `%`
│ └── <term> matching tracks
├── track/<id> a single track (canonical address)
└── album/<id> an album and its tracks (canonical address)
```
`search` is a creatable node: press `%` and type a term, and the term becomes
a child node holding its results. Rename it (`e`) to re-run the search,
delete it (`d`) to drop it. Terms live in memory only, so a restart forgets
the list. See [Search](./search.md).
Tracks and albums are addressed by id whatever browse node you reached them
through, so a queued track keeps working after the search term that found it
is gone. From a track you can navigate to its album to hear the rest of it.
## Playback
Jamendo hands out a direct MP3 URL per track, which the player streams over
its ordinary HTTP path — no HLS, no helper binary, and seeking works.
Captures (`W`) download those files into the content store.
The default `audioformat` is `mp31`, Jamendo's freely streamable MP3. `mp32`
(higher bitrate) is not reliably provisioned for streaming and comes back
empty for many tracks, so it is a poor default; a Pro account can set it
explicitly. If a track has no audio in the requested format the provider
falls back rather than failing the listing.
## Configuration — `jamendo.toml`
```toml
# The app key. Unset (or blank) uses the key crabidy ships with, which is
# written back here on first start so you can see what is in use. Replace it
# with your own from devportal.jamendo.com to get your own rate limit.
# client_id = "..."
# Optional, defaults shown.
audioformat = "mp31" # "mp31" (default) or "mp32" (see above)
search_results = 50 # tracks per search term (Jamendo caps at 200)
album_tracks = 200 # tracks listed per album
call_timeout_secs = 30 # per-request timeout
```

View File

@ -0,0 +1,92 @@
# SoundCloud — /soundcloud
<!-- toc -->
Mounts SoundCloud at `/soundcloud`: search the public catalogue, paste a
track or playlist link to resolve it, and — if you configure a token — browse
your own likes and playlists.
## Logging in (optional)
SoundCloud is the one provider that works with **no credentials at all**.
Public browsing and playback need only a `client_id`, which the provider
scrapes from SoundCloud's own web app on first start and then writes back
into `soundcloud.toml` so later starts reuse it. Nothing to do.
A login only adds two nodes — `likes` and `playlists`. To enable them, put an
OAuth token in `~/.config/crabidy/soundcloud.toml`:
```toml
oauth_token = "<your soundcloud oauth token>"
```
The token is the value SoundCloud's own web app uses for your session; read
it out of a logged-in browser (the `oauth_token` cookie on `soundcloud.com`,
or the `Authorization: OAuth …` header on any API request in the network
tab). There is no device-code flow — SoundCloud does not offer public API
registration any more, so this is the available route.
Restart the server after adding it: `likes` and `playlists` appear at the
provider root. If the token is rejected or expires, those two nodes are the
only thing lost — search, resolve, and playback keep working.
```admonish warning
The token is a live session credential for your account. It is redacted from
logs and from every debug dump, but `soundcloud.toml` holds it in cleartext —
keep the file private, and revoke the session in SoundCloud's settings if it
leaks.
```
## The tree
```text
/soundcloud
├── search create a search term with `%`
│ └── <term> matching tracks
├── resolve create an entry from a soundcloud.com URL
│ └── <url> the track or playlist that URL points at
├── likes your liked tracks (only with a token)
└── playlists your playlists (only with a token)
└── <playlist> its tracks
```
Both `search` and `resolve` are creatable nodes: press `%` and type a search
term or paste a URL. The entry becomes a child node you can rename (`e`,
which re-runs it) or delete (`d`). Entries live in memory, so a server
restart forgets the list — navigating back to a remembered path recreates it.
See [Search](./search.md).
Tracks and playlists are addressed canonically by id
(`/soundcloud/track/<id>`, `/soundcloud/playlist/<id>`), whichever browse
node you reached them through, so a queued track keeps working after you
delete the search term that found it.
## Playback
SoundCloud serves audio as **HLS**: the provider resolves a signed `.m3u8`
media URL and the player streams its MP3 segments in order as one continuous
stream. That URL is a short-lived secret and never appears in logs. Seeking
within an HLS track is not supported (the stream is forward-only); everything
else — play, pause, next, capture — behaves normally.
## Configuration — `soundcloud.toml`
```toml
# Scraped and written back by the provider; you normally never touch these.
# Delete them to force a fresh scrape on the next start.
# client_id = "..."
# app_version = "..."
# Optional login: adds the `likes` and `playlists` nodes.
# oauth_token = "..."
# Optional, defaults shown.
search_results = 50 # tracks per search term
playlist_tracks = 500 # tracks hydrated per playlist
call_timeout_secs = 30 # per-request timeout
hls_deadline_secs = 300 # total deadline for fetching one HLS stream
```
If SoundCloud rotates its web app and the stored `client_id` stops working,
the provider re-scrapes on the next start; if it cannot obtain one at all,
`/soundcloud` is dropped with a warning and the server runs on.

View File

@ -85,6 +85,5 @@ Search results and playlists are downloadable: `W` captures a subtree's
audio into `/crabidy`. Under the stream throttle this is slow, but audio into `/crabidy`. Under the stream throttle this is slow, but
captures are resumable — re-capture the same name to continue. captures are resumable — re-capture the same name to continue.
For the full analysis behind the rustypipe/`yt-dlp` split, read For the crate-level notes on the rustypipe/`yt-dlp` split, see
`ytdy/README.md` and `architecture/youtube-rustypipe.md` in the source `ytdy/README.md`.
tree.

View File

@ -20,9 +20,8 @@ The provider only reads: it lists the tomls and resolves store links when a
captured track plays. captured track plays.
```admonish note ```admonish note
There is one `/crabidy` provider, not the older `/queues`, `/bookmarks`, and Bookmarks (`w`) and captures (`W`) are two kinds of save in the same tree,
`/captures` split. Bookmarks (`w`) and captures (`W`) are now two kinds of told apart by the captured marker — not two separate providers.
save that coexist in the same tree, told apart by the captured marker.
``` ```
## The two roots ## The two roots
@ -252,9 +251,15 @@ apply:
The set is recomputed on every visit (no cached list), so a fresh capture that The set is recomputed on every visit (no cached list), so a fresh capture that
re-references an entry makes it drop off the list. "Referenced" means reachable re-references an entry makes it drop off the list. "Referenced" means reachable
through a mounted file provider: a `scan --capture` toml under a folder that is through a mounted file provider: a `scan --capture` toml under a folder that is
not mounted under `/fs` is not seen, so its target shows here as an orphan. See not mounted under `/fs` is not seen, so its target shows here as an orphan.
`architecture/orphans.md` for the boundary and the (benign) capture-then-delete
race. ```admonish note
The list is a snapshot taken when you open `/orphans`. A capture that starts
right after can adopt an entry you are still looking at, so deleting a
just-listed orphan can in principle remove audio a capture has meanwhile
claimed — the capture then re-downloads it. Nothing is corrupted; revisit the
node for a fresh list.
```
## The captured marker ## The captured marker

View File

@ -1,8 +1,11 @@
# fsdy — the filesystem provider # fsdy — the filesystem provider
Mounts a local directory at **`/fs`** in the crabidy library. The same Mounts a local directory at **`/fs`** in the crabidy library. The same
engine also powers the server-managed mounts `/queues`, `/bookmarks`, engine also powers the server-managed **`/crabidy`** mount (saved queues,
and `/captures` — everything below applies to their on-disk format too. bookmarks, and captures), so everything below applies to its on-disk format
too.
No login: the provider only reads a directory you point it at.
## How it works ## How it works
@ -11,8 +14,8 @@ becomes a queueable library node; every file ending in
**`.cbd-track.toml`** becomes a track; everything else (audio files, **`.cbd-track.toml`** becomes a track; everything else (audio files,
covers, hidden entries) is invisible to the library. Listing order is covers, hidden entries) is invisible to the library. Listing order is
case-insensitive by file name — prefix files with numbers to fix an case-insensitive by file name — prefix files with numbers to fix an
order (the capture/bookmark writers use `0001`-style prefixes for order (the save writers use `0001`-style prefixes for exactly this
exactly this reason). reason).
A track file carries the track's metadata plus a reference to the A track file carries the track's metadata plus a reference to the
playable thing: playable thing:
@ -50,18 +53,33 @@ fails at play time), which keeps cycles impossible.
# root = "/home/me/music" # root = "/home/me/music"
``` ```
## The server-managed instances ## The server-managed `/crabidy` instance
`/queues`, `/bookmarks`, and `/captures` are fsdy instances over `/crabidy` is a second instance of this same provider, over the server's
`~/.config/crabidy/{queues,bookmarks,captures}/`, written by the server state directory `~/.local/state/crabidy/`, written by the server (queue
(queue persistence, `w`, `W`) and readable/editable with any file persistence, `w`, `W`) and readable with any file manager — it is just
manager — they are just folders of the track files described above. 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.
`/captures` additionally lets `d` delete *anything* in its tree — a - Each top-level folder is one save. It can be renamed (`e`) and deleted
whole capture, a nested album folder, or a single track (which takes (`d`) from a client.
its downloaded audio file with it). Because that removes data from - `current` is the continuously persisted play queue and is protected: you
disk that was slow to download, the TUI asks for confirmation first cannot save over that name.
(`delete <name>? [y/N]`); deletes elsewhere stay unconfirmed. - A captured track's `[playable]` is a `store` entry naming a file in the
content store at `~/.local/share/crabidy/`, which is shared and
de-duplicated across every save.
- Deleting inside `/crabidy` removes only the metadata toml, **never** the
shared store audio — other saves may point at the same file. Audio that
ends up referenced by nothing shows up under `/orphans`, where you can
delete it for real.
The `scan` subcommand writes exactly these track files for a folder of audio,
so an existing music collection browses under `/fs` without any manual work:
```sh
crabidy-server scan ~/Music # index in place
crabidy-server scan ~/Downloads --capture # copy the audio into the store
```
Building the server without the `fs` cargo feature drops this provider, the
content store, `/crabidy`, `/orphans`, queue persistence, and `scan` — they
all share the same on-disk machinery.

View File

@ -36,7 +36,15 @@ All options are optional (fyyd needs no credentials):
# base_url = "https://api.fyyd.de/0.2" # API base override # base_url = "https://api.fyyd.de/0.2" # API base override
``` ```
All network access goes through the `Fyyd` trait ## Logging in
(`fyyd/src/api.rs`), so the provider's tree/path logic is unit-tested
with a fake and no network. See `architecture/fyyd-provider.md` for the Nothing to do — fyyd's search API is public. There is no account, no key, and
tree shape and design decisions. no token to maintain.
## Notes
All network access goes through the `Fyyd` trait (`fyyd/src/api.rs`), so the
provider's tree/path logic is unit-tested with a fake and no network.
Build the server without the `fyyd` cargo feature to leave this provider out
of the binary entirely.

77
jamendody/README.md Normal file
View File

@ -0,0 +1,77 @@
# jamendody — the Jamendo provider
Mounts [Jamendo](https://www.jamendo.com) at **`/jamendo`** in the crabidy
library: search a large catalogue of Creative-Commons licensed music, play it,
and browse the album a track came from.
## Logging in
Nothing to do. Jamendo has no user login — the public catalogue needs only an
**app key** (`client_id`) identifying the *application*, not you, and this
crate ships a working default (`DEFAULT_CLIENT_ID`). `/jamendo` mounts with no
configuration, and `init` writes the key it used into `jamendo.toml` so the
effective value is always visible.
Bring your own key if you use Jamendo heavily — the rate limit is **per key**,
so the shipped default is a shared budget:
1. Register at [devportal.jamendo.com](https://devportal.jamendo.com) and
create an application.
2. Copy its **Client ID** into `~/.config/crabidy/jamendo.toml` as
`client_id`.
3. Restart the server. A configured key always wins over the default.
Nothing expires and there is no refresh flow. The key is redacted from `Debug`
and never logged, but keep the file private.
## How it works
```text
/jamendo
├── search create a search term with `%`
│ └── <term> matching tracks
├── track/<id> a single track (canonical address)
└── album/<id> an album and its tracks (canonical address)
```
`search` is creatable: press `%` and type a term, and the term becomes a child
node holding its results. `e` renames it (re-running the search), `d` deletes
it. Terms live in memory, so a restart forgets the list.
Tracks and albums are addressed by id whatever browse node you reached them
through, so a queued track survives deleting the search term that found it.
From a track you can navigate to its album to hear the rest.
**Playback is a direct MP3 URL** streamed over the player's ordinary HTTP
path — no HLS, no helper binary, and seeking works. Captures (`W`) download
those files into the content store.
## Configuration — `~/.config/crabidy/jamendo.toml`
```toml
# The app key. Unset (or blank) uses the key this crate ships with, which is
# written back here on first start. Replace it with your own from
# devportal.jamendo.com for your own rate limit.
# client_id = "..."
# Optional, defaults shown.
# audioformat = "mp31" # "mp31" (default) or "mp32"; see below
# search_results = 50 # tracks per search term (Jamendo caps at 200)
# album_tracks = 200 # tracks listed per album
# call_timeout_secs = 30 # per-request timeout
```
`mp31` is Jamendo's freely streamable MP3. `mp32` (higher bitrate) is **not**
reliably provisioned for the streaming URL and comes back empty for many
tracks, which is why it is not the default; a Pro account can set it
explicitly. When a track has no audio in the requested format the provider
falls back rather than failing the listing.
## Failure behavior
- Missing or empty `client_id`: the shipped default is used.
- A revoked or rate-limited key: listings error, the provider stays mounted.
- API or network failure: the affected listing errors, the provider stays.
Build the server without the `jamendo` cargo feature to leave this provider
out of the binary entirely.

90
soundclouddy/README.md Normal file
View File

@ -0,0 +1,90 @@
# soundclouddy — the SoundCloud provider
Mounts SoundCloud at **`/soundcloud`** in the crabidy library: search the
public catalogue, resolve a track or playlist link, and — with a token — your
own likes and playlists.
## Logging in (optional)
This is the one provider that works with **no credentials at all**. Public
browsing and playback need only a `client_id`, which the provider scrapes
from SoundCloud's own web app on first start and writes back into
`soundcloud.toml` so later starts reuse it. Nothing to do.
A login adds exactly two nodes, `likes` and `playlists`. To enable them, put
an OAuth token in the config:
```toml
oauth_token = "<your soundcloud oauth token>"
```
The token is what SoundCloud's own web app uses for your session; read it out
of a logged-in browser — the `oauth_token` cookie on `soundcloud.com`, or the
`Authorization: OAuth …` header on any API request in the network tab.
SoundCloud no longer offers public API registration, so there is no
device-code flow to use instead.
Restart the server; `likes` and `playlists` appear. If the token is rejected
or expires, only those two nodes are lost — search, resolve, and playback
keep working.
**The token is a live session credential.** It is redacted from `Debug` and
never logged, but `soundcloud.toml` stores it in cleartext. Keep the file
private, and revoke the session in SoundCloud's settings if it leaks.
## How it works
```text
/soundcloud
├── search create a search term with `%`
│ └── <term> matching tracks
├── resolve create an entry from a soundcloud.com URL
│ └── <url> the track or playlist it points at
├── likes your liked tracks (token only)
└── playlists your playlists (token only)
└── <playlist> its tracks
```
`search` and `resolve` are creatable: press `%` and type a term or paste a
URL. The entry becomes a child node, renamable (`e`, re-runs it) and
deletable (`d`). Entries live in memory, so a restart forgets the list;
navigating to a remembered path recreates it.
Tracks and playlists are addressed canonically by id
(`/soundcloud/track/<id>`, `/soundcloud/playlist/<id>`) whatever browse node
you reached them through, so a queued track survives deleting the search term
that found it.
**Playback is HLS.** The provider resolves a signed `.m3u8` media URL and the
player streams its MP3 segments in order as one continuous stream. That URL
is a short-lived secret and is never logged. Seeking inside an HLS track is
not supported (the stream is forward-only); everything else, captures
included, behaves normally.
## Configuration — `~/.config/crabidy/soundcloud.toml`
```toml
# Scraped and written back by the provider — you normally never touch these.
# Delete them to force a fresh scrape on the next start.
# client_id = "..."
# app_version = "..."
# Optional login: adds the `likes` and `playlists` nodes.
# oauth_token = "..."
# Optional, defaults shown.
# search_results = 50 # tracks per search term
# playlist_tracks = 500 # tracks hydrated per playlist
# call_timeout_secs = 30 # per-request timeout
# hls_deadline_secs = 300 # total deadline for fetching one HLS stream
```
## Failure behavior
- No `client_id` obtainable: `/soundcloud` is dropped with a warning, the
server runs on.
- A rotated/stale `client_id`: re-scraped on the next start.
- Rejected token: `likes`/`playlists` disappear; nothing else changes.
Build the server without the `soundcloud` cargo feature to leave this
provider out of the binary entirely.

View File

@ -3,14 +3,28 @@
Mounts your Tidal account at **`/tidal`** in the crabidy library, using Mounts your Tidal account at **`/tidal`** in the crabidy library, using
Tidal's web API. Tidal's web API.
## How it works ## Logging in
On first start the provider runs Tidal's OAuth **device login**: the On first start the provider runs Tidal's OAuth **device login**:
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 1. Start the server. It prints a `link.tidal.com` verification URL to its
provider finishes logging in by itself. The obtained tokens are written stdout — for `cbd` (which owns the terminal) look in the log file under
back into the config file and refreshed automatically from then on; you `~/.local/state/crabidy/` instead.
should not need to log in again. 2. Open the URL in a browser and authorize the device. The server polls
meanwhile; the attempt expires after a few minutes, so restart it if you
miss the window.
3. The provider finishes logging in by itself. The tokens are written back
into `tidaly.toml` and refreshed automatically from then on — you should
not need to log in again.
To switch accounts or recover from an invalidated session, delete the
`[login]` section from `tidaly.toml` and restart.
**Note:** a `tidaly.toml` that exists but cannot be parsed **aborts server
startup** rather than silently dropping your account — unlike every other
provider, whose config errors only cost that one subtree.
## How it works
The tree offers your playlists, favorite artists (with their albums and The tree offers your playlists, favorite artists (with their albums and
tracks), and a **search** node: press `%` under `/tidal/search` to tracks), and a **search** node: press `%` under `/tidal/search` to
@ -19,7 +33,7 @@ its results (tracks, artists, albums). Terms are renamable (`e`,
re-runs the search) and deletable (`d`). re-runs the search) and deletable (`d`).
Everything queueable is also capturable: `w` bookmarks a subtree as Everything queueable is also capturable: `w` bookmarks a subtree as
links, `W` downloads a subtree's audio into `/captures/<name>`. links, `W` downloads a subtree's audio into `/crabidy/<name>`.
## Configuration — `~/.config/crabidy/tidaly.toml` ## Configuration — `~/.config/crabidy/tidaly.toml`
@ -59,3 +73,5 @@ Notes:
login runs on the next start. login runs on the next start.
- If Tidal ever invalidates the session (long offline periods), delete - If Tidal ever invalidates the session (long offline periods), delete
the `[login]` section and restart. the `[login]` section and restart.
- Build the server without the `tidal` cargo feature to leave this provider
out of the binary entirely.

View File

@ -23,13 +23,28 @@ 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 throttled ~32 KB/s (still ~2× audio bitrate). Without the binary the
provider still works, but playback of a track stops after roughly a provider still works, but playback of a track stops after roughly a
minute. Streams prefer `audio/mp4` (AAC), the format the built-in minute. Streams prefer `audio/mp4` (AAC), the format the built-in
player decodes; captures therefore store `.m4a` files. See player decodes; captures therefore store `.m4a` files.
`architecture/youtube-rustypipe.md` for the full analysis.
Search results and playlists are downloadable: `W` captures them with Search results and playlists are downloadable: `W` captures them with
audio into `/captures/<name>` (slow under the throttle, but captures audio into `/crabidy/<name>` (slow under the throttle, but captures
are resumable — re-capture the same name to continue). are resumable — re-capture the same name to continue).
## Logging in (optional)
Search and playback need no login. A cookie export only adds
`/youtube/playlists`, the saved playlists of an account:
1. Export your YouTube cookies in Netscape `cookies.txt` format — a browser
extension like "Get cookies.txt LOCALLY" does it. Doing this from an
Incognito window you close afterwards keeps the session from being
rotated out from under the file.
2. Point `cookies` at the file (see below) and restart the server.
3. The provider caches the rotated session cookie under
`~/.config/crabidy/rustypipe/`, so the export only has to be valid once.
Rejected or missing cookies degrade to logged-out with a warning; nothing
else is affected.
## Configuration — `~/.config/crabidy/ytdy.toml` ## Configuration — `~/.config/crabidy/ytdy.toml`
All options with their defaults: All options with their defaults:
@ -76,4 +91,7 @@ contents (paths only), and stream URLs are redacted from logs.
enforcement fluctuates; usually transient. If it persists, update enforcement fluctuates; usually transient. If it persists, update
`yt-dlp`. `yt-dlp`.
Build the server without the `youtube` cargo feature to leave this provider
(and the `rustypipe` dependency) out of the binary entirely.
[rustypipe]: https://crates.io/crates/rustypipe [rustypipe]: https://crates.io/crates/rustypipe