documentation
CI / check (push) Successful in 1m29s
CI / docker (push) Successful in 1m57s

This commit is contained in:
Connor Johnstone
2026-03-23 16:17:01 -04:00
parent c7110fbbe7
commit 53b3a644a1
12 changed files with 1518 additions and 157 deletions
+160
View File
@@ -0,0 +1,160 @@
# CLAUDE.md -- Shanty Architecture Reference
This document is the authoritative reference for any LLM working on the Shanty codebase. Read this before making changes.
## What Is Shanty?
Shanty is a self-hosted music management application ("better Lidarr"). It searches MusicBrainz for music metadata, downloads from YouTube via yt-dlp, tags and organizes files, and serves the library over the Subsonic protocol. It is a Cargo workspace where each component is both a standalone CLI tool and a library consumed by the web app.
## Design Philosophy
1. **Modular crates.** Each crate is a library and a CLI binary. The web app imports the library side; the CLI binary is for standalone use. Crates are git submodules hosted at `ssh://connor@git.rcjohnstone.com:2222/Shanty/{name}.git`. The exceptions are `shanty-config` and `shanty-data`, which are local workspace crates (not submodules).
2. **MBID-first matching.** All matching and deduplication in the web app uses MusicBrainz recording MBIDs, never string-based name matching. Name matching is only used in standalone CLI tools as a fallback.
3. **Provider-swappable data layer.** All external API calls go through trait-based providers in `shanty-data`. Metadata, artist images, bios, lyrics, and cover art each have a trait with multiple implementations. The active provider is selected via config.
4. **Track-level watchlist.** When a user watches an artist or album, it is expanded into individual track `wanted_item` records via MusicBrainz, each with a recording MBID. This enables per-track status tracking through the pipeline.
5. **Release groups, not releases.** The UI shows deduplicated release groups (album concepts), not individual releases (which have tons of reissues/regional editions). Filtered by secondary type -- default is studio only.
## Workspace Structure
All crates live in the workspace root `/home/connor/docs/projects/shanty/`.
| Crate | Purpose |
|-------|---------|
| `shanty` (root) | Top-level binary entry point, Actix server setup, graceful shutdown, background task spawning |
| `shanty-config` | Shared config types (AppConfig), YAML loading/saving, environment variable overrides |
| `shanty-data` | Unified external data providers: MusicBrainz (remote + local hybrid), Wikipedia, fanart.tv, Last.fm, LRCLIB, Cover Art Archive, MB dump import |
| `shanty-db` | Sea-ORM + SQLite schema, migrations, query modules for all tables |
| `shanty-index` | Scan directories, extract metadata from audio files via lofty |
| `shanty-tag` | MusicBrainz lookup, fuzzy matching, file tag writing |
| `shanty-org` | File organization with configurable format templates |
| `shanty-watch` | Watchlist management, MusicBrainz discography expansion (artist/album to tracks) |
| `shanty-dl` | yt-dlp download backend, rate limiting, download queue processing, ytmusicapi search |
| `shanty-search` | SearchProvider trait, MusicBrainz search + release group listing |
| `shanty-playlist` | Playlist generation strategies (similar-artist, genre, random, smart rules) |
| `shanty-web` | Actix backend routes, Yew (WASM) frontend, background task modules |
| `shanty-notify` | Notifications via Apprise/webhooks (stub -- not yet implemented) |
| `shanty-serve` | Subsonic streaming (stub -- functionality is in shanty-web) |
| `shanty-play` | Built-in web player (stub -- not yet implemented) |
The frontend is at `shanty-web/frontend/` and is excluded from the Cargo workspace. It builds separately with Trunk to `shanty-web/static/`.
## Key Architectural Patterns
### Data Providers (shanty-data)
`shanty-data` owns all external API calls. Key traits:
- `MetadataFetcher` -- artist info, release tracks, release resolution (MusicBrainz)
- `ArtistImageFetcher` -- artist photos (Wikipedia, fanart.tv)
- `ArtistBioFetcher` -- artist biographies (Wikipedia, Last.fm)
- `LyricsFetcher` -- song lyrics (LRCLIB)
- `CoverArtFetcher` -- album art (Cover Art Archive)
- `SimilarArtistFetcher` -- similar artist data (Last.fm)
**HybridMusicBrainzFetcher** wraps `LocalMusicBrainzFetcher` (optional) + `MusicBrainzFetcher` (remote API). It tries the local SQLite database first and falls back to the rate-limited remote API. The local DB is populated by importing MusicBrainz JSON dumps.
### Web Server (shanty-web + root binary)
The root `shanty` binary sets up the Actix server, creates shared `AppState`, and spawns background tasks. The `shanty-web` crate provides the route handlers and frontend.
**AppState** holds: database connection, MusicBrainz client (hybrid), search provider, Wikipedia fetcher, shared config (behind `Arc<RwLock>`), task manager, scheduler info, Firefox login session state.
### Background Tasks
Four background loops run via `tokio::spawn` + sleep:
1. **cookie_refresh** -- refreshes YouTube cookies via headless Firefox (every 6 hours, configurable)
2. **pipeline_scheduler** -- runs the full download pipeline automatically (every 3 hours, configurable)
3. **monitor** -- checks monitored artists for new releases (every 12 hours, configurable)
4. **mb_update** -- re-imports MusicBrainz dumps if auto_update is enabled (weekly)
One-off tasks (index, tag, organize, download process, monitor check, MB import) are spawned on demand and tracked via `TaskManager`.
### Database
Sea-ORM with SQLite. Migrations run automatically on startup. Key tables:
- `artists` -- name (unique), musicbrainz_id (unique), monitored flag, top_songs/similar_artists (JSON)
- `albums` -- name, album_artist, year, genre, musicbrainz_id, artist_id FK
- `tracks` -- file_path (unique), all metadata fields, musicbrainz_id, artist_id/album_id FKs
- `wanted_items` -- item_type, name, musicbrainz_id, artist_id, status (Wanted/Available/Downloaded/Owned), user_id
- `download_queue` -- query, wanted_item_id FK, status, retry_count
- `search_cache` -- query_key (unique), provider, result_json, expires_at (used for MB data, lyrics, artist enrichment)
- `users` -- username, password_hash (bcrypt), role (Admin/User), subsonic_password (plaintext per Subsonic protocol)
- `playlists` / `playlist_tracks` -- saved playlists with ordered track references
### Subsonic API
Mounted at `/rest/*` with separate authentication (username + MD5 token, per the Subsonic protocol spec). Supports browsing, streaming, playlists, search, cover art, and scrobbling. Opus files are auto-transcoded to MP3 via ffmpeg for client compatibility.
### Frontend
Yew 0.21 with client-side rendering (CSR, no SSR). Built with Trunk to WASM. The compiled output goes to `shanty-web/static/` and is served by Actix with SPA fallback (all non-API routes serve `index.html`).
## Data Flow (Pipeline)
The full automated pipeline, triggered by "Set Sail" in the UI or by the pipeline scheduler:
1. **Search** -- find artist/album on MusicBrainz
2. **Watch** -- add to watchlist (expands to individual track wanted_items with recording MBIDs)
3. **Sync** -- `shanty_dl::sync_wanted_to_queue` creates download_queue entries for wanted items
4. **Download** -- yt-dlp downloads via YouTube Music search (ytmusicapi Python script), creates track records in DB with MBIDs from wanted_items
5. **Index** -- scan library, extract metadata from new files
6. **Tag** -- MusicBrainz lookup by MBID (skips search since MBID is known), write tags to files
7. **Organize** -- move files to `{artist}/{album}/{track_number} - {title}.{ext}` in the library
8. **Promote** -- all Downloaded wanted_items are marked as Owned
## Configuration
YAML config file at `~/.config/shanty/config.yaml` (or `SHANTY_CONFIG` env var). Environment variables override YAML values. In Docker, the config file is at `/config/config.yaml`.
Key environment variables:
- `SHANTY_DATA_DIR` -- base directory for all application data (Docker: `/data`)
- `SHANTY_DATABASE_URL` -- SQLite connection string
- `SHANTY_LIBRARY_PATH` -- music library root
- `SHANTY_CONFIG` -- path to config YAML
- `SHANTY_WEB_PORT` / `SHANTY_WEB_BIND` -- server binding
- `SHANTY_LASTFM_API_KEY` -- Last.fm API key (for bios and similar-artist playlists)
- `SHANTY_FANART_API_KEY` -- fanart.tv API key (for artist images/banners)
The config is loaded once at startup and held in `Arc<RwLock<AppConfig>>`. It can be updated at runtime via the `/api/config` PUT endpoint, which writes back to the YAML file and updates the in-memory config.
## Coding Standards
- **Rust edition 2024** with resolver v3
- `cargo clippy -- -D warnings` must pass
- `cargo fmt` for formatting
- All crates must compile independently
- Never use `#[allow(dead_code)]` -- remove dead code instead
- Never create local DB records for artists the user is just browsing (only persist when they watch)
- Use MBIDs for all matching in the web app, never name-based matching
- Artist credits: use the primary (first) artist only, never concatenate collaborators
## Testing
```sh
cargo test --workspace
```
Integration tests per crate. Mock providers exist for MusicBrainz in tests. The frontend is excluded from workspace tests (it has its own build process via Trunk).
## Important Constraints
- **MusicBrainz rate limit:** 1 request per 1.1 seconds for the remote API. Mitigated by the local SQLite database (imported from MB dumps) and aggressive caching in `search_cache`.
- **YouTube cookies expire** roughly every 2 weeks. Auto-refreshed by headless Firefox every 6 hours when cookie_refresh is enabled.
- **Session key is random on startup** -- user sessions do not survive server restarts.
- **Subsonic password is stored in plaintext** per the Subsonic protocol specification. Users are warned about this in the UI.
- **Opus transcoding** for Subsonic clients transcodes the entire file to memory before streaming. Not ideal for very large files.
## Making Changes
- Backend route changes: edit files in `shanty-web/src/routes/`
- Frontend changes: edit files in `shanty-web/frontend/src/`, then `cd shanty-web/frontend && trunk build`
- Config changes: update `shanty-config/src/lib.rs` (add field + default), update `apply_env_overrides` if adding env var support
- Database schema changes: add a migration in `shanty-db`, update entities and queries
- Adding a new external data source: add a provider implementation in `shanty-data` behind the appropriate trait
- Each crate submodule must be committed and pushed independently before updating the parent workspace
+258
View File
@@ -0,0 +1,258 @@
# API Reference
Shanty exposes a REST API at `/api/*` and a Subsonic-compatible API at `/rest/*`. All REST endpoints require authentication via session cookie unless noted otherwise.
## Authentication
All `/api/*` endpoints (except the auth endpoints listed below) require an active session. To authenticate, call the login endpoint and include the returned session cookie in subsequent requests.
### Endpoints
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/auth/setup-required` | No | Check if initial setup is needed (returns `{"required": true}` if no users exist) |
| POST | `/api/auth/setup` | No | Create the first admin user. Only works when no users exist. Body: `{"username": "...", "password": "..."}` |
| POST | `/api/auth/login` | No | Log in. Body: `{"username": "...", "password": "..."}`. Returns user info and sets session cookie. |
| POST | `/api/auth/logout` | Yes | Log out and clear session. |
| GET | `/api/auth/me` | Yes | Get current user info (id, username, role). |
| GET | `/api/auth/users` | Admin | List all users. |
| POST | `/api/auth/users` | Admin | Create a new user. Body: `{"username": "...", "password": "..."}` |
| DELETE | `/api/auth/users/{id}` | Admin | Delete a user. Cannot delete yourself. |
| PUT | `/api/auth/subsonic-password` | Yes | Set Subsonic password for current user. Body: `{"password": "..."}` |
| GET | `/api/auth/subsonic-password-status` | Yes | Check if current user has a Subsonic password set. |
---
## Artists
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/artists` | Yes | List all artists in the library. Supports `?limit=50&offset=0` pagination. Returns artist names, MBIDs, monitored status, and track counts (watched/owned/total). |
| POST | `/api/artists` | Yes | Add (watch) an artist. Body: `{"name": "...", "mbid": "..."}`. At least one of name or mbid is required. Expands the artist's full discography into individual track wanted_items. |
| GET | `/api/artists/{id}` | Yes | Get artist by local database ID. Returns artist info and local albums. |
| GET | `/api/artists/{id}/full` | Yes | Get full artist detail with MusicBrainz enrichment. Accepts local ID or MBID. Returns discography with per-album status (owned/partial/wanted/unwatched), artist photo, bio, and banner. Supports `?quick=true` to skip per-album track fetches. |
| DELETE | `/api/artists/{id}` | Admin | Delete an artist from the library. |
| POST | `/api/artists/{id}/monitor` | Yes | Enable monitoring for an artist. |
| DELETE | `/api/artists/{id}/monitor` | Yes | Disable monitoring for an artist. |
---
## Albums
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/albums` | Yes | List all local albums. Supports `?limit=50&offset=0`. |
| POST | `/api/albums` | Yes | Add (watch) an album. Body: `{"artist": "...", "album": "...", "mbid": "..."}`. Accepts release or release-group MBID. Expands to individual track wanted_items. |
| GET | `/api/albums/{mbid}` | Yes | Get album track listing by MBID (release or release-group). Returns tracks with per-track status. |
---
## Tracks
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/tracks` | Yes | List tracks. Supports `?limit=50&offset=0` and `?q=search` for text search. |
| GET | `/api/tracks/{id}` | Yes | Get a single track by local database ID. |
---
## Search
Search queries MusicBrainz (either the local database or the remote API).
| Method | Path | Auth | Parameters | Description |
|--------|------|------|------------|-------------|
| GET | `/api/search/artist` | Yes | `q` (required), `limit` (default 25) | Search for artists by name. Returns MBID, name, disambiguation. |
| GET | `/api/search/album` | Yes | `q` (required), `artist` (optional), `limit` (default 25) | Search for albums. |
| GET | `/api/search/track` | Yes | `q` (required), `artist` (optional), `limit` (default 25) | Search for tracks. |
| GET | `/api/search/discography/{mbid}` | Yes | (none) | Get an artist's full discography (release groups) by MBID. |
---
## Downloads
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/downloads/queue` | Yes | List download queue. Optional `?status=pending\|downloading\|completed\|failed\|cancelled`. |
| POST | `/api/downloads` | Yes | Enqueue a manual download. Body: `{"query": "..."}`. |
| POST | `/api/downloads/sync` | Yes | Sync wanted items to download queue. Returns count of found/enqueued/skipped. |
| POST | `/api/downloads/process` | Yes | Start processing the download queue (background task). Returns a task_id. |
| POST | `/api/downloads/retry/{id}` | Yes | Requeue a failed download. |
| DELETE | `/api/downloads/{id}` | Yes | Cancel a download. |
---
## Playlists
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/playlists/generate` | Yes | Generate a playlist (preview, not saved). Body includes strategy, seed_artists, count, etc. |
| GET | `/api/playlists` | Yes | List saved playlists for the current user. |
| POST | `/api/playlists` | Yes | Save a playlist. Body: `{"name": "...", "description": "...", "track_ids": [1, 2, 3]}` |
| GET | `/api/playlists/{id}` | Yes | Get a playlist with its tracks. |
| PUT | `/api/playlists/{id}` | Yes | Update playlist name/description. Body: `{"name": "...", "description": "..."}` |
| DELETE | `/api/playlists/{id}` | Yes | Delete a playlist. |
| GET | `/api/playlists/{id}/m3u` | Yes | Export playlist as M3U file download. |
| POST | `/api/playlists/{id}/tracks` | Yes | Add a track. Body: `{"track_id": 123}` |
| PUT | `/api/playlists/{id}/tracks` | Yes | Reorder tracks. Body: `{"track_ids": [3, 1, 2]}` |
| DELETE | `/api/playlists/{id}/tracks/{track_id}` | Yes | Remove a track from the playlist. |
### Playlist Generation Body
```json
{
"strategy": "similar",
"seed_artists": ["Pink Floyd", "Radiohead"],
"count": 50,
"popularity_bias": 0.5,
"ordering": "interleave",
"genres": [],
"rules": []
}
```
Strategies: `similar`, `genre`, `random`, `smart`.
Ordering options: `interleave`, `score`, `random`.
---
## Lyrics
| Method | Path | Auth | Parameters | Description |
|--------|------|------|------------|-------------|
| GET | `/api/lyrics` | Yes | `artist` (required), `title` (required) | Fetch lyrics from LRCLIB. Returns plain lyrics and time-synced lyrics if available. Cached for 30 days. |
---
## System and Pipeline
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/status` | Yes | Dashboard status: library summary, download queue, tagging queue, active tasks, scheduler info. |
| POST | `/api/pipeline` | Yes | Trigger the full pipeline (sync + download + tag + organize). Returns task_ids. |
| POST | `/api/index` | Yes | Trigger a library scan (background task). |
| POST | `/api/tag` | Yes | Trigger tagging of untagged tracks (background task). |
| POST | `/api/organize` | Yes | Trigger file organization (background task). |
| GET | `/api/tasks/{id}` | Yes | Get task status (running/completed/failed, progress, message). |
---
## Watchlist
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/watchlist` | Yes | List wanted items for the current user. |
| DELETE | `/api/watchlist/{id}` | Yes | Remove a wanted item. |
---
## YouTube Authentication
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/ytauth/status` | Yes | Get YouTube auth status: authenticated, cookie age, refresh status, yt-dlp version, API key status. |
| POST | `/api/ytauth/login-start` | Admin | Launch Firefox + noVNC for interactive YouTube login. Returns VNC URL. |
| POST | `/api/ytauth/login-stop` | Admin | Stop Firefox, export cookies, enable auto-refresh. |
| POST | `/api/ytauth/refresh` | Admin | Trigger immediate headless cookie refresh. |
| DELETE | `/api/ytauth/cookies` | Admin | Clear all cookies and Firefox profile, disable refresh. |
---
## Monitor
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/monitor/check` | Admin | Trigger an immediate check of all monitored artists for new releases. |
| GET | `/api/monitor/status` | Yes | List all monitored artists with last check timestamps. |
---
## Scheduler
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| POST | `/api/scheduler/skip-pipeline` | Admin | Skip the next scheduled pipeline run. |
| POST | `/api/scheduler/skip-monitor` | Admin | Skip the next scheduled monitor check. |
---
## MusicBrainz
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/mb-status` | Yes | Check if local MusicBrainz database is available and get stats (artist/release/recording counts). |
| POST | `/api/mb-import` | Admin | Trigger MusicBrainz database import (background task). Downloads dumps and imports. |
---
## Config
| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/config` | Yes | Get current configuration. |
| PUT | `/api/config` | Admin | Update configuration. Body is the full AppConfig JSON object. Writes to YAML file and updates in-memory config. |
---
## Subsonic API
The Subsonic API is served at `/rest/*` and uses the Subsonic authentication protocol (username + MD5 token or plaintext password, passed as query parameters).
All Subsonic endpoints accept the standard Subsonic query parameters: `u` (username), `p` (password or `enc:` hex-encoded), `t` (MD5 token), `s` (salt), `v` (API version), `c` (client name), `f` (response format, defaults to XML).
Each endpoint is available at both `/rest/endpoint` and `/rest/endpoint.view`.
### System
| Path | Description |
|------|-------------|
| `/rest/ping` | Server health check. |
| `/rest/getLicense` | Returns license status (always valid). |
### Browsing
| Path | Description |
|------|-------------|
| `/rest/getMusicFolders` | List music library folders. |
| `/rest/getIndexes` | Get alphabetical artist index. |
| `/rest/getMusicDirectory` | Get contents of a directory (artist's albums or album's tracks). Parameter: `id`. |
| `/rest/getArtists` | Get all artists (ID3 tag based). |
| `/rest/getArtist` | Get an artist with albums. Parameter: `id`. |
| `/rest/getAlbum` | Get an album with tracks. Parameter: `id`. |
| `/rest/getSong` | Get a single track. Parameter: `id`. |
| `/rest/getGenres` | List all genres. |
### Search
| Path | Description |
|------|-------------|
| `/rest/search3` | Search artists, albums, and tracks. Parameters: `query`, `artistCount`, `albumCount`, `songCount`, `artistOffset`, `albumOffset`, `songOffset`. |
### Media
| Path | Description |
|------|-------------|
| `/rest/stream` | Stream an audio file. Parameter: `id`. Transcodes Opus to MP3 if transcoding is enabled. |
| `/rest/download` | Download an audio file (original format). Parameter: `id`. |
| `/rest/getCoverArt` | Get cover art image. Parameter: `id`. |
### Playlists
| Path | Description |
|------|-------------|
| `/rest/getPlaylists` | List all playlists. |
| `/rest/getPlaylist` | Get a playlist with tracks. Parameter: `id`. |
| `/rest/createPlaylist` | Create or update a playlist. Parameters: `name`, `songId` (repeatable). |
| `/rest/deletePlaylist` | Delete a playlist. Parameter: `id`. |
### Annotation
| Path | Description |
|------|-------------|
| `/rest/scrobble` | Record a play. Parameters: `id`, `submission` (true/false). |
### User
| Path | Description |
|------|-------------|
| `/rest/getUser` | Get user details. Parameter: `username`. |
+461
View File
@@ -0,0 +1,461 @@
# Configuration Reference
Shanty is configured via a YAML file and environment variables. Environment variables always override values from the YAML file.
## Config File Location
By default, Shanty looks for its config file at:
- **Linux:** `~/.config/shanty/config.yaml`
- **Docker:** `/config/config.yaml`
- **Custom:** Set the `SHANTY_CONFIG` environment variable to any path.
If no config file exists, Shanty uses sensible defaults for everything. You can also configure most settings from the web UI under **Settings**, which writes changes back to the YAML file.
## Environment Variables
These environment variables override their corresponding YAML settings. In Docker, set them in the `environment` section of your `docker-compose.yml`.
| Variable | Default | Description |
|----------|---------|-------------|
| `SHANTY_CONFIG` | `~/.config/shanty/config.yaml` | Path to the YAML config file |
| `SHANTY_DATA_DIR` | `~/.local/share/shanty` | Base directory for application data (database, downloads, Firefox profile). In Docker, this is `/data`. |
| `SHANTY_DATABASE_URL` | `sqlite://~/.local/share/shanty/shanty.db?mode=rwc` | Full SQLite connection string |
| `SHANTY_LIBRARY_PATH` | System music directory (`~/Music`) | Path to your music library |
| `SHANTY_WEB_PORT` | `8085` | HTTP server port |
| `SHANTY_WEB_BIND` | `0.0.0.0` | HTTP server bind address |
| `SHANTY_LASTFM_API_KEY` | (none) | Last.fm API key for artist bios and similar-artist playlists. Get one at https://www.last.fm/api/account/create |
| `SHANTY_FANART_API_KEY` | (none) | fanart.tv API key for artist images and banners. Get one at https://fanart.tv/get-an-api-key/ |
## Full Config Reference
Below is every configuration option grouped by section.
---
### Root Options
These are top-level keys in the YAML file.
#### `library_path`
- **Type:** string (file path)
- **Default:** System music directory (e.g., `~/Music`)
- **Env override:** `SHANTY_LIBRARY_PATH`
- **Description:** The root directory of your music library. Organized files are placed here according to the `organization_format` template.
- **Example:** `library_path: /home/user/Music`
#### `database_url`
- **Type:** string
- **Default:** `sqlite://~/.local/share/shanty/shanty.db?mode=rwc`
- **Env override:** `SHANTY_DATABASE_URL`
- **Description:** SQLite database connection string. The `?mode=rwc` suffix means read-write-create (the database file is created automatically if it does not exist).
- **Example:** `database_url: sqlite:///data/shanty.db?mode=rwc`
#### `download_path`
- **Type:** string (file path)
- **Default:** `~/.local/share/shanty/downloads`
- **Description:** Temporary directory where yt-dlp stores downloaded files before they are tagged and organized into the library.
- **Example:** `download_path: /tmp/shanty-downloads`
#### `organization_format`
- **Type:** string (template)
- **Default:** `{artist}/{album}/{track_number} - {title}.{ext}`
- **Description:** Template for organizing files in the library. Available placeholders: `{artist}`, `{album}`, `{title}`, `{track_number}`, `{ext}`, `{year}`, `{genre}`.
- **Example:** `organization_format: "{artist}/{album} ({year})/{track_number} - {title}.{ext}"`
#### `allowed_secondary_types`
- **Type:** list of strings
- **Default:** `[]` (empty, meaning studio albums only)
- **Description:** Which secondary release group types to include when displaying an artist's discography. By default, only pure studio albums (with no secondary type) are shown. Add types to this list to also see compilations, live albums, etc.
- **Options:** `Compilation`, `Live`, `Soundtrack`, `Remix`, `DJ-mix`, `Demo`
- **Example:**
```yaml
allowed_secondary_types:
- Compilation
- Live
```
#### `log_level`
- **Type:** string
- **Default:** `info`
- **Description:** Log verbosity level. The `-v` CLI flag overrides this at runtime.
- **Options:** `error`, `warn`, `info`, `debug`, `trace`
- **Example:** `log_level: debug`
---
### `web` Section
Controls the HTTP server.
#### `web.port`
- **Type:** integer
- **Default:** `8085`
- **Env override:** `SHANTY_WEB_PORT`
- **Description:** Port the web server listens on.
- **Example:**
```yaml
web:
port: 9090
```
#### `web.bind`
- **Type:** string
- **Default:** `0.0.0.0`
- **Env override:** `SHANTY_WEB_BIND`
- **Description:** Address the web server binds to. Use `0.0.0.0` to accept connections from any interface, or `127.0.0.1` to restrict to localhost.
- **Example:**
```yaml
web:
bind: 127.0.0.1
```
---
### `tagging` Section
Controls automatic file tagging behavior.
#### `tagging.auto_tag`
- **Type:** boolean
- **Default:** `false`
- **Description:** Whether to automatically tag files during indexing. When false, tagging must be triggered manually or as part of the pipeline.
- **Example:**
```yaml
tagging:
auto_tag: true
```
#### `tagging.write_tags`
- **Type:** boolean
- **Default:** `true`
- **Description:** Whether to actually write tags to audio files. When false, the tagger runs in dry-run mode (useful for testing).
- **Example:**
```yaml
tagging:
write_tags: false
```
#### `tagging.confidence`
- **Type:** float (0.0 to 1.0)
- **Default:** `0.8`
- **Description:** Minimum confidence threshold for fuzzy matching when the tagger tries to identify a track. Higher values mean stricter matching. Tracks below this threshold are skipped.
- **Example:**
```yaml
tagging:
confidence: 0.9
```
---
### `download` Section
Controls the download backend (yt-dlp).
#### `download.format`
- **Type:** string
- **Default:** `opus`
- **Description:** Audio format for downloaded files.
- **Options:** `opus`, `mp3`, `m4a`, `flac`
- **Example:**
```yaml
download:
format: mp3
```
#### `download.search_source`
- **Type:** string
- **Default:** `ytmusic`
- **Description:** Search backend for finding tracks to download.
- **Options:** `ytmusic` (YouTube Music via ytmusicapi)
- **Example:**
```yaml
download:
search_source: ytmusic
```
#### `download.cookies_path`
- **Type:** string (file path) or null
- **Default:** null (no cookies)
- **Description:** Path to a Netscape-format cookies file for YouTube authentication. Usually managed automatically by the YouTube auth feature. When set, the authenticated rate limit is used.
- **Example:**
```yaml
download:
cookies_path: /data/cookies.txt
```
#### `download.rate_limit`
- **Type:** integer
- **Default:** `250`
- **Description:** Maximum download requests per hour when not authenticated. YouTube's actual limit is roughly 300; the default includes a safety margin.
- **Example:**
```yaml
download:
rate_limit: 200
```
#### `download.rate_limit_auth`
- **Type:** integer
- **Default:** `1800`
- **Description:** Maximum download requests per hour when authenticated with cookies. YouTube's actual limit is roughly 2000; the default includes a safety margin.
- **Example:**
```yaml
download:
rate_limit_auth: 1500
```
#### `download.cookie_refresh_enabled`
- **Type:** boolean
- **Default:** `false`
- **Description:** Whether to automatically refresh YouTube cookies using headless Firefox. Enabled automatically when you log in via the YouTube Authentication feature in Settings.
- **Example:**
```yaml
download:
cookie_refresh_enabled: true
```
#### `download.cookie_refresh_hours`
- **Type:** integer
- **Default:** `6`
- **Description:** How often (in hours) to refresh YouTube cookies. YouTube cookies typically expire after about 2 weeks, so refreshing every 6 hours keeps them fresh.
- **Example:**
```yaml
download:
cookie_refresh_hours: 12
```
#### `download.vnc_port`
- **Type:** integer
- **Default:** `6080`
- **Description:** Port for the noVNC web interface used during interactive YouTube login. This port must be accessible from your browser.
- **Example:**
```yaml
download:
vnc_port: 6081
```
---
### `indexing` Section
Controls the library scanner.
#### `indexing.concurrency`
- **Type:** integer
- **Default:** `4`
- **Description:** Number of files to process concurrently during a library scan. Higher values use more CPU and memory but scan faster.
- **Example:**
```yaml
indexing:
concurrency: 8
```
---
### `metadata` Section
Controls where artist metadata, images, lyrics, and cover art come from.
#### `metadata.metadata_source`
- **Type:** string
- **Default:** `musicbrainz`
- **Description:** Source for structured metadata (artist info, release data, track listings).
- **Options:** `musicbrainz`
- **Example:**
```yaml
metadata:
metadata_source: musicbrainz
```
#### `metadata.artist_image_source`
- **Type:** string
- **Default:** `wikipedia`
- **Description:** Source for artist photos. Wikipedia works without an API key. fanart.tv provides higher quality images and background banners but requires `SHANTY_FANART_API_KEY`.
- **Options:** `wikipedia`, `fanarttv`
- **Example:**
```yaml
metadata:
artist_image_source: fanarttv
```
#### `metadata.artist_bio_source`
- **Type:** string
- **Default:** `wikipedia`
- **Description:** Source for artist biographies. Wikipedia works without an API key. Last.fm provides more music-focused bios but requires `SHANTY_LASTFM_API_KEY`.
- **Options:** `wikipedia`, `lastfm`
- **Example:**
```yaml
metadata:
artist_bio_source: lastfm
```
#### `metadata.lyrics_source`
- **Type:** string
- **Default:** `lrclib`
- **Description:** Source for song lyrics. LRCLIB provides both plain and time-synced lyrics.
- **Options:** `lrclib`
- **Example:**
```yaml
metadata:
lyrics_source: lrclib
```
#### `metadata.cover_art_source`
- **Type:** string
- **Default:** `coverartarchive`
- **Description:** Source for album cover art.
- **Options:** `coverartarchive`
- **Example:**
```yaml
metadata:
cover_art_source: coverartarchive
```
---
### `scheduling` Section
Controls automatic background tasks.
#### `scheduling.pipeline_enabled`
- **Type:** boolean
- **Default:** `true`
- **Description:** Whether the download pipeline (sync, download, tag, organize) runs automatically on a schedule.
- **Example:**
```yaml
scheduling:
pipeline_enabled: false
```
#### `scheduling.pipeline_interval_hours`
- **Type:** integer
- **Default:** `3`
- **Description:** Hours between automatic pipeline runs. The timer starts after the previous run completes.
- **Example:**
```yaml
scheduling:
pipeline_interval_hours: 6
```
#### `scheduling.monitor_enabled`
- **Type:** boolean
- **Default:** `true`
- **Description:** Whether the monitor automatically checks for new releases from monitored artists.
- **Example:**
```yaml
scheduling:
monitor_enabled: false
```
#### `scheduling.monitor_interval_hours`
- **Type:** integer
- **Default:** `12`
- **Description:** Hours between automatic monitor checks.
- **Example:**
```yaml
scheduling:
monitor_interval_hours: 24
```
---
### `subsonic` Section
Controls the Subsonic-compatible streaming API.
#### `subsonic.enabled`
- **Type:** boolean
- **Default:** `true`
- **Description:** Whether the Subsonic API is active. The API is served at `/rest/*` on the same port as the web UI.
- **Example:**
```yaml
subsonic:
enabled: false
```
#### `subsonic.transcoding_enabled`
- **Type:** boolean
- **Default:** `true`
- **Description:** Whether to transcode audio files (e.g., Opus to MP3) when streaming via the Subsonic API. Requires ffmpeg. Disable if your clients natively support your download format.
- **Example:**
```yaml
subsonic:
transcoding_enabled: false
```
---
### `musicbrainz` Section
Controls the local MusicBrainz database for faster lookups.
#### `musicbrainz.local_db_path`
- **Type:** string (file path) or null
- **Default:** null (auto-detected at `{data_dir}/shanty-mb.db`)
- **Description:** Path to the local MusicBrainz SQLite database. If not set and the file exists at the default location, it is used automatically. If not set and the file does not exist, only the remote MusicBrainz API is used.
- **Example:**
```yaml
musicbrainz:
local_db_path: /data/shanty-mb.db
```
#### `musicbrainz.auto_update`
- **Type:** boolean
- **Default:** `false`
- **Description:** Whether to automatically re-download and re-import MusicBrainz dumps on a weekly schedule.
- **Example:**
```yaml
musicbrainz:
auto_update: true
```
---
## Full Example Config
```yaml
library_path: /music
download_path: /data/downloads
organization_format: "{artist}/{album}/{track_number} - {title}.{ext}"
log_level: info
allowed_secondary_types:
- Compilation
web:
port: 8085
bind: 0.0.0.0
tagging:
write_tags: true
confidence: 0.8
download:
format: opus
search_source: ytmusic
rate_limit: 250
rate_limit_auth: 1800
indexing:
concurrency: 4
metadata:
metadata_source: musicbrainz
artist_image_source: wikipedia
artist_bio_source: wikipedia
lyrics_source: lrclib
cover_art_source: coverartarchive
scheduling:
pipeline_enabled: true
pipeline_interval_hours: 3
monitor_enabled: true
monitor_interval_hours: 12
subsonic:
enabled: true
transcoding_enabled: true
musicbrainz:
auto_update: false
```
+82
View File
@@ -0,0 +1,82 @@
# Artist Monitoring
Monitoring lets Shanty automatically detect when your favorite artists release new music. When a new album or single is found, it is added to your watchlist and queued for download.
## How monitoring differs from watching
- **Watching** an artist means you have added some or all of their existing discography to your watchlist. Watched tracks go through the download pipeline.
- **Monitoring** an artist means Shanty periodically checks MusicBrainz for new releases that were not in the artist's discography when you first added them. When something new appears, it is automatically added to your watchlist.
You can watch an artist without monitoring them (just download their existing catalog), and you can monitor a watched artist to stay up to date with new releases.
## How to set up
1. Open the Shanty web UI and navigate to an artist's page (search for them or open them from your library).
2. Click the **Monitor** button on the artist page.
3. The artist is now monitored. A badge indicates their monitoring status.
To stop monitoring an artist, click the **Monitor** button again to toggle it off.
## How it works
When monitoring is enabled, Shanty runs a background check on a schedule:
1. For each monitored artist, it fetches the current list of release groups from MusicBrainz.
2. It compares this against the release groups already in the watchlist.
3. Any new release groups (albums, singles, EPs) are expanded into individual track wanted_items and added to the watchlist.
4. The artist's `last_checked_at` timestamp is updated.
If you have the pipeline scheduler enabled (the default), new releases are automatically downloaded on the next pipeline run.
## Automatic scheduling
Both the monitor check and the download pipeline run on configurable schedules:
| Task | Default interval | Config key |
|------|-----------------|------------|
| Monitor check | Every 12 hours | `scheduling.monitor_interval_hours` |
| Pipeline (Set Sail) | Every 3 hours | `scheduling.pipeline_interval_hours` |
This means that in the default configuration:
1. The monitor checks for new releases every 12 hours.
2. If new releases are found, they are added to the watchlist.
3. Within 3 hours, the pipeline runs automatically, downloading and processing the new tracks.
## Configuring schedules
In your config file:
```yaml
scheduling:
pipeline_enabled: true
pipeline_interval_hours: 3
monitor_enabled: true
monitor_interval_hours: 12
```
Or adjust these settings in the web UI under **Settings**.
To disable automatic scheduling entirely:
```yaml
scheduling:
pipeline_enabled: false
monitor_enabled: false
```
When disabled, you can still trigger checks manually from the Dashboard.
## Manual checks
You can trigger a monitor check at any time from the Dashboard by clicking **Check Monitored** (under the system actions). The results show how many artists were checked, how many new releases were found, and how many tracks were added.
Similarly, you can trigger the pipeline manually by clicking **Set Sail** at any time.
## Viewing monitored artists
The monitoring status is visible on each artist's page. You can also check the current monitor status via the API at `GET /api/monitor/status`, which returns a list of all monitored artists with their last check timestamps.
## Skipping scheduled runs
If you want to skip the next scheduled pipeline or monitor run without disabling them permanently, you can do so from the Dashboard. The skip is a one-time action -- the schedule resumes after the skipped run.
+80
View File
@@ -0,0 +1,80 @@
# MusicBrainz Local Database
MusicBrainz is the source of all music metadata in Shanty: artist information, album listings, track listings, and release data. By default, Shanty queries the MusicBrainz API over the internet, which is rate-limited to 1 request every 1.1 seconds.
Importing the MusicBrainz database locally eliminates this bottleneck for most lookups.
## What this does
The import downloads a full copy of the MusicBrainz database (as JSON dumps) and loads it into a local SQLite database on your machine. When Shanty needs to look up an artist's discography, release track listings, or recording details, it checks the local database first. If the data is found locally, the response is instant. If not (for example, very new releases), it falls back to the remote API.
## Why you might want this
Without the local database, browsing a new artist's full discography requires many API calls (one per release group, one per release to get track listings). With the rate limit, loading an artist with 15 albums can take 30-60 seconds.
With the local database, the same operation completes in under a second.
If you are casually using Shanty for a few artists, the caching system works well enough without the local database. But if you frequently browse and add new artists, the local database makes the experience dramatically faster.
## Requirements
- **Download size:** approximately 24 GB (compressed JSON dumps from MusicBrainz)
- **Database size:** approximately 16 GB (the resulting SQLite database)
- **Disk type:** SSD strongly recommended. The import writes millions of rows, which is very slow on spinning disks.
- **Time:** 12-24 hours for the initial import, depending on your hardware and disk speed.
- **Disk space total:** You need roughly 40 GB free during import (dumps + database). After import, you can delete the dump files to reclaim the 24 GB.
## How to set up via the web UI
1. Open the Shanty web UI and go to **Settings**.
2. Scroll to the **MusicBrainz Database** section.
3. Click **Import**.
4. Shanty downloads the dump files and imports them. Progress is shown on the Dashboard.
You can continue using Shanty while the import runs. It will use the remote API until the import completes.
## How to set up via the CLI
If you prefer the command line (or want to run the import on a more powerful machine):
```sh
# Inside Docker
docker compose exec shanty ./shanty mb-import --download
# From source
cargo run --release --bin shanty -- mb-import --download
```
Options:
- `--download` -- Download fresh dump files from metabrainz.org before importing. Without this flag, the import looks for existing dump files in the data directory.
- `--data-dir /path/to/dumps` -- Custom directory for dump files. Defaults to `{data_dir}/mb-dumps`.
The database is created at `{data_dir}/shanty-mb.db` by default. You can override this with the `musicbrainz.local_db_path` config option.
## Automatic weekly updates
MusicBrainz publishes new database dumps regularly. You can configure Shanty to automatically re-download and re-import them:
```yaml
musicbrainz:
auto_update: true
```
When enabled, Shanty checks weekly for new dumps and runs the import in the background if a newer dump is available. The existing local database continues to serve queries during the update.
## The hybrid approach
Shanty uses what it calls a "hybrid fetcher":
1. **Local database** is checked first for any MusicBrainz lookup (artist info, release groups, track listings, etc.).
2. **Remote API** is used as a fallback when data is not in the local database (new releases added after the last import, for example).
3. **Cache** is used for data that was fetched from the remote API, so repeated lookups do not hit the rate limit.
This means you get the best of both worlds: instant lookups for the vast majority of music that exists in the database dumps, and up-to-date data for newly released music via the API.
## Configuration options
| Option | Default | Description |
|--------|---------|-------------|
| `musicbrainz.local_db_path` | `{data_dir}/shanty-mb.db` | Path to the local SQLite database |
| `musicbrainz.auto_update` | `false` | Automatically re-import dumps weekly |
+92
View File
@@ -0,0 +1,92 @@
# Playlists
Shanty can generate playlists from your library using similar-artist data from Last.fm. You can also create and edit playlists manually, export them as M3U files, and access them from Subsonic clients.
## Requirements
Playlist generation using the **similar-artist** strategy requires a Last.fm API key. Set it via the `SHANTY_LASTFM_API_KEY` environment variable:
```yaml
# In docker-compose.yml
environment:
- SHANTY_LASTFM_API_KEY=your_api_key_here
```
Get a free API key at: https://www.last.fm/api/account/create
Other playlist strategies (genre, random, smart rules) do not require an API key.
## Generating a playlist
1. Open the Shanty web UI and go to **Playlists**.
2. Click the **Generate** tab.
3. Choose a generation strategy:
- **Similar Artists** -- Start with one or more seed artists and build a playlist from their music and music by similar artists. This is the most useful strategy for discovering connections in your library.
- **Genre** -- Filter tracks by genre tags.
- **Random** -- A random selection from your library.
- **Smart Rules** -- Define rules based on track attributes (artist, album, year, genre, etc.).
4. For the Similar Artists strategy:
- Search for and select one or more **seed artists**.
- Adjust **popularity bias** (higher values favor well-known tracks, lower values dig deeper).
- Set the **track count** (how many tracks in the playlist).
- Choose **ordering**: interleave (mix artists evenly), score (highest match first), or random.
5. Click **Generate**.
The generated playlist is shown as a preview. You can then save it or adjust the parameters and regenerate.
## Saving a playlist
After generating a playlist, click **Save** to give it a name and store it. Saved playlists appear in the **Saved** tab.
## Editing a playlist
1. Go to the **Saved** tab in Playlists.
2. Click **Edit** on a playlist.
3. In the Edit tab, you can:
- **Drag and drop** tracks to reorder them.
- **Remove** tracks by clicking the remove button next to each track.
- **Add** tracks by searching in the track picker at the bottom.
- **Rename** the playlist or update its description.
## Exporting as M3U
To export a playlist as an M3U file (compatible with most music players):
1. Open a saved playlist.
2. Click **Export M3U**.
3. The file downloads to your computer.
M3U files contain file paths relative to your library, so they work with any music player that has access to the same library directory.
## Playlists via Subsonic
Saved playlists are available through the Subsonic API. Any Subsonic client that supports playlists (most do) can see and play your Shanty playlists.
To use this, make sure you have [Subsonic set up](subsonic.md) with a Subsonic password configured.
## Generation strategies explained
### Similar Artists
This uses Last.fm's similar-artist data to find tracks from artists related to your seeds. For example, if you seed with "Radiohead," the playlist might include tracks by Radiohead, Thom Yorke, Portishead, Massive Attack, and other similar artists -- but only tracks that exist in your library.
The **popularity bias** slider controls the balance:
- High bias (closer to 1.0): favors popular/well-known tracks
- Low bias (closer to 0.0): gives equal weight to all tracks
### Genre
Filters your library by genre tags and selects tracks that match. You can specify one or more genres.
### Random
A random sample from your entire library. If ordering is not set to "random," tracks are ordered by artist/album for easier listening.
### Smart Rules
Define custom rules like:
- Artist contains "Pink Floyd"
- Year is between 1970 and 1980
- Genre is "Progressive Rock"
Tracks matching all rules are included, up to the count limit.
+94
View File
@@ -0,0 +1,94 @@
# Subsonic Streaming
Shanty includes a Subsonic-compatible API that lets you stream your music library to mobile apps, desktop players, and other Subsonic clients.
## What is Subsonic?
Subsonic is a widely-supported protocol for streaming music from a personal server. Many music apps implement the Subsonic API, which means you can use any of them to stream your Shanty library to your phone, tablet, or desktop.
## Important note
Shanty implements a subset of the Subsonic API -- enough for browsing, streaming, playlists, and search. If you need a full-featured Subsonic server with advanced features like sharing, podcasts, or Internet radio, consider running [Navidrome](https://www.navidrome.org/) pointed at the same music library directory. Both can coexist.
## How to set up
### 1. Set a Subsonic password
The Subsonic protocol uses its own authentication system, separate from your Shanty web login.
1. Open the Shanty web UI and go to **Settings**.
2. Find the **Subsonic API** section.
3. Enter a password and click **Save**.
**Security note:** The Subsonic protocol transmits passwords as MD5 hashes (not encrypted). This is a limitation of the protocol itself. Do not reuse a password you use for other services. The Subsonic password is stored in plain text in the database, per the protocol specification.
### 2. Configure your client
In your Subsonic client app, add a new server with these settings:
| Field | Value |
|-------|-------|
| **Server URL** | `http://your-server-ip:8085` |
| **Username** | Your Shanty username |
| **Password** | The Subsonic password you set (not your web login password) |
Most clients automatically append `/rest` to the server URL. If your client asks for just the base URL, enter `http://your-server-ip:8085`.
If your client has separate fields for server address and port, enter the IP/hostname and `8085` separately.
### 3. Test the connection
Most clients have a "Test Connection" button. Use it to verify that the server is reachable and your credentials are correct.
## Recommended clients
These clients have been tested with Shanty's Subsonic implementation:
### Android
- **[Ultrasonic](https://f-droid.org/packages/org.moire.ultrasonic/)** -- Free and open source. Available on F-Droid and Google Play. Reliable and well-maintained.
- **[DSub](https://play.google.com/store/apps/details?id=github.daneren2005.dsub)** -- Feature-rich, supports offline caching.
- **[Symfonium](https://play.google.com/store/apps/details?id=app.symfonik.music.player)** -- Modern UI, excellent playback features. Paid app.
### Desktop
- **[Feishin](https://github.com/jeffvli/feishin)** -- Cross-platform desktop client with a modern interface. Free and open source.
## Supported Subsonic endpoints
Shanty implements these Subsonic API endpoints:
- **System:** `ping`, `getLicense`
- **Browsing:** `getMusicFolders`, `getIndexes`, `getMusicDirectory`, `getArtists`, `getArtist`, `getAlbum`, `getSong`, `getGenres`
- **Search:** `search3`
- **Media:** `stream`, `download`, `getCoverArt`
- **Playlists:** `getPlaylists`, `getPlaylist`, `createPlaylist`, `deletePlaylist`
- **Annotation:** `scrobble`
- **User:** `getUser`
## Transcoding
If your music is in Opus format (the default download format), many mobile clients cannot play it directly. Shanty automatically transcodes Opus files to MP3 when streaming via the Subsonic API. This requires ffmpeg, which is included in the Docker image.
If you download in MP3 format or your clients support Opus natively, you can disable transcoding:
```yaml
subsonic:
transcoding_enabled: false
```
## Troubleshooting
**"Authentication failed" in client:**
- Make sure you are using the Subsonic password, not your web login password.
- Make sure the username matches exactly (case-sensitive).
**Client cannot connect:**
- Verify the server URL includes the port (`http://ip:8085`, not just `http://ip`).
- Check that port 8085 is accessible from the device (firewall rules, same network, etc.).
**No music showing up:**
- Music must be organized in the library (run the pipeline at least once) to appear in the Subsonic API.
- Only tracks with file paths in the database are served.
**Audio does not play:**
- If using Opus format, make sure transcoding is enabled (it is by default).
- Check that ffmpeg is installed (included in Docker, must be installed manually for source builds).
+72
View File
@@ -0,0 +1,72 @@
# YouTube Authentication
Shanty downloads music from YouTube via yt-dlp. By default, it makes requests as a guest, which limits you to roughly 250 downloads per hour. Authenticating with a Google account increases this to roughly 1800 downloads per hour and grants access to age-restricted content.
## Should you set this up?
If you are downloading just a few albums at a time, guest mode is fine. If you are adding several artists at once or building a large library, authentication will save you a lot of waiting.
## Warning: Use a throwaway account
There is a small but real risk that Google may flag your account for unusual activity. Automated downloading is against YouTube's terms of service. To protect yourself:
- Create a new Google account specifically for this purpose.
- Do not use your main Google account.
- Do not use an account tied to important services (Gmail, Google Drive, etc.).
## How to set up
1. Open the Shanty web UI and go to **Settings**.
2. Scroll to the **YouTube Authentication** section.
3. Click **Authenticate**.
4. A noVNC browser window opens, showing Firefox inside the Shanty container.
5. Navigate to YouTube and log in with your throwaway Google account.
6. Once you are logged in, go back to the Shanty settings page and click **Done**.
Shanty extracts the YouTube cookies from Firefox and saves them. From this point forward, all downloads use your authenticated session.
## Docker port requirement
The noVNC interface runs on port **6080** by default. Your `docker-compose.yml` must expose this port:
```yaml
ports:
- "8085:8085"
- "6080:6080" # Required for YouTube login
```
If you are running Shanty on a remote server, you need to access noVNC from your browser at `http://your-server-ip:6080`. Make sure firewalls allow this port during the login process. You can close the port afterward if you want -- it is only needed during initial login and manual re-authentication.
The noVNC port is configurable via `download.vnc_port` in the config file.
## How auto-refresh works
YouTube cookies expire roughly every 2 weeks. Shanty automatically refreshes them using headless Firefox (no visible browser window needed):
- After you complete the login process, auto-refresh is enabled automatically.
- Every 6 hours (configurable via `download.cookie_refresh_hours`), Shanty launches Firefox in headless mode, loads YouTube using the saved profile, and exports fresh cookies.
- If a refresh fails, Shanty logs a warning and tries again at the next interval.
You can check the current cookie status in **Settings** under **YouTube Authentication**. It shows whether cookies are present, how old they are, and whether auto-refresh is enabled.
## Manual refresh
If cookies have expired and auto-refresh is not working, you can:
1. Click **Refresh** in the YouTube Authentication settings to trigger an immediate headless refresh.
2. If that fails, click **Authenticate** to log in again through noVNC.
## Clearing cookies
To remove all YouTube authentication data, click **Clear** in the YouTube Authentication settings. This deletes the cookies file and the Firefox profile, and disables auto-refresh.
## Configuration options
| Option | Default | Description |
|--------|---------|-------------|
| `download.cookies_path` | (auto) | Path to the cookies file. Managed automatically. |
| `download.cookie_refresh_enabled` | `false` | Auto-enabled after login. |
| `download.cookie_refresh_hours` | `6` | Hours between refresh attempts. |
| `download.rate_limit` | `250` | Requests/hour without cookies. |
| `download.rate_limit_auth` | `1800` | Requests/hour with cookies. |
| `download.vnc_port` | `6080` | Port for the noVNC login interface. |
+149
View File
@@ -0,0 +1,149 @@
# Getting Started
This guide walks you through setting up Shanty from scratch. By the end, you will have a running instance that can search for music, download it, and play it on your phone.
## Prerequisites
You need Docker and Docker Compose installed on your computer or server. If you do not have them:
- **Linux:** Follow the [Docker install guide](https://docs.docker.com/engine/install/) for your distribution.
- **Mac/Windows:** Install [Docker Desktop](https://www.docker.com/products/docker-desktop/).
You also need a folder where you want your music library stored.
## Step 1: Create a docker-compose.yml
Create a new folder for your Shanty configuration, then create a file called `docker-compose.yml` inside it:
```sh
mkdir shanty && cd shanty
```
Create `docker-compose.yml` with these contents:
```yaml
services:
shanty:
image: git.squid-inc.dev/connor/shanty:latest
ports:
- "8085:8085"
- "6080:6080"
volumes:
- ./config:/config
- shanty-data:/data
- /path/to/your/music:/music
environment:
- SHANTY_WEB_BIND=0.0.0.0
restart: unless-stopped
volumes:
shanty-data:
```
Replace `/path/to/your/music` with the actual path to where you want your music stored. For example, `/home/yourname/Music` on Linux or `/Users/yourname/Music` on Mac.
### What the volumes do
| Mount point | Purpose |
|-------------|---------|
| `./config` | Stores your configuration file (`config.yaml`). Lives next to your docker-compose.yml. |
| `shanty-data` | Stores the database and downloaded files before they are organized. Managed by Docker. |
| `/music` | Your music library. This is where organized, tagged music ends up. |
## Step 2: Start the container
From the folder containing your `docker-compose.yml`:
```sh
docker compose up -d
```
Wait a few seconds for the container to start. You can check the logs with:
```sh
docker compose logs -f
```
You should see a line like `starting server bind=0.0.0.0:8085`. Press Ctrl+C to stop watching logs (the container keeps running).
## Step 3: Create your account
Open your browser and go to:
```
http://localhost:8085
```
If Shanty is running on another machine, replace `localhost` with that machine's IP address.
On first launch, you will see a setup screen. Choose a username and password (at least 4 characters). This creates an admin account.
## Step 4: Search and watch an artist
1. Click the **Search** tab in the navigation.
2. Type an artist name (for example, "Pink Floyd") and press Enter.
3. Click on the artist in the search results to view their discography.
4. Click **Watch All** to add their entire discography to your watchlist. Or click individual albums to watch specific ones.
You will see the artist appear on the **Library** page with track counts showing how many tracks are wanted versus owned.
## Step 5: Download your music
Click the **Set Sail** button on the Dashboard. This runs the full pipeline:
1. **Sync** -- creates download jobs for all wanted tracks
2. **Download** -- fetches audio from YouTube Music via yt-dlp
3. **Tag** -- writes MusicBrainz metadata to the files
4. **Organize** -- moves files into your music library in a clean folder structure
You can watch progress on the Dashboard. The download queue shows each track as it is processed.
Depending on how many tracks you are downloading, this can take a while. YouTube has rate limits (roughly 250 downloads per hour without authentication). See the [YouTube Authentication](features/youtube-auth.md) guide to increase this to roughly 1800 per hour.
## Step 6 (Optional): Set up Subsonic for mobile playback
Shanty includes a Subsonic-compatible API that lets you stream your library from mobile apps like Ultrasonic, DSub, or Symfonium.
1. In the Shanty web UI, go to **Settings**.
2. Find the **Subsonic API** section.
3. Set a Subsonic password. This is separate from your web login password.
4. On your phone, install a Subsonic client (Ultrasonic for Android is free and works well).
5. In the client, add a server with the URL `http://your-server-ip:8085`. The client adds `/rest` automatically.
6. Enter your Shanty username and the Subsonic password you just set.
See the [Subsonic guide](features/subsonic.md) for more details and recommended clients.
## Step 7 (Optional): Set up YouTube authentication
Without authentication, YouTube limits you to roughly 250 downloads per hour. With authentication, this increases to roughly 1800 per hour. You also get access to age-restricted content.
1. Go to **Settings** in the Shanty web UI.
2. Find the **YouTube Authentication** section and click **Authenticate**.
3. A browser window opens in your browser via noVNC. Log in to a Google account.
4. Click **Done** when finished.
**Important:** Use a throwaway Google account. There is a small risk of account restrictions. See the [YouTube Authentication guide](features/youtube-auth.md) for details.
## Step 8 (Optional): Import the MusicBrainz database
MusicBrainz has a rate limit of 1 request every 1.1 seconds. When you browse a new artist, loading their full discography can take 30-60 seconds. Importing the MusicBrainz database locally makes these lookups instant.
This requires about 24 GB of download space, produces a 16 GB database, and takes 12-24 hours for the initial import. An SSD is strongly recommended.
To start the import from the web UI:
1. Go to **Settings**.
2. Find the **MusicBrainz Database** section.
3. Click **Import**.
Or from the command line:
```sh
docker compose exec shanty ./shanty mb-import --download
```
See the [MusicBrainz Database guide](features/musicbrainz-db.md) for more details.
## What next?
- Set up [monitoring](features/monitoring.md) to automatically detect new releases from your favorite artists.
- Generate [playlists](features/playlists.md) from your library using similar-artist data.
- Check the [Configuration Reference](configuration.md) to customize download formats, organization templates, and scheduling.
+59 -147
View File
@@ -1,172 +1,84 @@
# shanty # Shanty
A modular, self-hosted music management application for the high seas. Shanty aims to be a Shanty is a self-hosted music management application. It searches MusicBrainz for artists and albums, downloads music from YouTube, tags and organizes files, and serves your library over the Subsonic protocol for streaming on any device.
better alternative to Lidarr — managing, tagging, organizing, and downloading music, all
built as a collection of standalone Rust tools that work together seamlessly through a web UI.
## Features ## Features
- **Search** MusicBrainz for artists, albums, and tracks - **Search** MusicBrainz for artists, albums, and tracks
- **Watch** artists/albums — automatically expands to individual track-level monitoring - **Watch** artists or albums to track their full discography at the individual track level
- **Download** music via yt-dlp with YouTube Music search (ytmusicapi) - **Download** music via yt-dlp with YouTube Music search
- **Tag** files with MusicBrainz metadata (fuzzy matching + MBID-based lookup) - **Tag** files automatically using MusicBrainz metadata
- **Organize** files into clean directory structures with configurable templates - **Organize** files into clean directory structures with configurable templates
- **Web UI** — Yew (Rust/WASM) dashboard with real-time status, search, library browser - **Stream** your library to any Subsonic-compatible music app
- **Playlists** generated from similar-artist data (Last.fm), with drag-and-drop editing and M3U export
- **Monitor** artists for new releases, automatically added to your watchlist
- **Web UI** built in Rust/WASM for managing everything from your browser
## Architecture ## Quick Start with Docker
Shanty is a Cargo workspace where each component is its own crate and git submodule. This is the recommended way to run Shanty.
Each crate is both a library (for the web app) and a standalone CLI binary.
| Crate | Description | Status | **1.** Create a file called `docker-compose.yml`:
|-------|-------------|--------|
| `shanty-db` | Sea-ORM + SQLite schema, migrations, queries | Done |
| `shanty-index` | Music file scanning and metadata extraction (lofty) | Done |
| `shanty-tag` | MusicBrainz client, fuzzy matching, file tag writing | Done |
| `shanty-org` | File organization with configurable format templates | Done |
| `shanty-watch` | Watchlist management, MB discography expansion | Done |
| `shanty-dl` | yt-dlp backend, rate limiting, download queue | Done |
| `shanty-search` | SearchProvider trait, MB search + release groups | Done |
| `shanty-web` | Actix backend + Yew frontend | Done (MVP) |
| `shanty-notify` | Notifications (Apprise, webhooks) | Stub |
| `shanty-playlist` | Playlist generation | Stub |
| `shanty-serve` | Subsonic-compatible music streaming | Stub |
| `shanty-play` | Built-in web player | Stub |
## Quick Start
### Prerequisites
- Rust (edition 2024)
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) + ffmpeg
- Python 3 + `pip install ytmusicapi`
- [Trunk](https://trunkrs.dev/) (for building the frontend)
### Build
```sh
# Backend (all crates)
cargo build --workspace
# Frontend (Yew/WASM)
cd shanty-web/frontend && trunk build
```
### Run the web app
```sh
cargo run --bin shanty-web -- -v
# Open http://localhost:8085
```
### CLI tools
Each crate also works as a standalone CLI:
```sh
# Index a music directory
cargo run --bin shanty-index -- /path/to/music -v
# Tag untagged tracks
cargo run --bin shanty-tag -- --all --write-tags -v
# Search MusicBrainz
cargo run --bin shanty-search -- artist "Pink Floyd"
# Add to watchlist (expands album to individual tracks)
cargo run --bin shanty-watch -- add album "Green Day" "Dookie"
# Sync watchlist to download queue and process
cargo run --bin shanty-dl -- queue sync -v
cargo run --bin shanty-dl -- queue process -v
# Organize files
cargo run --bin shanty-org -- --from-db --target ~/Music -v
```
### Full pipeline (CLI)
```sh
shanty-watch add album "Green Day" "Dookie" --mbid <release-mbid>
shanty-dl queue sync -v
shanty-dl queue process -v
shanty-tag --all --write-tags -v
shanty-org --from-db --target ~/Music -v
```
## Configuration
Create `~/.config/shanty/config.yaml`:
```yaml ```yaml
library_path: ~/Music services:
download_path: ~/.local/share/shanty/downloads shanty:
organization_format: "{artist}/{album}/{track_number} - {title}.{ext}" image: git.squid-inc.dev/connor/shanty:latest
ports:
- "8085:8085"
- "6080:6080"
volumes:
- ./config:/config
- shanty-data:/data
- /path/to/your/music:/music
environment:
- SHANTY_WEB_BIND=0.0.0.0
restart: unless-stopped
# Filter which release types to show (empty = studio only) volumes:
# Options: Compilation, Live, Soundtrack, Remix, DJ-mix, Demo shanty-data:
allowed_secondary_types: []
web:
port: 8085
bind: 0.0.0.0
tagging:
write_tags: true
confidence: 0.85
download:
format: opus
search_source: ytmusic
# cookies_path: ~/.config/shanty/cookies.txt # for higher YT rate limits
``` ```
## Docker **2.** Replace `/path/to/your/music` with the directory where you want your music library stored.
### Quick start **3.** Start the container:
```sh ```sh
# Edit compose.yml to set your music library path, then:
docker compose up -d docker compose up -d
```
**4.** Open `http://localhost:8085` in your browser. Create an account on first launch.
**5.** Search for an artist, click **Watch All**, then click **Set Sail** to start downloading.
## Quick Start from Source
For developers who want to build and run locally:
```sh
# Prerequisites: Rust (edition 2024), yt-dlp, ffmpeg, Python 3, ytmusicapi, Trunk
git clone https://git.squid-inc.dev/connor/shanty.git
cd shanty
# Build frontend
cd shanty-web/frontend && trunk build --release && cd ../..
# Build and run
cargo run --release --bin shanty -- -v
# Open http://localhost:8085 # Open http://localhost:8085
``` ```
### Build from source ## Documentation
```sh - [Getting Started](docs/getting-started.md) -- step-by-step setup guide
docker build -t shanty . - [Configuration Reference](docs/configuration.md) -- all config options and environment variables
docker run -d \ - [API Reference](docs/api.md) -- REST and Subsonic API documentation
-p 8085:8085 \ - **Feature Guides:**
-v ./config:/config \ - [YouTube Authentication](docs/features/youtube-auth.md) -- higher rate limits with cookie auth
-v shanty-data:/data \ - [MusicBrainz Local Database](docs/features/musicbrainz-db.md) -- instant metadata lookups
-v /path/to/music:/music \ - [Subsonic Streaming](docs/features/subsonic.md) -- mobile and desktop playback
shanty - [Playlists](docs/features/playlists.md) -- generation, editing, and export
``` - [Monitoring](docs/features/monitoring.md) -- automatic new release detection
### Volumes
| Mount | Purpose |
|-------|---------|
| `/config` | Config file (`config.yaml`) |
| `/data` | Database (`shanty.db`) and downloads |
| `/music` | Your music library |
### Environment variables
| Variable | Default | Description |
|----------|---------|-------------|
| `SHANTY_CONFIG` | `/config/config.yaml` | Config file path |
| `SHANTY_DATABASE_URL` | `sqlite:///data/shanty.db?mode=rwc` | Database URL |
| `SHANTY_LIBRARY_PATH` | `/music` | Music library path |
| `SHANTY_DOWNLOAD_PATH` | `/data/downloads` | Download directory |
| `SHANTY_WEB_PORT` | `8085` | HTTP port |
| `SHANTY_WEB_BIND` | `0.0.0.0` | Bind address |
## Testing
```sh
cargo test --workspace
```
## License ## License
+10 -9
View File
@@ -44,20 +44,21 @@ impl LocalMusicBrainzFetcher {
/// Check whether the database has been populated with data. /// Check whether the database has been populated with data.
pub fn is_available(&self) -> bool { pub fn is_available(&self) -> bool {
let conn = self.conn.lock().unwrap(); let conn = self.conn.lock().unwrap();
// Check if the mb_artists table exists and has rows // Check if the mb_artists table exists and has at least one row.
// Use EXISTS (SELECT 1 ...) instead of COUNT(*) to avoid scanning the entire table.
conn.query_row( conn.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='mb_artists'", "SELECT EXISTS(SELECT 1 FROM sqlite_master WHERE type='table' AND name='mb_artists')",
[], [],
|row| row.get::<_, i32>(0), |row| row.get::<_, bool>(0),
) )
.map(|c| c > 0)
.unwrap_or(false) .unwrap_or(false)
&& conn && conn
.query_row("SELECT COUNT(*) FROM mb_artists LIMIT 1", [], |row| { .query_row(
row.get::<_, i32>(0) "SELECT EXISTS(SELECT 1 FROM mb_artists LIMIT 1)",
}) [],
.unwrap_or(0) |row| row.get::<_, bool>(0),
> 0 )
.unwrap_or(false)
} }
/// Get statistics about the imported data. /// Get statistics about the imported data.