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
/
├── 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`),
│ 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,
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
@ -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
[docs/src/build-features.md](docs/src/build-features.md) for the full
table (and what `fs` takes with it), and
[architecture/build-features.md](architecture/build-features.md) for the
design.
[docs/src/build-features.md](docs/src/build-features.md) for the full table,
what `fs` takes with it, and more examples.
**Note:** `--no-default-features` on its own drops **every** provider (it
compiles and runs, but plays nothing). Always name what you want.
## Configuration
@ -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
filled in.
| File | Component | Documentation |
| -------------------- | ---------- | -------------------------------------- |
| `tidaly.toml` | Tidal | [tidaldy/README.md](tidaldy/README.md) |
| `ytdy.toml` | YouTube | [ytdy/README.md](ytdy/README.md) |
| `fyyd.toml` | podcasts | [fyyd/README.md](fyyd/README.md) |
| `abs.toml` | audiobooks | [absdy/README.md](absdy/README.md) |
| `fsdy.toml` | local fs | [fsdy/README.md](fsdy/README.md) |
| `cbd-tui.toml` | `cbd-tui` | below |
| `cbd.toml` | `cbd` | below (same options as `cbd-tui.toml`) |
| `crabidy-server.toml`| server | below (providers + auth) |
| File | Configures | Login |
| --------------------- | ---------------- | ------------------ |
| `abs.toml` | audiobookshelf | URL + API key |
| `fsdy.toml` | local files | — |
| `fyyd.toml` | podcasts | none needed |
| `jamendo.toml` | Jamendo | none (key shipped) |
| `soundcloud.toml` | SoundCloud | optional token |
| `tidaly.toml` | Tidal | device login |
| `ytdy.toml` | YouTube | optional cookies |
| `cbd-tui.toml` | `cbd-tui` | server role + pw |
| `cbd.toml` | `cbd` | server role + pw |
| `crabidy-server.toml` | the server | its `[auth]` hashes|
Each provider's README — linked from the library tree at the top — explains
**how to log in** and documents every option in its file. The same material
is in the book under [`docs/src/providers/`](docs/src/providers/); the client
and server files are covered below.
Provider credentials and the client password are stored in **cleartext**
these are config files, not a keyring. Keep `~/.config/crabidy/` private.
Crabidy redacts secrets from logs, errors, and config dumps.
The server-managed `crabidy` provider does not live under `~/.config`. Its
track-file tree (saved queues, bookmarks, and captures) lives in
@ -87,7 +108,7 @@ track-file tree (saved queues, bookmarks, and captures) lives in
captures lives in a single content-addressed store under
`~/.local/share/crabidy/` (the *data* directory), shared and de-duplicated
across saves. Neither needs configuration; see
[architecture/crabidy-store.md](architecture/crabidy-store.md).
[docs/src/store.md](docs/src/store.md).
### `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:
```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
@ -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
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
By default the server is open: everyone who can reach the port has
full control. Setting password hashes in `[auth]` locks the server
**from the top down** — each password you set lowers what a
no-credential caller may do, while a matching password elevates a
caller to that role (see `architecture/roles-auth.md`):
caller to that role:
- **owner** — everything (the normal user).
- **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`.
`--capture` copies each file into the content store (the toml points
there); `--move` moves it instead of copying.
- `audio-devices [device]` — list the audio output devices, or pin one into
`[audio] device` (see above).
- `features` — print the build features this binary has (see [Tailored
builds](#tailored-builds)).
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)
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
queue) as a new folder under `/crabidy/<name>` of **link** files —
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
while audio plays (the server taps its own output, runs the FFT, and
streams the bars, so it works whether the server is local or remote).
Toggle it at runtime with `v`, or set the startup default with
`spectrum = false` in the client config.
Toggle it at runtime with `f`, or set the startup default with
`spectrum = false` in the client config. Servers built without the
`spectrum` feature simply never send bars.
## Web client
@ -330,9 +369,10 @@ devenv shell -- build-web # writes cbd-web/dist
cargo build -p crabidy-server # embeds it
```
Build the server with `--no-default-features` for a headless,
gRPC-only binary. See [cbd-web/README.md](cbd-web/README.md) for the
dev loop and details.
For a headless, gRPC-only binary, build with every feature *except*
`web-ui` — e.g. `--no-default-features --features
all-providers,opus,spectrum`. See [cbd-web/README.md](cbd-web/README.md) for
the dev loop and details.
## Nix packages and cross-compiling
@ -377,6 +417,12 @@ 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.
```sh
devenv shell -- bash -lc 'cargo test --workspace --exclude cbd-web'
devenv shell -- check-features # the build-feature matrix
devenv shell -- docs # serve the documentation book
```
`AGENTS.md` (and `CLAUDE.md`) carry the coding rules and the toolchain
conventions. The reference documentation for the system itself lives in
[`docs/`](docs/src/).

View File

@ -2,14 +2,24 @@
Mounts a self-hosted [audiobookshelf](https://www.audiobookshelf.org/) (ABS)
server at `/abs`, so you can browse, search, and play your audiobooks from
crabidy. See [the design doc](../architecture/audiobookshelf-provider.md) for
the design and decisions.
crabidy. Audiobooks only for now — podcast libraries on the ABS server are
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`
The provider needs your server URL and an API key. Without both, `/abs` does
not mount (the rest of the server is unaffected).
```toml
# Required.
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
```
Create an API key in audiobookshelf under **Settings → Users → (your user) →
API Keys** (or **Settings → API Keys** on older versions). The key is a
secret: it is redacted from logs and never printed, but keep `abs.toml`
private anyway.
The key is a secret: it is redacted from logs and never printed, but keep
`abs.toml` private anyway (it is stored in cleartext).
## The library tree
@ -52,3 +60,7 @@ private anyway.
HTTP range requests — no transcoding session.
- Listings are capped (see the config) and fetched fresh; only your typed
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
A [Leptos](https://leptos.dev) client-side WASM app with the same
functionality as `cbd-tui`, served by `crabidy-server` itself. See
`architecture/web-client.md` for the design.
functionality as `cbd-tui`, served by `crabidy-server` itself.
## 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
second API surface — feature parity is structural. The server wraps
its existing gRPC service in `tonic-web`, so the browser and the TUI
hit identical `/crabidy.v1.CrabidyService/…` paths, and the role
auth layer (`architecture/roles-auth.md`) gates both.
hit identical `/crabidy.v1.CrabidyService/…` paths, and the same role
auth layer gates both.
- **Serving**: the built bundle (`cbd-web/dist`) is embedded into
`crabidy-server` at compile time behind the default-on `web-ui`
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
Everything the TUI does: browse the library (`j`/`k`/`h`/`l`, click),
marks, create/rename/delete nodes (`%`/`e`/`d`, with the capture-delete
`y/N` confirmation), bookmark and capture (`w`/`W`, with live progress
lines and skipped-track marking), the full queue and playback controls,
volume, shuffle/repeat, and a `?` help overlay listing the keys. Keys
mirror the TUI; every key also has a clickable control. A light/dark
theme follows the OS and can be toggled (persisted). The accent color
is the crab orange-red.
marks, create/rename/delete nodes (`%`/`e`/`d`), bookmark and capture
(`w`/`W`, with live progress lines and skipped-track marking), the full queue
and playback controls, volume, shuffle/repeat, and a `?` help overlay listing
the keys. Keys mirror the TUI; every key also has a clickable control. A
light/dark theme follows the OS and can be toggled (persisted). The accent
color is the crab orange-red.
When the server requires credentials, a login form collects the role
(`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.
Building `crabidy-server` without a `cbd-web/dist` present is fine — it
embeds a placeholder page telling you to run `build-web`. Build the
server `--no-default-features` to drop the web client (and the
`tonic-web` layer) entirely.
embeds a placeholder page telling you to run `build-web`. To drop the web
client (and the `tonic-web` layer) entirely, build the server without its
`web-ui` cargo feature — e.g. `--no-default-features --features
all-providers,opus,spectrum`; see `docs/src/build-features.md`.
## 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)
- [Tidal — /tidal](./providers/tidal.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)
- [Search](./providers/search.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
synthetic root `/` itself — a node whose children are the mounted
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.
providers — and routes everything else by the **first path segment**.
Every provider except Tidal is optional: if its config or backing store
fails to initialize, the orchestrator logs a warning and simply omits
that child from the root, and the server runs without it. Because the
orchestrator sits behind an `Arc`, a slow library walk is spawned onto
its own task while the loop keeps serving other commands.
It holds one **mount** per provider: the library root it owns (`/tidal`,
`/fs`, …), the name shown in the root listing, and the client itself behind a
trait object. Every routed call — `get_lib_node`, `resolve_tracks_into`,
`get_urls_for_track`, `get_metadata_for_track`, and the
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
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
volume <DELTA>` control playback.
## Server commands: `guard` and `scan`
## Server commands
These live on `crabidy-server` (and `cbd`). They act on the server's
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
```
`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`
`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
item list (they survive filtering) so a marked-but-hidden row still
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
the whole queue — as a new folder of link files under
`/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
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;
`false` starts hidden). Either way it is only a display choice — the
server always computes and streams the bars while audio flows.
@ -124,7 +130,7 @@ pane).
| Global | `x` | Toggle repeat |
| Global | `Ctrl-n` | Next 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 | `g` / `G` | Select first / last item |
| Library | `Ctrl-d` | Jump 15 items down |
@ -132,6 +138,7 @@ pane).
| Library | `h` | Go to parent folder |
| Library | `l` | Enter selected folder |
| Library | `s` | Mark / unmark selection |
| Library | `v` / `V` | Visual mode: movement toggles marks |
| Library | `Enter` | Replace queue with selection |
| Library | `a` | Append selection to queue |
| 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
| File | Component | Auto-created |
| --------------------- | ---------- | ------------ |
| `tidaly.toml` | Tidal | yes |
| `ytdy.toml` | YouTube | yes |
| `fyyd.toml` | podcasts | yes |
| `fsdy.toml` | local fs | yes |
| `cbd-tui.toml` | `cbd-tui` | yes |
| `cbd.toml` | `cbd` | yes |
| `crabidy-server.toml` | server | yes |
| File | Component | Credentials needed |
| --------------------- | ---------------- | --------------------------- |
| `abs.toml` | audiobookshelf | server URL + API key |
| `fsdy.toml` | local files | none |
| `fyyd.toml` | podcasts | none |
| `jamendo.toml` | Jamendo | none (a key is shipped) |
| `soundcloud.toml` | SoundCloud | none (token optional) |
| `tidaly.toml` | Tidal | device login (interactive) |
| `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
media providers — Tidal, YouTube, fyyd podcasts, and a local music
folder (its filesystem root). See [Providers](./providers.md).
Every file is auto-created with defaults on first start.
- 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).
- `crabidy-server.toml` holds the enabled-providers list (below) and server
auth (see [Roles and authorization](./auth.md)).
- `crabidy-server.toml` holds the enabled-providers list (below), the audio
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
@ -38,7 +50,17 @@ On first start the server writes `crabidy-server.toml` with every provider
enabled:
```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
@ -123,3 +145,41 @@ de-duplication work.
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
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.
This book describes **how the current version works**: the pieces, the
boundaries between them, and the behaviour you can rely on. It is reference
documentation, not a design log — the "why we chose X over Y" discussions
live in the `architecture/` folder in the source tree.
boundaries between them, the behaviour you can rely on, and every
configuration option. It is reference documentation — it describes the system
as it is, not the history of how it got there.
```admonish note
Crabidy is a personal, single-server music player for a trusted home
@ -36,9 +36,9 @@ server: crabidy-server {
}
media: Media {
tidal: Tidal
youtube: YouTube
fs: Local files
streaming: "Tidal · YouTube\nSoundCloud · Jamendo"
spoken: "audiobookshelf · fyyd"
fs: "Local files\n+ the crabidy store"
}
clients -> server: gRPC (commands + update stream)
@ -59,12 +59,20 @@ segment is a **provider** mounted as a subtree:
```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
├── youtube YouTube search & playlists
├── fs a local music folder
└── crabidy your saves: queues, bookmarks, and captures
└── orphans store audio no save references any more
```
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
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
@ -93,5 +101,8 @@ mode.
- [The crabidy store](./store.md) — saves, captures, and de-duplication.
- [Queue and playback](./queue.md) — the queue, playback, and persistence.
- [Clients](./clients.md) — the TUI, the web client, and the CLI.
- [Configuration](./config.md) and
[Roles and authorization](./auth.md).
- [Configuration](./config.md) — every config file and option, including
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
}
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"
youtube: "/youtube — YouTube search & playlists"
fyyd: "/fyyd — podcast search"
fs: "/fs — a local music folder"
crabidy: "/crabidy — your saves & captures"
orphans: "/orphans — unreferenced store audio"
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 -> youtube: "route /youtube/*"
root -> fyyd: "route /fyyd/*"
root -> fs: "route /fs/*"
root -> crabidy: "route /crabidy/*"
root -> orphans: "route /orphans/*"
```
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
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
@ -37,43 +48,63 @@ can be queued alongside a track from another.
## Each provider is optional
A provider is only mounted when its client initializes successfully. The
orchestrator holds each one as an optional handle and only adds its child
to the root listing when the client exists.
A provider appears in the library only if it got that far, and three
independent things decide it:
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
Initialization failure is **non-fatal**. A broken config file, missing
credentials, or a missing helper binary drops that one subtree with a
warning — the server and every other provider keep running. The
orchestrator never fails to start because a single provider could not.
warning — the server and every other provider keep running. The only
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
configuration: `/tidal` needs a login, `/youtube` needs its client to
build, `/fs` needs a readable root directory, and `/crabidy` is always
present because the server owns it.
So the exact set of top-level entries depends on your build *and* your
configuration: `/tidal` needs a login, `/abs` needs
credentials, `/youtube` needs its client to build, `/fs` needs a readable
root directory, and `/crabidy` needs a writable state directory.
## Today's providers
- **[`/tidal`](./providers/tidal.md)** — streams from a Tidal account
via the `tidaldy` crate: playlists, favorite artists and their albums,
mixes, and search. Credentials live in `tidaly.toml`.
- **[`/youtube`](./providers/youtube.md)** — YouTube search and the
logged-in account's playlists via the `ytdy` crate. Metadata is
extracted in-process; config lives in `ytdy.toml`.
- **[`/fyyd`](./providers/fyyd.md)** — podcast search via the `fyyd`
crate and fyyd's keyless public API: find a podcast, drill into its
episodes, and play them. Config (all optional) lives in `fyyd.toml`.
- **[`/fs`](./providers/fs.md)** — serves a local music folder. Folders
become nodes and `*.cbd-track.toml` files become tracks. Config lives
in `fsdy.toml`.
- **`/crabidy`** — where the server writes the things you save (saved
queues, bookmarks, and downloaded captures), backed by a
content-addressed store. It has its own page: [The crabidy
store](./store.md).
- **`/orphans`** — store audio that no save references any more, surfaced
for manual reclamation (rename, delete, or queue). See [Reclaiming
orphans](./store.md#reclaiming-orphans--orphans).
Listed in the order the library root serves them.
- **`/crabidy`** — where the server writes everything you save: saved queues,
bookmarks, and downloaded captures, backed by a content-addressed audio
store. It has its own page: [The crabidy store](./store.md).
- **[`/abs`](./providers/abs.md)** — audiobooks from a self-hosted
audiobookshelf server. Needs a server URL and an API key (`abs.toml`).
- **[`/fs`](./providers/fs.md)** — a local music folder: directories become
nodes, `*.cbd-track.toml` files become tracks. No login (`fsdy.toml`).
- **[`/fyyd`](./providers/fyyd.md)** — podcast search over fyyd's public
API: find a podcast, drill into its episodes, play them. No account of any
kind (`fyyd.toml`).
- **[`/jamendo`](./providers/jamendo.md)** — Jamendo's catalogue of
Creative-Commons music, by search or album. Works out of the box on a
shipped app key; bring your own for your own rate limit (`jamendo.toml`).
- **[`/soundcloud`](./providers/soundcloud.md)** — SoundCloud search, link
resolving, and playback with no credentials at all; an optional token adds
your likes and playlists (`soundcloud.toml`).
- **[`/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
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.
`/tidal/artists/3634161/536243361`.
- `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 = 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
made and edited with normal file tools. (Other instances of the same
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
provider.
For the tree shape and design decisions, see
`architecture/fyyd-provider.md` in the source tree.
Everything the provider needs is public: fyyd requires no account, no key,
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
captures are resumable — re-capture the same name to continue.
For the full analysis behind the rustypipe/`yt-dlp` split, read
`ytdy/README.md` and `architecture/youtube-rustypipe.md` in the source
tree.
For the crate-level notes on the rustypipe/`yt-dlp` split, see
`ytdy/README.md`.

View File

@ -20,9 +20,8 @@ The provider only reads: it lists the tomls and resolves store links when a
captured track plays.
```admonish note
There is one `/crabidy` provider, not the older `/queues`, `/bookmarks`, and
`/captures` split. Bookmarks (`w`) and captures (`W`) are now two kinds of
save that coexist in the same tree, told apart by the captured marker.
Bookmarks (`w`) and captures (`W`) are two kinds of save in the same tree,
told apart by the captured marker — not two separate providers.
```
## The two roots
@ -252,9 +251,15 @@ apply:
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
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
`architecture/orphans.md` for the boundary and the (benign) capture-then-delete
race.
not mounted under `/fs` is not seen, so its target shows here as an orphan.
```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

View File

@ -1,8 +1,11 @@
# 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.
engine also powers the server-managed **`/crabidy`** mount (saved queues,
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
@ -11,8 +14,8 @@ 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).
order (the save writers use `0001`-style prefixes for exactly this
reason).
A track file carries the track's metadata plus a reference to the
playable thing:
@ -50,18 +53,33 @@ fails at play time), which keeps cycles impossible.
# root = "/home/me/music"
```
## The server-managed instances
## The server-managed `/crabidy` instance
`/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.
`/crabidy` is a second instance of this same provider, over the server's
state directory `~/.local/state/crabidy/`, written by the server (queue
persistence, `w`, `W`) and readable with any file manager — it is just
folders of the track files described above.
`/captures` additionally lets `d` delete *anything* in its tree — a
whole capture, a nested album folder, or a single track (which takes
its downloaded audio file with it). Because that removes data from
disk that was slow to download, the TUI asks for confirmation first
(`delete <name>? [y/N]`); deletes elsewhere stay unconfirmed.
- Each top-level folder is one save. It can be renamed (`e`) and deleted
(`d`) from a client.
- `current` is the continuously persisted play queue and is protected: you
cannot save over that name.
- 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
```
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. See `architecture/fyyd-provider.md` for the
tree shape and design decisions.
## Logging in
Nothing to do — fyyd's search API is public. There is no account, no key, and
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
Tidal's web API.
## How it works
## Logging in
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.
On first start the provider runs Tidal's OAuth **device login**:
1. Start the server. It prints a `link.tidal.com` verification URL to its
stdout — for `cbd` (which owns the terminal) look in the log file under
`~/.local/state/crabidy/` instead.
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
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`).
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`
@ -59,3 +73,5 @@ Notes:
login runs on the next start.
- If Tidal ever invalidates the session (long offline periods), delete
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
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.
player decodes; captures therefore store `.m4a` files.
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).
## 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`
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
`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