chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Audio Quality
|
||||
|
||||
cliamp lets you tune the output sample rate, speaker buffer size, resample quality, and bit depth via `~/.config/cliamp/config.toml`. The active settings are shown in the `OUT` status line below the EQ.
|
||||
|
||||
## Configuration
|
||||
|
||||
Add any of these to your config file:
|
||||
|
||||
```toml
|
||||
# Output sample rate in Hz (22050, 44100, 48000, 96000, 192000)
|
||||
sample_rate = 44100
|
||||
|
||||
# Speaker buffer in milliseconds (50-500)
|
||||
buffer_ms = 100
|
||||
|
||||
# Resample quality (1-4, where 4 is best)
|
||||
resample_quality = 4
|
||||
|
||||
# PCM bit depth for FFmpeg-decoded formats: 16 (default) or 32 (lossless)
|
||||
bit_depth = 16
|
||||
```
|
||||
|
||||
All four are optional. Defaults are shown above.
|
||||
|
||||
## What they do
|
||||
|
||||
| Setting | Effect |
|
||||
|--------------------|------------------------------------------------------------------------|
|
||||
| `sample_rate` | Output rate sent to your sound card. 48000 matches most modern DACs. |
|
||||
| `buffer_ms` | Lower = less latency, higher = fewer glitches. Try 200 if audio pops. |
|
||||
| `resample_quality` | Sinc interpolation quality when a file's native rate differs from output. 4 is best, 1 is fastest. |
|
||||
| `bit_depth` | PCM precision for FFmpeg-decoded formats (m4a, aac, alac, opus, wma, webm). 32 uses float PCM which preserves up to 24-bit audio without truncation. Native formats (mp3, flac, wav, ogg) always decode at full precision regardless of this setting. |
|
||||
|
||||
## Quick recipes
|
||||
|
||||
**Lossless / hi-res setup** (good DAC, beefy CPU):
|
||||
|
||||
```toml
|
||||
sample_rate = 96000
|
||||
buffer_ms = 100
|
||||
resample_quality = 4
|
||||
bit_depth = 32
|
||||
```
|
||||
|
||||
**Low-latency / weak hardware**:
|
||||
|
||||
```toml
|
||||
sample_rate = 44100
|
||||
buffer_ms = 200
|
||||
resample_quality = 1
|
||||
```
|
||||
|
||||
Changes take effect on next launch.
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
# CLI Flags
|
||||
|
||||
Override any config option for a single session without editing `~/.config/cliamp/config.toml`. Flags can appear before or after file/URL arguments.
|
||||
|
||||
## Playback
|
||||
|
||||
```sh
|
||||
cliamp --volume -5 track.mp3 # volume in dB [-30, +6]
|
||||
cliamp --shuffle ~/Music # enable shuffle
|
||||
cliamp --repeat all ~/Music # repeat mode: off, all, one
|
||||
cliamp --mono track.mp3 # downmix to mono
|
||||
cliamp --no-mono track.mp3 # force stereo
|
||||
cliamp --auto-play ~/Music # start playback immediately
|
||||
cliamp --playlist "Blade Runner" # load a local TOML playlist (add --auto-play to start playback)
|
||||
```
|
||||
|
||||
## Audio engine
|
||||
|
||||
```sh
|
||||
cliamp --sample-rate 48000 track.mp3 # output sample rate (22050, 44100, 48000, 96000, 192000)
|
||||
cliamp --buffer-ms 200 track.mp3 # speaker buffer in ms (50–500)
|
||||
cliamp --resample-quality 1 track.mp3 # resample quality factor (1–4)
|
||||
cliamp --bit-depth 32 track.m4a # PCM bit depth: 16 (default) or 32 (lossless)
|
||||
```
|
||||
|
||||
## Appearance
|
||||
|
||||
```sh
|
||||
cliamp --compact ~/Music # cap width at 80 columns
|
||||
cliamp --eq-preset "Bass Boost" ~/Music
|
||||
```
|
||||
|
||||
## Diagnostics
|
||||
|
||||
```sh
|
||||
cliamp --log-level debug # raise log verbosity for one session
|
||||
```
|
||||
|
||||
Logs are written to `~/.config/cliamp/cliamp.log`. Levels: `debug`, `info` (default), `warn`, `error`.
|
||||
|
||||
## Low-power mode
|
||||
|
||||
```sh
|
||||
cliamp --low-power track.mp3 # reduce CPU load for this session
|
||||
```
|
||||
|
||||
Reduces CPU load by lowering UI/render cadence and forcing the visualizer to `none`. Useful on battery, slow terminals, or SSH sessions. Press `v` in the player to cycle visualizers back on at any time.
|
||||
|
||||
To make this persistent, set it in `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
low_power = true
|
||||
```
|
||||
|
||||
## Headless daemon mode
|
||||
|
||||
```sh
|
||||
cliamp --daemon # no TUI, IPC only
|
||||
cliamp --daemon --auto-play --playlist Lofi # start playing on launch
|
||||
cliamp -d ~/Music --auto-play # short flag form
|
||||
```
|
||||
|
||||
Runs cliamp without rendering a UI, listening on the same Unix socket the TUI uses. All `cliamp <subcommand>` IPC clients keep working. UI-specific commands (`theme`, `vis`) return an error in this mode. See [Headless Daemon Mode](headless.md) for use cases and example configs (Waybar, Hyprland, systemd, cron).
|
||||
|
||||
## Search
|
||||
|
||||
Search and play a track directly from the command line (requires [yt-dlp](https://github.com/yt-dlp/yt-dlp)):
|
||||
|
||||
```sh
|
||||
cliamp search "never gonna give you up" # search YouTube
|
||||
cliamp search-sc "lofi beats" # search SoundCloud
|
||||
```
|
||||
|
||||
Press `Ctrl+F` in the player for context-aware search: it runs the active provider's native search when available or falls back to YouTube search.
|
||||
|
||||
## General
|
||||
|
||||
| Flag | Short | Description |
|
||||
|------|-------|-------------|
|
||||
| `--help` | `-h` | Show help and exit |
|
||||
| `--version` | `-v` | Print version and exit |
|
||||
| `--upgrade` | | Update to the latest release |
|
||||
|
||||
## Mixing flags and files
|
||||
|
||||
Flags can appear before, after, or between positional arguments:
|
||||
|
||||
```sh
|
||||
cliamp --shuffle track.mp3 --volume -5
|
||||
cliamp track.mp3 --repeat all --mono ~/Music
|
||||
```
|
||||
|
||||
## Flag reference
|
||||
|
||||
| Flag | Type | Default | Range / Values |
|
||||
|------|------|---------|----------------|
|
||||
| `--volume` | float | 0 | -30 to +6 dB |
|
||||
| `--shuffle` | bool | false | |
|
||||
| `--repeat` | string | off | off, all, one |
|
||||
| `--mono` / `--no-mono` | bool | false | |
|
||||
| `--auto-play` | bool | false | |
|
||||
| `--compact` | bool | false | |
|
||||
| `--theme` | string | | theme name |
|
||||
| `--eq-preset` | string | | preset name |
|
||||
| `--sample-rate` | int | 44100 | 22050, 44100, 48000, 96000, 192000 |
|
||||
| `--buffer-ms` | int | 100 | 50–500 |
|
||||
| `--resample-quality` | int | 4 | 1–4 |
|
||||
| `--bit-depth` | int | 16 | 16, 32 |
|
||||
| `--playlist` | string | | local TOML playlist name |
|
||||
| `--log-level` | string | info | debug, info, warn, error |
|
||||
| `--low-power` | bool | false | lowers UI cadence; disables visualization |
|
||||
| `--daemon` / `-d` | bool | false | run headless; IPC only, no TUI |
|
||||
|
||||
CLI flags override config file values for the current session only. They are not persisted.
|
||||
|
||||
## Setup wizard
|
||||
|
||||
Configure remote providers (Navidrome, Plex, Jellyfin, Emby, Spotify, Qobuz, NetEase, YouTube Music) through a small TUI. Each provider page links to where to find the required credentials, validates the connection live, and writes the resulting `[provider]` block to `~/.config/cliamp/config.toml` without disturbing the rest of the file.
|
||||
|
||||
```sh
|
||||
cliamp setup
|
||||
```
|
||||
|
||||
Keys: `↑/↓` to navigate, `Enter` to confirm or submit, `Esc` to back out, `q` from the menu to quit. Passwords and tokens are masked. Running setup again for an already-configured provider replaces its section in place.
|
||||
|
||||
## Playlist Management
|
||||
|
||||
Manage local TOML playlists from the command line without opening the TUI.
|
||||
|
||||
```sh
|
||||
cliamp playlist list # list playlists with track counts
|
||||
cliamp playlist create "Name" # create an empty playlist
|
||||
cliamp playlist create "Name" file1 dir/ ... # create from files/folders (recursive, skips duplicate paths)
|
||||
cliamp playlist create "Name" --ssh HOST dir/ # create from remote machine via SSH
|
||||
cliamp playlist add "Name" file1 ... # append tracks to existing playlist, skipping duplicates
|
||||
cliamp playlist rename "Old" "New" # rename a playlist
|
||||
cliamp playlist show "Name" # display tracks
|
||||
cliamp playlist show "Name" --json # machine-readable output
|
||||
cliamp playlist remove "Name" --index 3 # remove track by index
|
||||
cliamp playlist dedupe "Name" # remove duplicate paths, keeping the first
|
||||
cliamp playlist sort "Name" --by album # sort in place
|
||||
cliamp playlist doctor [Name] # report missing local files
|
||||
cliamp playlist doctor "Name" --fix # prune missing local files
|
||||
cliamp playlist export "Name" -o mix.m3u # export as M3U
|
||||
cliamp playlist export "Name" --format pls # export as PLS to stdout
|
||||
cliamp playlist import mix.m3u --name "Name" # import local M3U/M3U8/PLS
|
||||
cliamp playlist bookmark "Name" --index 3 # toggle bookmark flag
|
||||
cliamp playlist bookmarks # list bookmarked tracks
|
||||
cliamp playlist enrich "Name" # probe duration/album metadata
|
||||
cliamp playlist delete "Name" # delete entire playlist
|
||||
```
|
||||
|
||||
Sort keys: `track`, `title`, `artist`, `album`, `artist+album`, `path`.
|
||||
|
||||
See [playlists.md](playlists.md) for the TOML format and [ssh-streaming.md](ssh-streaming.md) for remote playback.
|
||||
|
||||
## Recently Played
|
||||
|
||||
```sh
|
||||
cliamp history # show the 50 most recent plays
|
||||
cliamp history --limit 200 # change the cap
|
||||
cliamp history --json # machine-readable output
|
||||
cliamp history clear # wipe ~/.config/cliamp/history.toml
|
||||
```
|
||||
|
||||
A play is recorded once you've listened to a track for at least 50% of its
|
||||
duration. Inside the TUI, the same data appears as the virtual "Recently
|
||||
Played" entry in the Local Playlists provider. See [history.md](history.md).
|
||||
|
||||
## Spotify
|
||||
|
||||
```sh
|
||||
cliamp spotify reset # clear stored Spotify credentials
|
||||
```
|
||||
|
||||
Use `spotify reset` if you see persistent `rate-limited on /v1/me` warnings or stale auth errors. After running it, relaunch cliamp and select Spotify to sign in again. See [spotify.md](spotify.md) for the full setup guide.
|
||||
|
||||
## Remote Control (IPC)
|
||||
|
||||
Control a running cliamp instance from another terminal:
|
||||
|
||||
```sh
|
||||
cliamp play / pause / toggle / stop # playback control
|
||||
cliamp next / prev # track navigation
|
||||
cliamp status # current state
|
||||
cliamp status --json # machine-readable state
|
||||
cliamp volume -5 # adjust volume (dB)
|
||||
cliamp seek 30 # seek to position (seconds)
|
||||
cliamp load "Playlist Name" # load a playlist
|
||||
cliamp queue /path/to/file.mp3 # queue a track
|
||||
cliamp shuffle [on|off|toggle] # toggle or set shuffle
|
||||
cliamp repeat [off|all|one|cycle] # set or cycle repeat mode
|
||||
cliamp mono [on|off|toggle] # toggle or set mono output
|
||||
cliamp speed 1.5 # set playback speed (0.25–2.0)
|
||||
cliamp eq Rock # set EQ preset by name
|
||||
cliamp eq --band 0 6.0 # set EQ band 0 to +6 dB
|
||||
cliamp device list # list audio output devices
|
||||
cliamp device "My DAC" # switch audio output device
|
||||
```
|
||||
|
||||
See [remote-control.md](remote-control.md) for the full protocol specification.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Community Plugins
|
||||
|
||||
Plugins built by the community. If you've built a plugin, open a PR to add it here.
|
||||
|
||||
| Plugin | Description | Author |
|
||||
|--------|-------------|--------|
|
||||
| [cliamp-lastfm](https://github.com/tetsuo76/cliamp-lastfm) | Last.fm scrobbling | [@tetsuo76](https://github.com/tetsuo76) |
|
||||
| [cliamp-plugin-led-burst](https://github.com/AlexZeitler/cliamp-plugin-led-burst) | Stereo LED matrix visualizer | [@AlexZeitler](https://github.com/AlexZeitler) |
|
||||
| [cliamp-plugin-block-burst](https://github.com/AlexZeitler/cliamp-plugin-block-burst) | Stereo LED block matrix visualizer | [@AlexZeitler](https://github.com/AlexZeitler) |
|
||||
| [cliamp-plugin-block-burst](https://github.com/AlexZeitler/cliamp-plugin-vu-meter) | Analog multi VU meter style visualizer | [@AlexZeitler](https://github.com/AlexZeitler) |
|
||||
| [cliamp-plugin-nightrider](https://github.com/HANCORE-linux/cliamp-plugin-nightrider) | Nightrider style visualizer | [@HANCORE-linux](https://github.com/HANCORE-linux) |
|
||||
| [cliamp-plugin-tubeamp](https://github.com/8bit64k/cliamp-plugin-tubeamp) | Vacuum-tube amplifier visualizer | [@8bit64k](https://github.com/8bit64k) |
|
||||
| [cliamp-plugin-nova](https://github.com/8bit64k/cliamp-plugin-nova) | Concentric-ring spectrum visualizer | [@8bit64k](https://github.com/8bit64k) |
|
||||
| [cliamp-plugin-mandelbrot](https://github.com/mikkel-bergmann/cliamp-plugin-mandelbrot) | Zooming psychedelic Mandelbrot set visualizer with braille rendering and bass reactivity | [@mikkel-bergmann](https://github.com/mikkel-bergmann) |
|
||||
@@ -0,0 +1,249 @@
|
||||
# Configuration
|
||||
|
||||
For remote providers (Navidrome, Plex, Jellyfin, Emby, Spotify, Qobuz, NetEase, YouTube Music), the fastest path is the interactive wizard:
|
||||
|
||||
```sh
|
||||
cliamp setup
|
||||
```
|
||||
|
||||
It validates your credentials live and writes the right TOML block without touching the rest of your config. See [cli.md](cli.md#setup-wizard) for details.
|
||||
|
||||
## Config directory
|
||||
|
||||
cliamp resolves its config directory in this order:
|
||||
|
||||
- `CLIAMP_CONFIG_DIR`
|
||||
- `XDG_CONFIG_HOME/cliamp`
|
||||
- `HOME/.config/cliamp`
|
||||
- on Windows, `%APPDATA%\cliamp` when `HOME` is not set
|
||||
|
||||
The examples below use `~/.config/cliamp` for brevity. On Windows without `HOME`, replace that path with `%APPDATA%\cliamp`.
|
||||
|
||||
For everything else, copy the example config and edit by hand:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/cliamp
|
||||
cp config.toml.example ~/.config/cliamp/config.toml
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
```toml
|
||||
# Default volume in dB (range: volume_min to 6)
|
||||
volume = 0
|
||||
|
||||
# Minimum volume floor in dB (range: -90 to 0, default: -50)
|
||||
# Controls how low the volume control can go.
|
||||
volume_min = -50
|
||||
|
||||
# Repeat mode: "off", "all", or "one"
|
||||
repeat = "off"
|
||||
|
||||
# Start with shuffle enabled
|
||||
shuffle = false
|
||||
|
||||
# Start with mono output (L+R downmix)
|
||||
mono = false
|
||||
|
||||
# Initial directory for the file browser ('o' key)
|
||||
initial_directory = "~/Music"
|
||||
|
||||
# Shift+Left/Right seek jump in seconds
|
||||
seek_large_step_sec = 30
|
||||
|
||||
# EQ preset: "Flat", "Rock", "Pop", "Jazz", "Classical",
|
||||
# "Bass Boost", "Treble Boost", "Vocal", "Electronic", "Acoustic"
|
||||
# Leave empty or "Custom" to use manual eq values below
|
||||
eq_preset = "Flat"
|
||||
|
||||
# 10-band EQ gains in dB (range: -12 to 12)
|
||||
# Bands: 70Hz, 180Hz, 320Hz, 600Hz, 1kHz, 3kHz, 6kHz, 12kHz, 14kHz, 16kHz
|
||||
# Only used when eq_preset is "Custom" or empty
|
||||
eq = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
# Visualizer mode (leave empty for default Bars)
|
||||
# Options: Bars, BarsDot, Rain, BarsOutline, Bricks, Columns, ClassicPeak, Wave, Scatter, Flame, Retro, Pulse, Matrix, Binary, Sakura, Firework, Bubbles, Logo, Terrain, Scope, Heartbeat, Butterfly, Ascii, Firefly, Mosaic, Sand, Geyser, ClassicLED, None
|
||||
visualizer = "Bars"
|
||||
|
||||
# Visualizer volume linking (default: true)
|
||||
# When true, bar height follows the current volume level (classic behavior).
|
||||
# Set to false to decouple the visualizer from volume — bars stay visible
|
||||
# even at very low volume levels.
|
||||
vis_volume_linked = true
|
||||
|
||||
# Reduce CPU usage by lowering UI cadence and disabling visualization.
|
||||
# This has the same effect as starting with --low-power.
|
||||
low_power = false
|
||||
|
||||
# Compact mode: cap UI width at 80 columns (default: fluid/full-width)
|
||||
compact = false
|
||||
|
||||
# UI theme name (see available themes in ~/.config/cliamp/themes/)
|
||||
theme = "Tokyo Night"
|
||||
|
||||
# Log level: "debug", "info", "warn", or "error" (default "info")
|
||||
# Logs are written to ~/.config/cliamp/cliamp.log
|
||||
log_level = "info"
|
||||
|
||||
```
|
||||
|
||||
## Secrets from Environment Variables
|
||||
|
||||
Any string value in `config.toml` can be read from an environment variable by setting the value to `$VAR_NAME` or `${VAR_NAME}`. This keeps passwords, tokens, and client secrets out of the file itself.
|
||||
|
||||
```toml
|
||||
[navidrome]
|
||||
url = "https://music.example.com"
|
||||
user = "alice"
|
||||
password = "${NAVIDROME_PASSWORD}"
|
||||
|
||||
[plex]
|
||||
url = "http://plex.local:32400"
|
||||
token = "$PLEX_TOKEN"
|
||||
|
||||
[jellyfin]
|
||||
url = "https://jelly.example.com"
|
||||
token = "${JELLYFIN_TOKEN}"
|
||||
|
||||
[emby]
|
||||
url = "https://emby.example.com"
|
||||
token = "${EMBY_TOKEN}"
|
||||
|
||||
[ytmusic]
|
||||
client_id = "${YTMUSIC_CLIENT_ID}"
|
||||
client_secret = "${YTMUSIC_CLIENT_SECRET}"
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Interpolation only happens when the **entire** value is `$NAME` or `${NAME}`. Mixed values like `"p@$$word"` are kept literally — no escaping needed.
|
||||
- Variable names match `[A-Za-z_][A-Za-z0-9_]*`.
|
||||
- If the variable is unset, the value is empty (the same as if you had left it blank).
|
||||
- Works for any string field, including plugin config under `[plugins.<name>]`.
|
||||
|
||||
## Default Provider
|
||||
|
||||
Set which provider to start with:
|
||||
|
||||
```toml
|
||||
provider = "radio"
|
||||
```
|
||||
|
||||
Valid values: `radio` (default), `navidrome`, `spotify`, `plex`, `jellyfin`, `emby`, `qobuz`, `soundcloud`, `netease`, `yt`, `youtube`, `ytmusic`.
|
||||
|
||||
You can also override from the CLI: `cliamp --provider jellyfin`.
|
||||
|
||||
## SoundCloud
|
||||
|
||||
SoundCloud is opt-in. Add the section to `~/.config/cliamp/config.toml` to register the provider:
|
||||
|
||||
```toml
|
||||
[soundcloud]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
Once enabled, search works via `Ctrl+F`, pasted SoundCloud URLs play through yt-dlp, and the empty browse view is seeded with a curated set of search-backed genre playlists (**Trending**, **Hip-Hop**, **Electronic**, **House**, **Lo-Fi**, **Indie**, **Pop**) so there's something to explore on first launch.
|
||||
|
||||
> SoundCloud's official charts/discover endpoints all 404 through yt-dlp at present, so cliamp can't surface real chart data anonymously. The genre playlists are search-backed (results vary in quality but reflect current uploads).
|
||||
|
||||
### Browse a profile
|
||||
|
||||
Set a username to expose that profile's tracks, likes, and reposts in the browse view:
|
||||
|
||||
```toml
|
||||
[soundcloud]
|
||||
enabled = true
|
||||
user = "yourname"
|
||||
```
|
||||
|
||||
Three playlists appear: **Tracks**, **Likes**, and **Reposts** for `soundcloud.com/yourname`. Works for any public profile.
|
||||
|
||||
### Sign in via browser cookies
|
||||
|
||||
SoundCloud closed its OAuth program in 2014, so the bring-your-own-client_id pattern Spotify uses isn't available. Instead, point yt-dlp at your existing browser session — it picks up your SoundCloud login from the browser cookie jar:
|
||||
|
||||
```toml
|
||||
[soundcloud]
|
||||
enabled = true
|
||||
user = "yourname"
|
||||
cookies_from = "firefox" # or chrome, chromium, brave, edge, opera, safari, vivaldi
|
||||
```
|
||||
|
||||
With cookies set, yt-dlp can stream subscriber-gated tracks (SoundCloud Go+) and access private likes/playlists your account is authorized for. The same cookies also apply to the player's yt-dlp invocations, so playback uses your signed-in session.
|
||||
|
||||
Requires `yt-dlp` on `PATH`.
|
||||
|
||||
## NetEase Cloud Music
|
||||
|
||||
NetEase is opt-in and uses your existing browser session. Sign in at `music.163.com`, then run:
|
||||
|
||||
```sh
|
||||
cliamp setup
|
||||
```
|
||||
|
||||
Pick **NetEase Cloud Music** and choose the browser you used to sign in. Common browsers are shown as menu choices; select the custom option only for profile-specific values. The setup wizard validates the session and writes:
|
||||
|
||||
```toml
|
||||
[netease]
|
||||
enabled = true
|
||||
cookies_from = "chrome"
|
||||
user_id = "your-account-user-id"
|
||||
```
|
||||
|
||||
Once enabled, the provider shows your liked songs, created playlists, saved playlists, and public charts. Search works with `Ctrl+F`, and playback uses `yt-dlp` with the same browser cookie source.
|
||||
|
||||
## Custom Radio Stations
|
||||
|
||||
Add your own stations to `~/.config/cliamp/radios.toml`:
|
||||
|
||||
```toml
|
||||
[[station]]
|
||||
name = "Jazz FM"
|
||||
url = "https://jazz.example.com/stream"
|
||||
|
||||
[[station]]
|
||||
name = "Ambient Radio"
|
||||
url = "https://ambient.example.com/stream.m3u"
|
||||
```
|
||||
|
||||
These appear alongside the built-in cliamp radio in the Radio provider.
|
||||
|
||||
See [audio-quality.md](audio-quality.md) for sample rate, buffer, bit depth, and resample quality settings.
|
||||
|
||||
## WSL2 (Windows Subsystem for Linux)
|
||||
|
||||
cliamp uses ALSA for audio on Linux. WSL2 doesn't expose ALSA hardware directly, but WSLg provides a PulseAudio server that ALSA can route through.
|
||||
|
||||
If you see errors like `ALSA lib pcm.c: Unknown PCM default`, fix it with two steps:
|
||||
|
||||
**1. Install the ALSA PulseAudio plugin:**
|
||||
|
||||
```sh
|
||||
sudo apt install libasound2-plugins
|
||||
```
|
||||
|
||||
**2. Create `~/.asoundrc` to route ALSA through PulseAudio:**
|
||||
|
||||
```sh
|
||||
cat > ~/.asoundrc << 'EOF'
|
||||
pcm.default pulse
|
||||
ctl.default pulse
|
||||
EOF
|
||||
```
|
||||
|
||||
WSLg must be active (`echo $PULSE_SERVER` should print a path). If it's empty, ensure you're on Windows 11 with WSLg enabled and run `wsl --shutdown` then reopen your terminal.
|
||||
|
||||
## ffmpeg (optional)
|
||||
|
||||
AAC, ALAC (`.m4a`), Opus, and WMA playback requires [ffmpeg](https://ffmpeg.org/):
|
||||
|
||||
```sh
|
||||
# Arch
|
||||
sudo pacman -S ffmpeg
|
||||
# Debian/Ubuntu
|
||||
sudo apt install ffmpeg
|
||||
# macOS
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
MP3, WAV, FLAC, and OGG work without ffmpeg.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Emby
|
||||
|
||||
cliamp can stream music directly from an Emby server using Emby's authenticated HTTP API. The integration exposes your music libraries as a flat album list in the normal provider pane, following the same shape as the Jellyfin and Plex providers.
|
||||
|
||||
> **Quick start:** run `cliamp setup` for a guided TUI that lets you pick API-key or username+password auth, validates against `/System/Info`, and writes the `[emby]` block for you. Manual setup steps are below.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A reachable Emby server
|
||||
- At least one library with `CollectionType = music`
|
||||
- An Emby API key or user credentials
|
||||
|
||||
## Configuration
|
||||
|
||||
Add an `[emby]` section to `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[emby]
|
||||
url = "https://emby.example.com"
|
||||
user = "alice"
|
||||
password = "your_password_here"
|
||||
# optional alternatives:
|
||||
# token = "xxxxxxxxxxxxxxxxxxxx"
|
||||
# user_id = "00000000000000000000000000000000"
|
||||
```
|
||||
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `url` | Base URL of your Emby server |
|
||||
| `user` | Emby username — used for password login, and to select the matching account when using an API key |
|
||||
| `password` | Emby password for password-based login |
|
||||
| `token` | Emby API key — alternative to username/password |
|
||||
| `user_id` | Optional Emby user id to skip discovery |
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured, **Emby** appears as a provider alongside Radio, Navidrome, Plex, Jellyfin, Spotify, and the YouTube providers.
|
||||
|
||||
To start cliamp with Emby selected:
|
||||
|
||||
```bash
|
||||
cliamp --provider emby
|
||||
```
|
||||
|
||||
Or set it in config:
|
||||
|
||||
```toml
|
||||
provider = "emby"
|
||||
```
|
||||
|
||||
The provider exposes a flat list of albums:
|
||||
|
||||
```text
|
||||
Artist — Album Title (Year)
|
||||
```
|
||||
|
||||
Select an album to load its tracks, then play as normal. Press `E` anywhere in the UI to switch to Emby quickly.
|
||||
|
||||
## How it works
|
||||
|
||||
cliamp authenticates with either a configured API key or the supplied username/password, resolves the active Emby user, enumerates music library views, fetches album items from those views, then fetches track items for the selected album. Playback uses Emby's authenticated download endpoint, so the existing cliamp HTTP pipeline can stream the result directly.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **Album list is flat**: no artist drill-down yet
|
||||
- **Token-based access**: store the API key carefully
|
||||
- **API key user selection**: Emby API keys are server-level (no "current user"). When no `user` is configured, cliamp picks the first user returned by `/Users`. On single-user servers this is always correct; on multi-user servers, set `user_id` explicitly in `[emby]` to target a specific account.
|
||||
@@ -0,0 +1,159 @@
|
||||
# Headless Daemon Mode
|
||||
|
||||
Run cliamp without a TUI. The daemon listens on the same Unix socket as the interactive player, so every `cliamp <subcommand>` keeps working — but nothing renders to the terminal. This is useful when you want a music player you only ever talk to over IPC: from a status bar, a script, a hotkey daemon, or a cron job.
|
||||
|
||||
```sh
|
||||
cliamp --daemon # no TUI, IPC only
|
||||
cliamp -d # short form
|
||||
cliamp --daemon --auto-play --playlist Lofi # start playing on launch
|
||||
cliamp --daemon ~/Music --auto-play # auto-play a directory
|
||||
```
|
||||
|
||||
Send `SIGINT` or `SIGTERM` to stop. Resume position is saved on graceful shutdown.
|
||||
|
||||
## What works
|
||||
|
||||
The daemon exposes the same IPC surface as the TUI. See [Remote Control](remote-control.md) for the full list:
|
||||
|
||||
- Playback: `play`, `pause`, `toggle`, `stop`, `next`, `prev`
|
||||
- Position: `seek`, `volume`, `speed`
|
||||
- Playback modes: `shuffle`, `repeat`, `mono`
|
||||
- Library: `load "Name"`, `queue /path/to.mp3`
|
||||
- Audio: `eq <preset>`, `eq --band N <dB>`, `device <name|list>`
|
||||
- Status: `status`, `status --json`
|
||||
|
||||
## What doesn't
|
||||
|
||||
UI-only commands return an error in headless mode:
|
||||
|
||||
- `theme` — no UI to apply a theme to
|
||||
- `vis` — no visualizer running
|
||||
|
||||
There is also no MPRIS / macOS NowPlaying bridge in this mode. Wire your media keys to `cliamp` subcommands directly (see [Hyprland](#hyprland) below).
|
||||
|
||||
## Use cases
|
||||
|
||||
### Background music daemon
|
||||
|
||||
Start cliamp once at login (e.g. via `~/.config/systemd/user/cliamp.service` or your DE's autostart) and leave it running. Control it from any terminal:
|
||||
|
||||
```sh
|
||||
cliamp toggle # play/pause from anywhere
|
||||
cliamp next
|
||||
cliamp volume -3
|
||||
```
|
||||
|
||||
A minimal systemd user unit:
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=cliamp headless music player
|
||||
|
||||
[Service]
|
||||
ExecStart=%h/.local/bin/cliamp --daemon --auto-play --playlist "Lofi"
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
```
|
||||
|
||||
```sh
|
||||
systemctl --user enable --now cliamp.service
|
||||
```
|
||||
|
||||
### Waybar / Polybar / i3blocks status modules
|
||||
|
||||
Poll `cliamp status --json` on an interval, render whatever fields you want.
|
||||
|
||||
**Waybar** (`~/.config/waybar/config`):
|
||||
|
||||
```jsonc
|
||||
"custom/cliamp": {
|
||||
"exec": "cliamp status --json | jq -r 'if .state == \"playing\" then \" \" + (.track.title // \"\") else \"\" end'",
|
||||
"interval": 2,
|
||||
"on-click": "cliamp toggle",
|
||||
"on-click-right": "cliamp next",
|
||||
"on-scroll-up": "cliamp volume +3",
|
||||
"on-scroll-down": "cliamp volume -3"
|
||||
}
|
||||
```
|
||||
|
||||
**Polybar**:
|
||||
|
||||
```ini
|
||||
[module/cliamp]
|
||||
type = custom/script
|
||||
exec = cliamp status --json | jq -r '.track.title // ""'
|
||||
interval = 2
|
||||
click-left = cliamp toggle
|
||||
click-right = cliamp next
|
||||
```
|
||||
|
||||
### Hotkeys (window manager / sxhkd / Hyprland)
|
||||
|
||||
Bind your media keys directly to IPC subcommands.
|
||||
|
||||
**Hyprland** (`~/.config/hypr/hyprland.conf`):
|
||||
|
||||
```ini
|
||||
bind = , XF86AudioPlay, exec, cliamp toggle
|
||||
bind = , XF86AudioNext, exec, cliamp next
|
||||
bind = , XF86AudioPrev, exec, cliamp prev
|
||||
bind = , XF86AudioRaiseVolume, exec, cliamp volume +3
|
||||
bind = , XF86AudioLowerVolume, exec, cliamp volume -3
|
||||
```
|
||||
|
||||
**sxhkd**:
|
||||
|
||||
```
|
||||
XF86AudioPlay
|
||||
cliamp toggle
|
||||
|
||||
XF86AudioNext
|
||||
cliamp next
|
||||
```
|
||||
|
||||
### Sleep / wake timers via cron
|
||||
|
||||
```cron
|
||||
# Start lofi playback at 8am on weekdays
|
||||
0 8 * * 1-5 /home/me/.local/bin/cliamp --daemon --auto-play --playlist Lofi >/dev/null 2>&1 &
|
||||
|
||||
# Stop at 6pm
|
||||
0 18 * * * pkill -TERM -f 'cliamp --daemon'
|
||||
```
|
||||
|
||||
### Scripted playlists
|
||||
|
||||
Build a queue from a script:
|
||||
|
||||
```sh
|
||||
cliamp --daemon --auto-play &
|
||||
sleep 1 # let the socket bind
|
||||
for f in $(find ~/Music/Albums/Daft\ Punk -name '*.flac' | sort); do
|
||||
cliamp queue "$f"
|
||||
done
|
||||
```
|
||||
|
||||
### Remote control over SSH
|
||||
|
||||
Since the socket lives at `~/.config/cliamp/cliamp.sock` and the CLI talks to it locally, anything that gets you a shell on the host (SSH, tmux session attach) lets you control playback:
|
||||
|
||||
```sh
|
||||
ssh kitchen-pi cliamp toggle
|
||||
ssh kitchen-pi cliamp status --json
|
||||
```
|
||||
|
||||
### Embedded / kiosk audio
|
||||
|
||||
Run on a Pi or small Linux box that has no display. The daemon needs no terminal allocation, just a working ALSA/PipeWire/PulseAudio output.
|
||||
|
||||
```sh
|
||||
cliamp --daemon --auto-play http://radio.cliamp.stream/lofi/stream
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The daemon and TUI share the same Unix socket, so only one cliamp instance can run at a time per user. Starting a second instance refuses to bind.
|
||||
- Lua plugins are not loaded in this version of headless mode. They depend on UI hooks that aren't wired up here.
|
||||
- Auto-advance has no gapless preloading in headless mode — small inter-track gaps are expected.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Recently Played
|
||||
|
||||
cliamp keeps a local listening history in `~/.config/cliamp/history.toml`. A
|
||||
play is recorded once you've listened to a track for at least 50% of its
|
||||
duration — the same threshold Last.fm and the Navidrome scrobbler use, so
|
||||
skipped tracks never enter the list.
|
||||
|
||||
## Browsing in the TUI
|
||||
|
||||
Open the **Local Playlists** provider. When at least one play has been
|
||||
recorded, a virtual `Recently Played` entry appears at the top of the list.
|
||||
Open it like any other playlist — the tracks are listed newest-first. The list
|
||||
is read-only: bookmarking, removing tracks, or deleting the playlist itself is
|
||||
rejected with a clear error.
|
||||
|
||||
To clear the list, run `cliamp history clear` (see below).
|
||||
|
||||
## CLI
|
||||
|
||||
```sh
|
||||
cliamp history # show the 50 most recent plays
|
||||
cliamp history --limit 200 # show the 200 most recent
|
||||
cliamp history --limit 0 # show all (capped at 200 entries on disk)
|
||||
cliamp history --json # machine-readable output
|
||||
cliamp history clear # wipe the history file
|
||||
```
|
||||
|
||||
The relative timestamp (`3m ago`, `yesterday`, …) is local time. The JSON
|
||||
output uses `played_at` in RFC 3339 UTC for portability.
|
||||
|
||||
## File format
|
||||
|
||||
`history.toml` uses the same minimal TOML dialect as cliamp's local playlists:
|
||||
|
||||
```toml
|
||||
[[entry]]
|
||||
played_at = "2026-05-06T22:09:11Z"
|
||||
path = "/home/me/Music/AC-DC/Highway to Hell.flac"
|
||||
title = "Highway to Hell"
|
||||
artist = "AC/DC"
|
||||
album = "Highway to Hell"
|
||||
year = 1979
|
||||
duration_secs = 208
|
||||
```
|
||||
|
||||
Entries cap at 200 by default; older plays roll off FIFO. Consecutive replays
|
||||
of the same track within 5 minutes update the existing top entry's timestamp
|
||||
rather than duplicating it.
|
||||
|
||||
## What is not recorded
|
||||
|
||||
- Tracks you skipped before the 50% threshold.
|
||||
- Live streams without a known duration (radio stations, ICY streams) — there
|
||||
is no "halfway through" to detect.
|
||||
- Tracks with empty paths (defensive guard).
|
||||
@@ -0,0 +1,67 @@
|
||||
# Jellyfin
|
||||
|
||||
cliamp can stream music directly from a Jellyfin server using Jellyfin's authenticated HTTP API. The first integration exposes your music libraries as a flat album list in the normal provider pane, following the same shape as the Plex provider.
|
||||
|
||||
> **Quick start:** run `cliamp setup` for a guided TUI that lets you pick API-token or username+password auth, validates against `/Users/Me`, and writes the `[jellyfin]` block for you. Manual setup steps are below.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A reachable Jellyfin server
|
||||
- At least one library with `CollectionType = music`
|
||||
- A Jellyfin API token
|
||||
|
||||
## Configuration
|
||||
|
||||
Add a `[jellyfin]` section to `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[jellyfin]
|
||||
url = "https://jellyfin.example.com"
|
||||
user = "finamp"
|
||||
password = "your_password_here"
|
||||
# optional alternatives:
|
||||
# token = "xxxxxxxxxxxxxxxxxxxx"
|
||||
# user_id = "00000000000000000000000000000000"
|
||||
```
|
||||
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `url` | Base URL of your Jellyfin server |
|
||||
| `user` | Jellyfin username for password-based login |
|
||||
| `password` | Jellyfin password for password-based login |
|
||||
| `token` | Optional Jellyfin API token instead of username/password |
|
||||
| `user_id` | Optional Jellyfin user id to skip discovery |
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured, **Jellyfin** appears as a provider alongside Radio, Navidrome, Plex, Spotify, and the YouTube providers.
|
||||
|
||||
To start cliamp with Jellyfin selected:
|
||||
|
||||
```bash
|
||||
cliamp --provider jellyfin
|
||||
```
|
||||
|
||||
Or set it in config:
|
||||
|
||||
```toml
|
||||
provider = "jellyfin"
|
||||
```
|
||||
|
||||
The provider currently exposes a flat list of albums:
|
||||
|
||||
```text
|
||||
Artist - Album Title (Year)
|
||||
```
|
||||
|
||||
Select an album to load its tracks, then play as normal.
|
||||
|
||||
## How it works
|
||||
|
||||
cliamp authenticates with either a configured token or the supplied username/password, resolves the active Jellyfin user, enumerates music library views, fetches album items from those views, then fetches track items for the selected album. Playback uses Jellyfin's authenticated audio endpoint, so the existing cliamp HTTP pipeline can stream the result directly.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **Album list is flat**: no artist drill-down yet
|
||||
- **No scrobbling/write-back**: plays are not reported back to Jellyfin yet
|
||||
- **Token-based access**: store the API token carefully
|
||||
@@ -0,0 +1,186 @@
|
||||
# Keybindings
|
||||
|
||||
Press `?` or `Ctrl+K` in the player to see all keybindings.
|
||||
|
||||
## Playback
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Space` | Play / Pause |
|
||||
| `s` | Stop |
|
||||
| `>` `.` | Next track |
|
||||
| `<` `,` | Previous track |
|
||||
| `Left` `Right` | Seek -/+5s |
|
||||
| `Shift+Left` `Shift+Right` | Seek -/+30s (configurable) |
|
||||
| `N` then `j` | Seek to N×10% of the track (e.g. `7j` jumps to 70%, `0j` to the start) |
|
||||
| `+` `-` | Volume up/down |
|
||||
| `]` `[` | Speed up/down (±0.25x) |
|
||||
| `m` | Toggle mono |
|
||||
| `Ctrl+J` | Jump to time |
|
||||
|
||||
## Navigation
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Tab` | Toggle focus (Playlist / EQ) |
|
||||
| `j` `k` / `Up` `Down` | Playlist scroll / EQ band adjust (wraps around) |
|
||||
| `PageUp` `PageDown` / `Ctrl+U` `Ctrl+D` | Scroll playlist/file browser by page |
|
||||
| `Home` `End` / `g` `G` | Go to top/end of playlist/file browser |
|
||||
| `Shift+Up` `Shift+Down` | Move track up/down in playlist/queue |
|
||||
| `h` `l` | EQ cursor left/right |
|
||||
| `Enter` | Play selected track |
|
||||
| `/` | Search playlist (navigate results with `↑` `↓` / `Ctrl+N` `Ctrl+P`; page scroll with `Ctrl+U` `Ctrl+D`) |
|
||||
| `Ctrl+X` | Expand/collapse playlist |
|
||||
| `o` | Open file browser |
|
||||
| `b` `Esc` | Back to provider |
|
||||
|
||||
|
||||
## EQ and Appearance
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `e` | Cycle EQ preset |
|
||||
| `t` | Choose theme |
|
||||
| `v` | Cycle visualizer |
|
||||
| `Ctrl+V` | Pick visualizer from a list (live preview) |
|
||||
| `V` | Full screen visualizer |
|
||||
| `Ctrl+H` | Toggle album headers |
|
||||
|
||||
## Features
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `f` | Toggle bookmark ★ on selected track (or favorite radio station in radio browser) |
|
||||
| `Ctrl+F` | Search — active provider's native search (Spotify, Qobuz, Navidrome, Jellyfin, Emby, Plex, NetEase, Local) or YouTube fallback. Available from playlist and provider-browser views. |
|
||||
| `u` | Load URL (stream/playlist) |
|
||||
| `y` | Show lyrics |
|
||||
| `Ctrl+S` | Save track to ~/Music |
|
||||
| `w` | Write the highlighted track to a local playlist |
|
||||
| `N` | Navidrome browser |
|
||||
| `L` | Browse local playlists (with cliamp radio) |
|
||||
| `R` | Open radio provider |
|
||||
| `S` | Open Spotify provider |
|
||||
| `P` | Open Plex provider |
|
||||
| `J` | Open Jellyfin provider |
|
||||
| `E` | Open Emby provider |
|
||||
| `Y` | Open YouTube provider |
|
||||
| `C` | Open SoundCloud provider |
|
||||
| `M` | Open NetEase provider |
|
||||
| `Q` | Open Qobuz provider |
|
||||
|
||||
## Playlist and Queue
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `a` | Toggle queue (play next) |
|
||||
| `A` | Queue manager |
|
||||
| `x` | Remove the highlighted track from the current playlist |
|
||||
| `p` | Playlist manager |
|
||||
| `r` | Cycle repeat (Off / All / One) |
|
||||
| `z` | Toggle shuffle |
|
||||
|
||||
### Inside the playlist manager
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Move cursor |
|
||||
| `/` | Filter (incremental); `Esc` clears |
|
||||
| `Enter` / `→` | List screen: open the highlighted playlist · Tracks screen: play the **highlighted** track |
|
||||
| `p` | Tracks screen: play all from the top |
|
||||
| `a` | List: add the now-playing track to the highlighted playlist. Tracks: mark/unmark all visible tracks. |
|
||||
| `w` | List: save the current queue through the playlist picker. Tracks: copy marked/highlighted tracks to another playlist. |
|
||||
| `Space` | Tracks: mark/unmark highlighted track and advance |
|
||||
| `[` `]` | Tracks: move highlighted track and save the playlist |
|
||||
| `s` | Tracks: sort and save, cycling `track`, `title`, `artist`, `album`, `artist+album`, `path` |
|
||||
| `o` | Tracks: open file browser to add files to this playlist |
|
||||
| `r` | List: rename the playlist |
|
||||
| `d` | List: delete playlist (confirms). Tracks: remove marked tracks, or highlighted track when none are marked |
|
||||
| `u` | Undo the last manager edit |
|
||||
| `←` `Backspace` `h` | Tracks screen: go back to the list |
|
||||
| `Esc` | Close the playlist manager or go back |
|
||||
|
||||
Shift-letter keys are reserved for provider switching, so playlist-manager track actions use lowercase or punctuation keys.
|
||||
|
||||
## File browser
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Move cursor |
|
||||
| `←` `→` / `h` `l` / `Enter` | Back / open directory or file |
|
||||
| `/` | Filter files |
|
||||
| `Space` | Select or unselect file/directory |
|
||||
| `a` | Select/unselect all visible audio files |
|
||||
| `R` | Replace the current queue with selected files |
|
||||
| `w` | Write selected files to a local playlist |
|
||||
| `~` `.` | Jump to home / current working directory |
|
||||
| `Esc` `o` | Close file browser |
|
||||
|
||||
## Provider browser (`N` key)
|
||||
|
||||
When you press `N` to drill into a provider (Navidrome, Plex, Jellyfin, Emby, Spotify, Qobuz, YouTube Music), the album/artist/track screens use:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Move cursor (wraps top↔bottom) |
|
||||
| `←` `→` / `h` `l` | Back / drill in |
|
||||
| `/` | Filter the visible list (search bar appears under the title) |
|
||||
| `Enter` | Open (artists/albums) · play the highlighted track and queue the rest of the visible list |
|
||||
| `R` | Replace the queue with all visible tracks (start from the top) |
|
||||
| `a` | Append all visible tracks to the queue |
|
||||
| `q` | Queue the highlighted track to play next |
|
||||
| `s` | Cycle album sort (album list only) |
|
||||
| `S` `N` `P` `J` `E` `Y` `C` `M` `Q` `L` `R` | Quick-switch to that provider without going back through the main pane |
|
||||
| `Esc` `b` | Walk back one level / close the browser |
|
||||
|
||||
The track screen shows a `N tracks · 47:22` subtitle and right-aligned per-track durations when the provider returns them.
|
||||
|
||||
## Provider playlist list
|
||||
|
||||
The playlists pane (visible when focus is on a provider — Spotify, Navidrome, Local Playlists, etc.):
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Move cursor (wraps) |
|
||||
| `Ctrl+U` `Ctrl+D` | Scroll by page |
|
||||
| `Enter` | Load the highlighted playlist's tracks into the queue |
|
||||
| `/` | Filter the playlist list |
|
||||
| `Ctrl+F` | Online/server search (Spotify/Navidrome/NetEase/etc.'s own search) |
|
||||
| `Ctrl+R` | Refresh — re-pull the playlist list from the provider |
|
||||
| `S` `N` `P` `J` `E` `Y` `C` `M` `Q` `L` `R` | Switch to that provider |
|
||||
| `Tab` | Switch focus to EQ |
|
||||
| `Esc` `b` | Back to the playlist pane |
|
||||
|
||||
Playlist rows show `Name · N tracks · 1h 23m` when the provider returns track counts and total duration. The currently loaded playlist is marked with a `▶` prefix. Spotify groups its playlists under section headers (`── library ──`, `── your playlists ──`, `── followed playlists ──`).
|
||||
|
||||
## Search results overlays
|
||||
|
||||
When `Ctrl+F` opens provider search or YouTube/SoundCloud net search and you're viewing the results list:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` / `Ctrl+N` `Ctrl+P` | Move cursor (single item) |
|
||||
| `Ctrl+U` `Ctrl+D` | Scroll results by page |
|
||||
| `Enter` | Play the selected track now |
|
||||
| `a` | Append the selected track to the playlist |
|
||||
| `q` | Queue the selected track to play next |
|
||||
| `p` | (Spotify only) Save the selected track to a Spotify playlist |
|
||||
| `Esc` `Backspace` | Back to the search input |
|
||||
|
||||
## Fuzzy search
|
||||
|
||||
The local search boxes match fuzzily: your query characters only need to appear in order, not contiguously, and results are ranked by relevance (best match first). For example, `skr` or `saku` both find a track titled "Sakura".
|
||||
|
||||
This applies to:
|
||||
|
||||
- `/` playlist search
|
||||
- `/` file browser filter
|
||||
- `Ctrl+F` when the active provider is Local (your saved playlists)
|
||||
|
||||
Other `Ctrl+F` providers (Spotify, Qobuz, Navidrome, Jellyfin, Emby, Plex, NetEase, YouTube) send your query to their own search API, so matching there follows each service's rules.
|
||||
|
||||
## General
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `?` / `Ctrl+K` | Show keymap |
|
||||
| `q` | Quit |
|
||||
@@ -0,0 +1,18 @@
|
||||
# Lyrics
|
||||
|
||||
Press `y` to show lyrics for the current track. For local files, cliamp uses embedded lyrics from the file tags first. If no embedded lyrics are present, lyrics are fetched from LRCLIB and NetEase Cloud Music.
|
||||
|
||||
## Modes
|
||||
|
||||
- **Synced lyrics**: for local files and Navidrome tracks, lyrics auto scroll and highlight the active line in time with playback.
|
||||
- **Scroll mode**: for streams and plain lyrics without timestamps, use `j`/`k` or arrow keys to scroll manually.
|
||||
|
||||
Embedded LRC lyrics keep their timestamps. Embedded plain text lyrics are shown in scroll mode.
|
||||
|
||||
## Streams
|
||||
|
||||
Lyrics auto update when the ICY metadata changes (e.g., internet radio station transitions).
|
||||
|
||||
## YouTube and SoundCloud
|
||||
|
||||
Titles like "Artist - Song (Official Video)" are parsed to build better search queries.
|
||||
@@ -0,0 +1,139 @@
|
||||
# Media Controls
|
||||
|
||||
Cliamp integrates with the operating system's media control infrastructure so that desktop environments, hardware media keys, and command line tools can control playback, read track metadata, and adjust volume without touching the TUI.
|
||||
|
||||
## Platform Support
|
||||
|
||||
| Platform | Backend | Requirements |
|
||||
|---|---|---|
|
||||
| Linux | [MPRIS2](https://specifications.freedesktop.org/mpris-spec/latest/) over D-Bus | A running D-Bus session bus (provided by most desktop environments and Wayland compositors) |
|
||||
| macOS | MPNowPlayingInfoCenter / MPRemoteCommandCenter | None (frameworks are built-in) |
|
||||
| Other | No-op stub | — |
|
||||
|
||||
## Linux (MPRIS2)
|
||||
|
||||
### Bus Name
|
||||
|
||||
Cliamp registers itself as:
|
||||
|
||||
```
|
||||
org.mpris.MediaPlayer2.cliamp
|
||||
```
|
||||
|
||||
Only one instance can hold this name at a time. If a second Cliamp process tries to start, the MPRIS registration will fail silently and that instance will run without D-Bus integration.
|
||||
|
||||
### Playback Control
|
||||
|
||||
All standard transport commands are supported through the `org.mpris.MediaPlayer2.Player` interface:
|
||||
|
||||
| playerctl command | Effect |
|
||||
|---|---|
|
||||
| `playerctl play-pause` | Toggle play / pause |
|
||||
| `playerctl play` | Resume playback |
|
||||
| `playerctl pause` | Pause playback |
|
||||
| `playerctl stop` | Stop playback |
|
||||
| `playerctl next` | Skip to the next track |
|
||||
| `playerctl previous` | Go to the previous track (or restart if more than 3 seconds in) |
|
||||
|
||||
### Seeking
|
||||
|
||||
Relative and absolute seeking are both supported:
|
||||
|
||||
```sh
|
||||
playerctl position 30 # seek to 30 seconds
|
||||
playerctl position 5+ # seek forward 5 seconds
|
||||
playerctl position 5- # seek backward 5 seconds
|
||||
```
|
||||
|
||||
Desktop widgets that display a progress bar will receive `Seeked` signals and stay in sync.
|
||||
|
||||
### Volume
|
||||
|
||||
Volume is exposed as a linear value between 0.0 and 1.0. Internally Cliamp uses a decibel scale (from -30 dB to +6 dB), and the conversion happens automatically.
|
||||
|
||||
```sh
|
||||
playerctl volume # print current volume (0.0 to 1.0)
|
||||
playerctl volume 0.5 # set volume to 50%
|
||||
```
|
||||
|
||||
Setting volume through `playerctl` updates the player immediately. Changing volume with the `+` and `-` keys in the TUI is reflected back to D-Bus clients on the next tick.
|
||||
|
||||
### Metadata
|
||||
|
||||
Track metadata is published under the standard MPRIS keys:
|
||||
|
||||
| Key | Description |
|
||||
|---|---|
|
||||
| `mpris:trackid` | D-Bus object path identifying the current track |
|
||||
| `xesam:title` | Track title |
|
||||
| `xesam:artist` | Artist name (as a list with one entry) |
|
||||
| `xesam:album` | Album name, when available |
|
||||
| `xesam:url` | File path or stream URL |
|
||||
| `mpris:artUrl` | Embedded album artwork from local files, when available |
|
||||
| `mpris:length` | Duration in microseconds |
|
||||
|
||||
Query metadata with:
|
||||
|
||||
```sh
|
||||
playerctl metadata # all keys
|
||||
playerctl metadata artist # just the artist
|
||||
playerctl metadata title # just the title
|
||||
```
|
||||
|
||||
For live radio streams that provide ICY metadata, the artist and title fields update dynamically as the station reports new track information.
|
||||
|
||||
### Status
|
||||
|
||||
```sh
|
||||
playerctl status # prints Playing, Paused, or Stopped
|
||||
```
|
||||
|
||||
### Hyprland bindings
|
||||
|
||||
Hyprland does not bind `XF86Audio*` keys by default. Add the following to your Hyprland config (typically `~/.config/hypr/bindings.conf` or `hyprland.conf`) to wire hardware media keys to Cliamp through `playerctl`:
|
||||
|
||||
```conf
|
||||
bindl = , XF86AudioPlay, exec, playerctl --player=cliamp play-pause
|
||||
bindl = , XF86AudioPause, exec, playerctl --player=cliamp play-pause
|
||||
bindl = , XF86AudioNext, exec, playerctl --player=cliamp next
|
||||
bindl = , XF86AudioPrev, exec, playerctl --player=cliamp previous
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `bindl` fires even when the session is locked, so keys continue to work under `hyprlock`.
|
||||
- `--player=cliamp` scopes the command to Cliamp only. Drop the flag to control whichever MPRIS player was most recently active (useful when Cliamp shares the session with browsers or Spotify).
|
||||
- Reload with `hyprctl reload` after editing.
|
||||
- `playerctl` must be installed (`pacman -S playerctl`, `apt install playerctl`, …).
|
||||
|
||||
## macOS
|
||||
|
||||
On macOS, Cliamp publishes now-playing information to the system's MPNowPlayingInfoCenter. This enables:
|
||||
|
||||
- Control Centre and Lock Screen media controls
|
||||
- Touch Bar playback buttons
|
||||
- Hardware media keys (play/pause, next, previous)
|
||||
- Bluetooth headphone buttons
|
||||
|
||||
Local files with embedded cover art publish that artwork to Control Centre and Lock Screen media controls. Artwork is cached by content under `~/.local/share/cliamp/album-art/` and pruned opportunistically to stay around 100 MB.
|
||||
|
||||
The macOS implementation requires the media-control runtime to pin the main goroutine to thread 0 (via `runtime.LockOSThread`) so that the Cocoa run loop can pump events. Bubbletea runs on a background goroutine instead.
|
||||
|
||||
## Architecture
|
||||
|
||||
The app-owned playback command and notifier boundary lives in `internal/playback`. The `mediactl` package translates platform APIs to and from that boundary and owns the platform-specific interactive runtime helper.
|
||||
|
||||
Platform-specific `Service` implementations:
|
||||
|
||||
- `internal/playback/*` — app-level playback commands and outbound notifier state.
|
||||
- `mediactl/service_linux.go` — connects to the session bus, claims the MPRIS bus name, translates D-Bus calls into playback commands, and publishes outbound state through MPRIS properties.
|
||||
- `mediactl/service_darwin.go` — initialises NSApplication as an accessory process, registers MPRemoteCommandCenter handlers, translates them into playback commands, and publishes now-playing state on the main-thread run loop.
|
||||
- `mediactl/service_stub.go` — no-op implementation for unsupported platforms.
|
||||
|
||||
The model publishes playback state through the playback notifier whenever state changes. On Linux, `mediactl` uses `SetMust` rather than `Set` to bypass the property library's writable checks and callback triggers, which are intended for external D-Bus writes. For writable properties like Volume, the D-Bus callback is translated into an app playback command and dispatched back into the Bubbletea event loop.
|
||||
|
||||
## Limitations
|
||||
|
||||
Shuffle and loop status are not exposed. The `z` and `r` keys in the TUI control shuffle and repeat locally, but these states are not visible to or controllable from external tools.
|
||||
|
||||
The `HasTrackList` property is set to false on Linux. Cliamp does not implement the optional `org.mpris.MediaPlayer2.TrackList` interface.
|
||||
@@ -0,0 +1,122 @@
|
||||
# Navidrome Integration
|
||||
|
||||
Cliamp can connect to a [Navidrome](https://www.navidrome.org/) server and stream music directly from your library. Navidrome is a self-hosted music server compatible with the Subsonic API.
|
||||
|
||||
> **Quick start:** run `cliamp setup` for a guided TUI that prompts for the server URL, username, and password, validates the connection, and writes the `[navidrome]` block for you. Manual setup steps are below.
|
||||
|
||||
## Setup
|
||||
|
||||
Set three environment variables before launching Cliamp:
|
||||
|
||||
```sh
|
||||
export NAVIDROME_URL="http://your-server:4533"
|
||||
export NAVIDROME_USER="your-username"
|
||||
export NAVIDROME_PASS="your-password"
|
||||
```
|
||||
|
||||
Then run Cliamp without any file arguments:
|
||||
|
||||
```sh
|
||||
cliamp
|
||||
```
|
||||
|
||||
You can also combine local files with a Navidrome session:
|
||||
|
||||
```sh
|
||||
NAVIDROME_URL=http://localhost:4533 NAVIDROME_USER=admin NAVIDROME_PASS=secret cliamp ~/Music/extra.mp3
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
When the environment variables are set, Cliamp authenticates with your Navidrome server using the Subsonic API. On launch it fetches your playlists and presents them in the TUI.
|
||||
|
||||
Browse your playlists with the arrow keys and press Enter to load one. The tracks are added to the local playlist and playback starts immediately. Audio is streamed as MP3 from the server.
|
||||
|
||||
## Controls
|
||||
|
||||
When focused on the provider panel:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Up` `Down` / `j` `k` | Navigate playlists |
|
||||
| `Enter` | Load the selected playlist |
|
||||
| `Tab` | Switch between provider and playlist focus |
|
||||
| `N` | Open the Navidrome browser |
|
||||
|
||||
After loading a playlist you return to the standard playlist view with all the usual controls (seek, volume, EQ, shuffle, repeat, queue, search).
|
||||
|
||||
## Navidrome Browser
|
||||
|
||||
Press `N` at any time (or from the provider panel) to open the full-screen Navidrome browser. It lets you explore your library in three modes:
|
||||
|
||||
- **By Album**: browse a paginated list of all albums, then open any album to see its tracks.
|
||||
- **By Artist**: browse all artists; selecting one loads every track across all their albums, grouped by album with separator headers.
|
||||
- **By Artist / Album**: three-level drill-down: artist → album list → track list.
|
||||
|
||||
### Browser controls
|
||||
|
||||
**Mode menu:**
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Navigate |
|
||||
| `Enter` | Select mode |
|
||||
| `Esc` / `N` | Close browser |
|
||||
|
||||
**Artist or album list:**
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Navigate |
|
||||
| `Enter` / `→` | Drill in |
|
||||
| `s` | Cycle album sort order (album list only) |
|
||||
| `Esc` / `←` | Back |
|
||||
|
||||
**Track list:**
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `↑` `↓` / `j` `k` | Navigate |
|
||||
| `Enter` | Append selected track to playlist |
|
||||
| `a` | Append all tracks to playlist |
|
||||
| `R` | Replace playlist with all tracks and start playing |
|
||||
| `Esc` / `←` | Back |
|
||||
|
||||
### Album sort order
|
||||
|
||||
While viewing the global album list, press `s` to cycle through sort modes:
|
||||
|
||||
| Value | Description |
|
||||
|---|---|
|
||||
| `alphabeticalByName` | A → Z by album title (default) |
|
||||
| `alphabeticalByArtist` | A → Z by artist name |
|
||||
| `newest` | Most recently added |
|
||||
| `recent` | Most recently played |
|
||||
| `frequent` | Most frequently played |
|
||||
| `starred` | Starred / favourited |
|
||||
| `byYear` | Chronological by release year |
|
||||
| `byGenre` | Grouped by genre |
|
||||
|
||||
The chosen sort is saved automatically to `~/.config/cliamp/config.toml` under the `[navidrome]` section as `browse_sort` and is restored on the next launch.
|
||||
|
||||
## Architecture
|
||||
|
||||
The integration is built around a `Provider` interface defined in the `playlist` package:
|
||||
|
||||
```go
|
||||
type Provider interface {
|
||||
Name() string
|
||||
Playlists() ([]PlaylistInfo, error)
|
||||
Tracks(playlistID string) ([]Track, error)
|
||||
}
|
||||
```
|
||||
|
||||
The Navidrome client (`external/navidrome/client.go`) implements this interface. It builds authenticated Subsonic API requests using MD5 token auth (password + random salt) and parses the JSON responses into playlist and track structs.
|
||||
|
||||
Playlist and track fetching runs asynchronously through Bubbletea commands so the UI stays responsive while the server responds.
|
||||
|
||||
Adding support for another Subsonic-compatible server (Airsonic, Gonic, etc.) would mean implementing the same `Provider` interface against that server's API.
|
||||
|
||||
## Requirements
|
||||
|
||||
No additional dependencies are needed beyond a running Navidrome instance. The client uses Go's standard `net/http` and `crypto/md5` packages. Your Navidrome server must have the Subsonic API enabled, which is the default.
|
||||
@@ -0,0 +1,63 @@
|
||||
# NetEase Cloud Music Integration
|
||||
|
||||
cliamp supports NetEase Cloud Music as an opt-in provider. It can browse your account playlists, saved playlists, liked songs, and public charts. Playback is handled by `yt-dlp`, so `yt-dlp` and `ffmpeg` must be on `PATH`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Sign in at `music.163.com` in your browser, then run:
|
||||
|
||||
```sh
|
||||
cliamp setup
|
||||
```
|
||||
|
||||
Pick **NetEase Cloud Music**, then choose the browser where you are signed in. The wizard validates the session and writes:
|
||||
|
||||
```toml
|
||||
[netease]
|
||||
enabled = true
|
||||
cookies_from = "chrome"
|
||||
user_id = "your-account-user-id"
|
||||
```
|
||||
|
||||
cliamp stores the browser name and user id only. It does not store your password or copy cookies into `config.toml`.
|
||||
|
||||
## Manual Config
|
||||
|
||||
```toml
|
||||
[netease]
|
||||
enabled = true
|
||||
cookies_from = "chrome"
|
||||
user_id = "78819429"
|
||||
```
|
||||
|
||||
`cookies_from` is passed to `yt-dlp --cookies-from-browser`. Supported names depend on your `yt-dlp` version and commonly include `chrome`, `chromium`, `firefox`, `brave`, `edge`, `opera`, `safari`, and `vivaldi`. The setup wizard has common browsers as menu choices; use **Custom browser/profile** only for profile-specific values such as `chrome:Profile 1` or `firefox:default-release`.
|
||||
|
||||
`user_id` is optional when cookies are valid. If omitted, cliamp discovers it from the signed-in account.
|
||||
|
||||
## Usage
|
||||
|
||||
Start directly on NetEase:
|
||||
|
||||
```sh
|
||||
cliamp --provider netease
|
||||
```
|
||||
|
||||
Inside the TUI:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `M` | Open NetEase provider |
|
||||
| `Ctrl+F` | Search NetEase songs while NetEase is active |
|
||||
| `Enter` | Load the highlighted playlist or play the highlighted track |
|
||||
| `Ctrl+R` | Refresh playlists |
|
||||
|
||||
Direct NetEase URLs also work:
|
||||
|
||||
```sh
|
||||
cliamp 'https://music.163.com/#/song?id=1973665667'
|
||||
cliamp 'https://music.163.com/#/playlist?id=3778678'
|
||||
```
|
||||
|
||||
## Limits
|
||||
|
||||
NetEase playback availability depends on the account, region, and track rights. If a song is unavailable upstream, cliamp surfaces the `yt-dlp` error. Using `cookies_from` gives `yt-dlp` the same account context as your browser, which improves access for tracks your account can play.
|
||||
@@ -0,0 +1,248 @@
|
||||
# Playlists
|
||||
|
||||
Cliamp supports local **TOML playlists** managed from the TUI or CLI, plus **M3U/M3U8/PLS playlists** loaded from files or URLs.
|
||||
|
||||
## M3U and PLS Playlists
|
||||
|
||||
Load any `.m3u`, `.m3u8`, or `.pls` file, local or remote:
|
||||
|
||||
```sh
|
||||
cliamp ~/radio-stations.m3u
|
||||
cliamp http://radio.example.com/streams.m3u
|
||||
cliamp ~/music.m3u https://example.com/live.m3u # mix local + remote
|
||||
cliamp ~/radio.pls
|
||||
```
|
||||
|
||||
### EXTINF Metadata
|
||||
|
||||
The parser extracts titles and durations from `#EXTINF` lines:
|
||||
|
||||
```m3u
|
||||
#EXTM3U
|
||||
#EXTINF:180,Radio Station 1
|
||||
http://station-1.com/stream
|
||||
#EXTINF:-1,Radio Station 2
|
||||
http://station-2.com/stream/hd
|
||||
```
|
||||
|
||||
Entries without `#EXTINF` still work. The filename or URL is used as the title instead.
|
||||
|
||||
### Relative Paths
|
||||
|
||||
Paths in a local M3U file are resolved relative to the M3U file's directory:
|
||||
|
||||
```m3u
|
||||
#EXTINF:240,My Song
|
||||
../Music/song.mp3
|
||||
#EXTINF:-1,Live Stream
|
||||
http://example.com/live
|
||||
```
|
||||
|
||||
If `radio.m3u` is in `~/playlists/`, then `../Music/song.mp3` resolves to `~/Music/song.mp3`.
|
||||
|
||||
### Edge Cases Handled
|
||||
|
||||
- UTF-8 BOM (common in Windows-created files)
|
||||
- `\r\n` line endings
|
||||
- Missing `#EXTM3U` header
|
||||
- Mixed local and remote entries in the same file
|
||||
- Other `#` directives (silently skipped)
|
||||
|
||||
---
|
||||
|
||||
## Local TOML Playlists
|
||||
|
||||
Create and manage your own playlists stored as `.toml` files in `~/.config/cliamp/playlists/`.
|
||||
|
||||
### File Format
|
||||
|
||||
Each playlist is a separate `.toml` file. The filename (minus extension) becomes the playlist name. Empty playlists are kept on disk so they remain visible in the TUI and CLI.
|
||||
|
||||
```toml
|
||||
# ~/.config/cliamp/playlists/radio-stations.toml
|
||||
|
||||
[[track]]
|
||||
path = "http://station-1.com/stream"
|
||||
title = "Radio Station 1"
|
||||
|
||||
[[track]]
|
||||
path = "http://station-2.com/stream/hd"
|
||||
title = "Radio Station 2"
|
||||
artist = "Radio Network"
|
||||
|
||||
[[track]]
|
||||
path = "/home/user/Music/song.mp3"
|
||||
title = "My Song"
|
||||
artist = "My Artist"
|
||||
```
|
||||
|
||||
Each `[[track]]` section supports:
|
||||
|
||||
| Key | Required | Description |
|
||||
|-----|----------|-------------|
|
||||
| `path` | Yes | File path or HTTP URL |
|
||||
| `title` | Yes | Display title |
|
||||
| `artist` | No | Artist name |
|
||||
| `album` | No | Album name |
|
||||
| `genre` | No | Genre name |
|
||||
| `year` | No | Release year |
|
||||
| `track_number` | No | Track number |
|
||||
| `duration_secs` | No | Duration in seconds |
|
||||
| `embedded_lyrics` | No | Lyrics copied from local file tags |
|
||||
| `album_art_url` | No | Cached file URL for embedded album art |
|
||||
| `bookmark` | No | Bookmark flag |
|
||||
|
||||
HTTP/HTTPS paths are automatically treated as streams.
|
||||
|
||||
### Podcast / RSS Feed Playlists
|
||||
|
||||
You can save podcast RSS feed URLs in a playlist. Add `feed = true` to mark a track as a feed. When played, the feed is resolved into individual episodes instead of being streamed directly.
|
||||
|
||||
```toml
|
||||
# ~/.config/cliamp/playlists/podcasts.toml
|
||||
|
||||
[[track]]
|
||||
path = "https://feeds.simplecast.com/54nAGcIl"
|
||||
title = "The Daily"
|
||||
feed = true
|
||||
|
||||
[[track]]
|
||||
path = "https://lexfridman.com/feed/podcast/"
|
||||
title = "Lex Fridman Podcast"
|
||||
feed = true
|
||||
```
|
||||
|
||||
Each `[[track]]` with `feed = true` supports:
|
||||
|
||||
| Key | Required | Description |
|
||||
|-----|----------|-------------|
|
||||
| `path` | Yes | RSS/Atom feed URL |
|
||||
| `title` | Yes | Display name for the feed |
|
||||
| `feed` | Yes | Must be `true` to enable feed resolution |
|
||||
|
||||
When you select a feed entry, cliamp fetches the RSS feed, extracts all episodes with audio enclosures, and loads them into the playlist. Episode titles and durations (from `<itunes:duration>`) are preserved.
|
||||
|
||||
URLs with `.xml`, `.rss`, or `.atom` extensions are also auto-detected as feeds without needing `feed = true`.
|
||||
|
||||
### Browsing and Loading Playlists
|
||||
|
||||
Running `cliamp` without arguments connects to the built-in radio channel. If Navidrome is configured, it opens the provider browser instead.
|
||||
|
||||
To browse your local playlists, press `Esc` or `b` during playback to open the provider browser. Navigate with `Up`/`Down` (or `j`/`k`) and press `Enter` to load a playlist. Tracks replace the current playlist and playback starts immediately. Press `Tab` to jump back to the now-playing playlist without reloading.
|
||||
|
||||
If Navidrome is also configured, both sources appear in the same list with provider labels (e.g., `[Navidrome] Jazz`, `[Local Playlists] favorites`).
|
||||
|
||||
You can start with CLI files and browse playlists later:
|
||||
|
||||
```sh
|
||||
cliamp song.mp3 # starts playing, Esc opens browser
|
||||
```
|
||||
|
||||
### Managing Playlists
|
||||
|
||||
Press `p` from any view to open the playlist manager:
|
||||
|
||||
1. **Browse**: see all playlists with track counts
|
||||
2. **Filter**: press `/` to incrementally filter the list (works on both the playlists screen and the track screen). `Esc` clears the filter.
|
||||
3. **Open**: press `Enter` or `→` to view tracks inside a playlist
|
||||
4. **Add now-playing**: press `a` to add the currently playing track (the footer shows the track name so you know what gets added)
|
||||
5. **Delete playlist**: press `d` then `y` to confirm deletion
|
||||
6. **Mark tracks**: open a playlist, press `Space` to mark a track and advance, or `a` to mark or unmark all visible tracks
|
||||
7. **Move tracks**: press `[` or `]`; the saved playlist is updated immediately
|
||||
8. **Sort tracks**: press `s` to cycle `track`, `title`, `artist`, `album`, `artist+album`, and `path` sorting
|
||||
9. **Remove tracks**: press `d` to remove the marked tracks, or the highlighted track when nothing is marked
|
||||
10. **Undo manager edits**: press `u` after delete, remove, move, or sort
|
||||
11. **Write tracks elsewhere**: press `w` to copy the marked or highlighted tracks to another playlist; duplicate paths are skipped
|
||||
12. **Add files**: press `o` from inside a playlist to browse files and add them to that playlist
|
||||
13. **Play this**: press `Enter` on the track list to start playback at the highlighted track. The rest of the playlist follows.
|
||||
14. **Play all**: press `p` to start from the top, regardless of cursor position
|
||||
15. **New playlist**: select "+ New Playlist...", type a name, and press Enter. If you create a playlist while a `/` filter is active, the filter text is pre-filled as the new playlist name.
|
||||
|
||||
Tracks with an `album` field are grouped by album with visual separator headers in the playlist manager (album grouping is hidden while a filter is active) and the main player view.
|
||||
|
||||
The directory `~/.config/cliamp/playlists/` is created automatically on first use. Removing the last track leaves an empty playlist file; use `d` on the playlist list or `cliamp playlist delete` to delete the playlist itself.
|
||||
|
||||
### Writing to Playlists
|
||||
|
||||
Press `w` on a track in the main playlist to open the local playlist picker. Pick an existing playlist or choose `+ New Playlist...`. Exact duplicate paths are skipped and reported.
|
||||
|
||||
In the file browser, select files with `Space`, select all visible audio files with `a`, then press `w` to write the selection to a playlist instead of loading it into the current queue.
|
||||
|
||||
### Command Line Management
|
||||
|
||||
Manage local TOML playlists without opening the TUI:
|
||||
|
||||
```sh
|
||||
cliamp playlist list
|
||||
cliamp playlist create "Name" # create an empty playlist
|
||||
cliamp playlist create "Name" file1 dir/ ... # create from files/folders
|
||||
cliamp playlist add "Name" file1 dir/ ... # append, skipping duplicate paths
|
||||
cliamp playlist rename "Old" "New"
|
||||
cliamp playlist dedupe "Name"
|
||||
cliamp playlist sort "Name" --by artist+album
|
||||
cliamp playlist doctor # report missing local files in all playlists
|
||||
cliamp playlist doctor "Name" --fix # prune missing local files
|
||||
cliamp playlist export "Name" --format m3u -o mix.m3u
|
||||
cliamp playlist import mix.pls --name "Imported"
|
||||
cliamp playlist show "Name" --json
|
||||
cliamp playlist remove "Name" --index 3
|
||||
cliamp playlist bookmark "Name" --index 3 # toggle bookmark flag
|
||||
cliamp playlist bookmarks # list all bookmarked tracks
|
||||
cliamp playlist enrich "Name" # backfill duration/album metadata
|
||||
cliamp playlist delete "Name"
|
||||
```
|
||||
|
||||
Sort keys are `track`, `title`, `artist`, `album`, `artist+album`, and `path`.
|
||||
|
||||
New playlist names reject path separators and non-portable filename characters. Existing playlist files with older Unix-only names remain readable and writable.
|
||||
|
||||
### Creating Playlists Manually
|
||||
|
||||
Create the directory and add a `.toml` file:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/cliamp/playlists
|
||||
```
|
||||
|
||||
```toml
|
||||
# ~/.config/cliamp/playlists/favorites.toml
|
||||
|
||||
[[track]]
|
||||
path = "/home/user/Music/song.mp3"
|
||||
title = "Great Song"
|
||||
artist = "Good Artist"
|
||||
|
||||
[[track]]
|
||||
path = "https://radio.example.com/stream"
|
||||
title = "My Radio"
|
||||
```
|
||||
|
||||
### Controls
|
||||
|
||||
**Playlist browser (provider view):**
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `Up` `Down` / `j` `k` | Navigate playlists |
|
||||
| `Enter` | Load selected playlist |
|
||||
| `Tab` | Switch to now-playing playlist |
|
||||
| `Esc` `b` | Open browser (from playlist view) |
|
||||
|
||||
**Playlist manager (`p` key):**
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `p` / `Esc` | Open/close playlist manager (Esc on tracks screen goes back) |
|
||||
| `Up` `Down` / `j` `k` | Navigate |
|
||||
| `/` | Filter playlists or tracks; `Esc` clears |
|
||||
| `Enter` / `→` | Open playlist (list screen) / Play **highlighted** track (tracks screen) |
|
||||
| `p` | Play all tracks from the top (tracks screen) |
|
||||
| `a` | List: add currently playing track. Tracks: mark/unmark all visible tracks |
|
||||
| `Space` | Mark/unmark track and advance (tracks screen) |
|
||||
| `s` | Sort tracks, cycling supported sort keys (tracks screen) |
|
||||
| `w` | Write marked/highlighted tracks, or the current queue from the list screen, to another playlist |
|
||||
| `o` | Add files to the open playlist (tracks screen) |
|
||||
| `[` `]` | Move track up/down and save (tracks screen) |
|
||||
| `d` | Delete playlist (confirms) / Remove marked tracks, or highlighted track if none are marked |
|
||||
| `u` | Undo the last playlist-manager edit |
|
||||
| `←` / `Backspace` | Go back from tracks screen to list |
|
||||
@@ -0,0 +1,81 @@
|
||||
# Plex Media Server
|
||||
|
||||
cliamp can stream music directly from your Plex Media Server, giving you access to your full Plex music library, including any library served by PlexAmp. Streaming uses the same Plex HTTP API that official Plex clients use; no extra software is required.
|
||||
|
||||
> **Quick start:** run `cliamp setup` for a guided TUI that prompts for the server URL and `X-Plex-Token`, pings the server to verify the token, and writes the `[plex]` block for you. Manual setup steps are below.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Plex Media Server running and reachable on your network (or remotely)
|
||||
- At least one music library configured in Plex
|
||||
- Your `X-Plex-Token` (see below)
|
||||
|
||||
## Finding your X-Plex-Token
|
||||
|
||||
1. Open Plex Web in a browser and sign in
|
||||
2. Browse to any item in your music library
|
||||
3. Click the **···** menu → **Get Info** → **View XML**
|
||||
4. In the URL of the XML page, copy the value of the `X-Plex-Token` query parameter
|
||||
|
||||
Alternatively, follow the [official Plex guide](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/).
|
||||
|
||||
## Configuration
|
||||
|
||||
Add a `[plex]` section to `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[plex]
|
||||
url = "http://192.168.1.10:32400"
|
||||
token = "xxxxxxxxxxxxxxxxxxxx"
|
||||
libraries = ["Music", "Jazz"]
|
||||
```
|
||||
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `url` | Base URL of your Plex Media Server, including port (default `32400`) |
|
||||
| `token` | Your `X-Plex-Token` for authentication |
|
||||
| `libraries` | Optional comma-separated list of music library names to load. When omitted, all music libraries are loaded. Names are matched case-insensitively. |
|
||||
|
||||
If you access Plex remotely via `app.plex.tv`, you can still use a direct server URL if your server has remote access enabled, or use your server's `plex.direct` URL from the Plex Web address bar.
|
||||
|
||||
## Usage
|
||||
|
||||
Once configured, **Plex** appears as a provider in the cliamp TUI alongside Radio, Navidrome, Spotify, etc.
|
||||
|
||||
The provider exposes your music library as a flat list of albums, labelled:
|
||||
|
||||
```
|
||||
Artist - Album Title (Year)
|
||||
```
|
||||
|
||||
Select an album to load its tracks, then play as normal.
|
||||
|
||||
To start cliamp with Plex as the default provider:
|
||||
|
||||
```bash
|
||||
cliamp --provider plex
|
||||
```
|
||||
|
||||
Or set it persistently in config:
|
||||
|
||||
```toml
|
||||
provider = "plex"
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
cliamp calls the Plex HTTP API to enumerate your music libraries and albums. When you select an album, it fetches the track list and constructs authenticated streaming URLs of the form:
|
||||
|
||||
```
|
||||
http://<server>:32400/library/parts/<partID>/<timestamp>/file.<ext>?X-Plex-Token=<token>
|
||||
```
|
||||
|
||||
These are direct file-serve URLs. Plex serves the original file without transcoding, and cliamp's existing HTTP streaming pipeline handles playback. All formats supported by cliamp (MP3, FLAC, AAC, OGG, OPUS, WAV, etc.) work as long as the original file format is one of them.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **No scrobbling**: play counts are not reported back to Plex
|
||||
- **No playlist write-back**: cliamp cannot create or modify Plex playlists
|
||||
- **Token is long-lived**: store it carefully; it grants full access to your Plex account
|
||||
- **Album list is flat**: no artist drill-down; search by scrolling or using cliamp's search
|
||||
- **No Plex playlists**: only library albums are exposed (Plex user-created playlists are not yet surfaced)
|
||||
+601
@@ -0,0 +1,601 @@
|
||||
# Lua Plugins
|
||||
|
||||
cliamp has a Lua 5.1 plugin system. Plugins can hook into playback events (scrobbling, notifications, status bar output) and add custom visualizers. Each plugin runs in an isolated VM. A crash in one plugin cannot affect others or the player.
|
||||
|
||||
Plugins live in `~/.config/cliamp/plugins/`. Create the directory:
|
||||
|
||||
```
|
||||
mkdir -p ~/.config/cliamp/plugins
|
||||
```
|
||||
|
||||
Plugins run only after their exact contents have been approved. Existing and manually copied plugins start untrusted; approve one with `cliamp plugins trust <name>`.
|
||||
|
||||
## Plugin manager
|
||||
|
||||
```sh
|
||||
cliamp plugins # show help
|
||||
cliamp plugins list # list installed plugins
|
||||
cliamp plugins install <source> # install a plugin
|
||||
cliamp plugins trust <name> # approve installed plugin contents
|
||||
cliamp plugins remove <name> # remove a plugin
|
||||
```
|
||||
|
||||
Install and trust display the source, SHA-256, declared permissions, and implicit filesystem/network access before prompting. Use `--yes` only after independently reviewing the same content in non-interactive environments. Approvals are stored in `plugins/.trust.json`; editing a plugin changes its hash and disables it until it is approved again. Unknown permission names are rejected.
|
||||
|
||||
### Install sources
|
||||
|
||||
| Format | Example |
|
||||
|--------|---------|
|
||||
| GitHub | `user/repo` |
|
||||
| GitHub with tag | `user/repo@v1.0` |
|
||||
| GitLab | `gitlab:user/repo` |
|
||||
| GitLab with tag | `gitlab:user/repo@v1.0` |
|
||||
| Codeberg | `codeberg:user/repo` |
|
||||
| Codeberg with tag | `codeberg:user/repo@v1.0` |
|
||||
| Direct URL | `https://example.com/plugin.lua` |
|
||||
|
||||
### Naming convention
|
||||
|
||||
Plugin repositories **must** be named `cliamp-plugin-<name>` with the entry point `<name>.lua` at the repo root. The `cliamp-plugin-` prefix is stripped on install, so `cliamp-plugin-soap-bubbles` (containing `soap-bubbles.lua`) installs as `soap-bubbles`.
|
||||
|
||||
```sh
|
||||
cliamp plugins install bjarneo/cliamp-plugin-lastfm
|
||||
cliamp plugins install bjarneo/cliamp-plugin-lastfm@v1.0
|
||||
cliamp plugins install gitlab:user/my-visualizer
|
||||
cliamp plugins install codeberg:user/my-plugin
|
||||
cliamp plugins install https://example.com/my-plugin.lua
|
||||
cliamp plugins remove lastfm
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
### Now-playing file (for Waybar, Polybar, etc.)
|
||||
|
||||
```lua
|
||||
-- ~/.config/cliamp/plugins/now-playing.lua
|
||||
local p = plugin.register({
|
||||
name = "now-playing",
|
||||
type = "hook",
|
||||
description = "Write now-playing to /tmp for status bars",
|
||||
})
|
||||
|
||||
p:on("track.change", function(track)
|
||||
cliamp.fs.write("/tmp/cliamp-now-playing", track.artist .. " - " .. track.title)
|
||||
end)
|
||||
|
||||
p:on("playback.state", function(ev)
|
||||
if ev.status == "paused" then
|
||||
cliamp.fs.write("/tmp/cliamp-now-playing", "paused")
|
||||
end
|
||||
end)
|
||||
|
||||
p:on("app.quit", function()
|
||||
cliamp.fs.remove("/tmp/cliamp-now-playing")
|
||||
end)
|
||||
```
|
||||
|
||||
### Desktop notification on track change
|
||||
|
||||
```lua
|
||||
-- ~/.config/cliamp/plugins/notify.lua
|
||||
local p = plugin.register({
|
||||
name = "notify",
|
||||
type = "hook",
|
||||
})
|
||||
|
||||
p:on("track.change", function(track)
|
||||
local title = track.artist .. " - " .. track.title
|
||||
os.execute('notify-send "cliamp" "' .. title .. '"')
|
||||
end)
|
||||
```
|
||||
|
||||
Note: `os.execute` is removed by the sandbox. Public HTTP endpoints are available through `cliamp.http`; private, loopback, link-local, multicast, and unspecified addresses are blocked. For local automation, write to an allowlisted file that a watcher picks up or declare the permission-gated `exec` capability.
|
||||
|
||||
### Webhook
|
||||
|
||||
```lua
|
||||
-- ~/.config/cliamp/plugins/webhook.lua
|
||||
local p = plugin.register({
|
||||
name = "webhook",
|
||||
type = "hook",
|
||||
})
|
||||
|
||||
local url = p:config("url")
|
||||
|
||||
p:on("track.change", function(track)
|
||||
if not url then return end
|
||||
cliamp.http.post(url, {
|
||||
json = { title = track.title, artist = track.artist, album = track.album }
|
||||
})
|
||||
end)
|
||||
```
|
||||
|
||||
```toml
|
||||
# config.toml
|
||||
[plugins.webhook]
|
||||
url = "https://example.com/hook"
|
||||
```
|
||||
|
||||
## Plugin structure
|
||||
|
||||
### Single file
|
||||
|
||||
```
|
||||
~/.config/cliamp/plugins/myplugin.lua
|
||||
```
|
||||
|
||||
### Directory with init.lua
|
||||
|
||||
```
|
||||
~/.config/cliamp/plugins/myplugin/
|
||||
init.lua
|
||||
helpers.lua
|
||||
```
|
||||
|
||||
The directory name becomes the plugin name. Only `init.lua` is loaded automatically.
|
||||
|
||||
## Registration
|
||||
|
||||
Every plugin must call `plugin.register()` to be recognized. Files that don't call it are silently skipped.
|
||||
|
||||
```lua
|
||||
local p = plugin.register({
|
||||
name = "myplugin", -- required
|
||||
type = "hook", -- "hook" or "visualizer"
|
||||
version = "1.0.0", -- optional
|
||||
description = "What it does", -- optional
|
||||
})
|
||||
```
|
||||
|
||||
The returned object `p` provides two methods:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `p:on(event, callback)` | Subscribe to a playback event |
|
||||
| `p:config(key)` | Read a config value from `[plugins.myplugin]` in config.toml |
|
||||
|
||||
## Events
|
||||
|
||||
Plugins subscribe to events with `p:on(event, callback)`. Callbacks run asynchronously in goroutines and have a 5-second timeout.
|
||||
|
||||
### Available events
|
||||
|
||||
| Event | Callback argument | When |
|
||||
|-------|-------------------|------|
|
||||
| `track.change` | `{title, artist, album, genre, year, path, duration, stream}` | New track starts |
|
||||
| `track.scrobble` | Same + `{played_secs}` | Track played >= 50% or >= 4 min |
|
||||
| `playback.state` | `{status, title, artist, album, path, duration, stream, position}` | Any playback state change (play, pause, stop, seek, volume, track transition) |
|
||||
| `player.seek` | `{position, duration}` (seconds) | A seek completes |
|
||||
| `player.volume` | `{db}` | Volume changes |
|
||||
| `player.eq` | `{bands, preset}` | An EQ band or preset changes |
|
||||
| `player.mode` | `{shuffle, repeat}` | Shuffle toggled or repeat mode cycled |
|
||||
| `queue.change` | `{count, index, queued}` | Playlist or play-next queue changes |
|
||||
| `app.start` | `{}` | After all plugins loaded |
|
||||
| `app.quit` | `{}` | Before shutdown |
|
||||
|
||||
The `status` field in `playback.state` is one of: `"playing"`, `"paused"`, `"stopped"`. The `repeat` field in `player.mode` is one of: `"Off"`, `"All"`, `"One"` (matching `cliamp.player.repeat_mode()`). In `player.eq`, `bands` is an array of 10 dB values.
|
||||
|
||||
The `player.*` and `queue.change` events are fired by diffing state after each UI update, so they cover every change regardless of source (keypress, IPC, MPRIS, or another plugin).
|
||||
|
||||
## Plugin object methods
|
||||
|
||||
The object returned by `plugin.register(...)` exposes additional methods beyond `:on()` / `:config()`:
|
||||
|
||||
### `p:bind(key, [description,] callback)` — keyboard binding (requires `permissions = {"keymap"}`)
|
||||
|
||||
```lua
|
||||
local p = plugin.register({
|
||||
name = "my-plugin",
|
||||
type = "hook",
|
||||
permissions = {"keymap"},
|
||||
})
|
||||
|
||||
-- Listed in the Ctrl+K overlay under "— plugins —":
|
||||
p:bind("x", "Extract chapters", function(key) ... end)
|
||||
|
||||
-- Not listed (hidden binding):
|
||||
p:bind("ctrl+e", function(key) ... end)
|
||||
```
|
||||
|
||||
Returns `true` on success, or `false, reason` if the key is already owned by cliamp's core UI or the plugin lacks the `keymap` permission. Pass a description string as the middle argument to surface the binding in the `Ctrl+K` keymap overlay; omit it for an internal-only binding.
|
||||
|
||||
Key strings are in Bubbletea's `msg.String()` form: lowercase letters, `ctrl+` / `shift+` / `alt+` prefixes (e.g. `"x"`, `"ctrl+e"`, `"shift+f1"`). Case-insensitive.
|
||||
|
||||
Plugin keys only fire in the main view — overlays like the file browser, theme picker, and keymap itself capture their own input. Core reserves all keys documented in `docs/keybindings.md`; trying to bind one of those logs a warning and returns `false`.
|
||||
|
||||
Use `p:unbind(key)` to release a binding.
|
||||
|
||||
### `p:command(name, callback)` — shell-invokable command
|
||||
|
||||
```lua
|
||||
p:command("run", function(args)
|
||||
-- args is an array of strings passed after the command name
|
||||
return "done: " .. args[1]
|
||||
end)
|
||||
```
|
||||
|
||||
The callback can return a string, which is printed by the CLI client. Commands are invoked from the shell via `cliamp plugins call <plugin-name> <command> [args...]` and dispatched to the running cliamp over IPC. Since dispatch runs in the running player, commands don't need a separate permission (they're user-initiated).
|
||||
|
||||
List all registered commands with `cliamp plugins commands`. Commands can run for up to 5 minutes before timing out.
|
||||
|
||||
## Lua API
|
||||
|
||||
All APIs are under the `cliamp` global table.
|
||||
|
||||
### cliamp.player (read-only)
|
||||
|
||||
```lua
|
||||
cliamp.player.state() --> "playing" | "paused" | "stopped"
|
||||
cliamp.player.position() --> number (seconds)
|
||||
cliamp.player.duration() --> number (seconds)
|
||||
cliamp.player.volume() --> number (dB, -30 to +6)
|
||||
cliamp.player.speed() --> number (ratio, 1.0 = normal)
|
||||
cliamp.player.mono() --> boolean
|
||||
cliamp.player.repeat_mode() --> "Off" | "All" | "One"
|
||||
cliamp.player.shuffle() --> boolean
|
||||
cliamp.player.eq_bands() --> table of 10 dB values
|
||||
```
|
||||
|
||||
### cliamp.track (read-only)
|
||||
|
||||
```lua
|
||||
cliamp.track.title() --> string
|
||||
cliamp.track.artist() --> string
|
||||
cliamp.track.album() --> string
|
||||
cliamp.track.genre() --> string
|
||||
cliamp.track.year() --> number
|
||||
cliamp.track.track_number() --> number
|
||||
cliamp.track.path() --> string
|
||||
cliamp.track.is_stream() --> boolean
|
||||
cliamp.track.duration_secs() --> number
|
||||
```
|
||||
|
||||
### cliamp.queue
|
||||
|
||||
Read the playlist freely; mutating it requires `permissions = {"control"}`. All
|
||||
indices are 0-based, matching `cliamp.queue.current()`.
|
||||
|
||||
```lua
|
||||
-- read (no permission)
|
||||
cliamp.queue.list() --> array of {title, artist, album, path, index, queued}
|
||||
cliamp.queue.count() --> number of tracks
|
||||
cliamp.queue.current() --> 0-based index of the current track
|
||||
|
||||
-- mutate (requires "control")
|
||||
cliamp.queue.add(path) -- resolve a file/dir/URL and append
|
||||
cliamp.queue.jump(index) -- make index current and play it
|
||||
cliamp.queue.remove(index) -- remove the track at index
|
||||
cliamp.queue.move(from, to) -- reorder a track
|
||||
```
|
||||
|
||||
`add` accepts anything the CLI accepts: a local file or directory, an HTTP
|
||||
stream, an M3U/PLS URL, or a YouTube/yt-dlp URL. Resolution happens off the UI
|
||||
thread, so a slow URL never blocks playback.
|
||||
|
||||
### cliamp.http
|
||||
|
||||
```lua
|
||||
-- GET
|
||||
local body, status = cliamp.http.get("https://api.example.com/data", {
|
||||
headers = { Authorization = "Bearer token" }
|
||||
})
|
||||
|
||||
-- POST with JSON
|
||||
local body, status = cliamp.http.post("https://api.example.com/scrobble", {
|
||||
json = { artist = "Radiohead", track = "Everything In Its Right Place" }
|
||||
})
|
||||
|
||||
-- POST with form body
|
||||
local body, status = cliamp.http.post(url, {
|
||||
headers = { ["Content-Type"] = "application/x-www-form-urlencoded" },
|
||||
body = "key=value&foo=bar"
|
||||
})
|
||||
```
|
||||
|
||||
Restrictions: 5-second timeout, 1 MB response body cap.
|
||||
|
||||
### cliamp.fs
|
||||
|
||||
```lua
|
||||
cliamp.fs.write(path, content) -- overwrite file
|
||||
cliamp.fs.append(path, content) -- append to file
|
||||
cliamp.fs.read(path) --> string (max 1 MB)
|
||||
cliamp.fs.remove(path) -- delete file
|
||||
cliamp.fs.exists(path) --> boolean
|
||||
cliamp.fs.mkdir(path) -- create directory (recursive)
|
||||
cliamp.fs.listdir(path) --> {names}, err
|
||||
```
|
||||
|
||||
Writes are restricted to the system temp directory (`/tmp/` on Unix), `~/.config/cliamp/`, `~/.local/share/cliamp/`, and `~/Music/cliamp/`. Reads are allowed from anywhere. On Windows, if `HOME` is unset, the config directory portion resolves to `%APPDATA%\cliamp`.
|
||||
|
||||
### cliamp.json
|
||||
|
||||
```lua
|
||||
local tbl = cliamp.json.decode('{"key": "value"}')
|
||||
local str = cliamp.json.encode({ key = "value" })
|
||||
```
|
||||
|
||||
### cliamp.store
|
||||
|
||||
A persistent per-plugin key/value store. Values (strings, numbers, booleans,
|
||||
and tables) survive restarts. No permission required: each plugin sees only its
|
||||
own namespace, so one plugin can never read another's keys.
|
||||
|
||||
```lua
|
||||
cliamp.store.set(key, value) -- value: string|number|boolean|table
|
||||
cliamp.store.get(key) --> value or nil
|
||||
cliamp.store.delete(key)
|
||||
cliamp.store.keys() --> sorted array of keys
|
||||
cliamp.store.clear()
|
||||
```
|
||||
|
||||
Backed by `~/.local/share/cliamp/plugins/<name>/store.json`, written owner-only
|
||||
(0600). Use it for play counts, offline scrobble queues, resume positions, or
|
||||
remembered settings, not for large data.
|
||||
|
||||
```lua
|
||||
local counts = cliamp.store.get("counts") or {}
|
||||
counts[cliamp.track.path()] = (counts[cliamp.track.path()] or 0) + 1
|
||||
cliamp.store.set("counts", counts)
|
||||
```
|
||||
|
||||
### cliamp.crypto
|
||||
|
||||
```lua
|
||||
cliamp.crypto.md5("hello") --> hex string
|
||||
cliamp.crypto.sha256("hello") --> hex string
|
||||
cliamp.crypto.hmac_sha256("secret", "msg") --> hex string
|
||||
```
|
||||
|
||||
### cliamp.log
|
||||
|
||||
```lua
|
||||
cliamp.log.info("loaded successfully")
|
||||
cliamp.log.warn("missing config key")
|
||||
cliamp.log.error("request failed: " .. err)
|
||||
cliamp.log.debug("response: " .. body)
|
||||
```
|
||||
|
||||
Logs are written to `~/.config/cliamp/plugins.log` with timestamps and `[plugin-name]` prefix.
|
||||
|
||||
### cliamp.player control (requires permissions)
|
||||
|
||||
Plugins that declare `permissions = {"control"}` can send commands to the player:
|
||||
|
||||
```lua
|
||||
local p = plugin.register({
|
||||
name = "my-controller",
|
||||
type = "hook",
|
||||
permissions = {"control"},
|
||||
})
|
||||
|
||||
cliamp.player.next() -- skip to next track
|
||||
cliamp.player.prev() -- go to previous track
|
||||
cliamp.player.play_pause() -- toggle play/pause
|
||||
cliamp.player.stop() -- stop playback
|
||||
cliamp.player.set_volume(-5) -- set volume in dB (-30 to +6)
|
||||
cliamp.player.set_speed(1.25) -- set playback speed (0.25 to 2.0)
|
||||
cliamp.player.seek(30) -- seek to 30 seconds
|
||||
cliamp.player.toggle_mono() -- toggle mono output
|
||||
cliamp.player.set_eq_preset("Rock") -- switch to built-in preset (sets bands + UI label)
|
||||
cliamp.player.set_eq_preset("Metal", {6,4,1,-1,-2,2,4,6,6,5}) -- custom preset with bands
|
||||
cliamp.player.set_eq_band(1, 6) -- set EQ band 1 to +6 dB (bands 1-10, -12 to +12)
|
||||
```
|
||||
|
||||
Without `permissions = {"control"}`, these functions log a warning and do nothing.
|
||||
|
||||
### cliamp.notify
|
||||
|
||||
```lua
|
||||
cliamp.notify("Song Title") -- notification with title only
|
||||
cliamp.notify("Song Title", "Artist Name") -- notification with title and body
|
||||
```
|
||||
|
||||
Sends a desktop notification via `notify-send`. Works with mako, dunst, and other notification daemons.
|
||||
|
||||
### cliamp.exec (requires permissions)
|
||||
|
||||
Plugins that declare `permissions = {"exec"}` can spawn subprocesses from a configurable binary allowlist. Default allowlist: `yt-dlp`, `ffmpeg`. Extend it in `config.toml`:
|
||||
|
||||
```toml
|
||||
[plugins]
|
||||
allowed_binaries = "ffprobe, curl" # merged with defaults
|
||||
```
|
||||
|
||||
```lua
|
||||
local p = plugin.register({
|
||||
name = "my-downloader",
|
||||
type = "hook",
|
||||
permissions = {"exec"},
|
||||
})
|
||||
|
||||
local handle, err = cliamp.exec.run("yt-dlp", {"--dump-json", url}, {
|
||||
on_stdout = function(line) ... end, -- optional, called per line
|
||||
on_stderr = function(line) ... end, -- optional
|
||||
on_exit = function(code) ... end, -- optional, fires exactly once
|
||||
cwd = "/tmp/work", -- optional; must be in write allowlist
|
||||
timeout = 300, -- optional seconds, hard cap 1800
|
||||
})
|
||||
|
||||
handle:cancel() -- terminate the process
|
||||
handle:alive() -- --> boolean
|
||||
```
|
||||
|
||||
**Safety rails:**
|
||||
|
||||
- Binary must be in the allowlist. Argv is argv — no shell, no expansion.
|
||||
- `args` must be a flat array of strings. Nested tables / non-strings are rejected.
|
||||
- Subprocess env is minimal (`PATH`, `HOME`, `LANG`) — secrets in the parent env are not passed through.
|
||||
- Output is capped at 4 MiB per process (stdout + stderr combined); further lines are dropped silently.
|
||||
- Concurrency capped at 4 running processes per plugin.
|
||||
- All processes owned by a plugin are killed on plugin unload and on cliamp exit.
|
||||
- Negative `on_exit` codes signal cancellation/timeout (`-1`) or spawn failure (`-2`).
|
||||
|
||||
Without `permissions = {"exec"}`, `cliamp.exec.run` returns `nil, "exec permission required"`.
|
||||
|
||||
### cliamp.message
|
||||
|
||||
```lua
|
||||
cliamp.message("Scrobble Sent") -- show for default duration
|
||||
cliamp.message("Syncing Library", 5) -- show for 5 seconds
|
||||
```
|
||||
|
||||
Displays a transient message in the status bar at the bottom of the UI. The
|
||||
duration argument is optional (seconds); omit it to use the default TTL. Durations above 60 seconds are clamped.
|
||||
|
||||
### cliamp.sleep
|
||||
|
||||
```lua
|
||||
cliamp.sleep(2.5) -- block for 2.5 seconds (max 10)
|
||||
```
|
||||
|
||||
Blocks the plugin's Lua VM. Other hooks for the same plugin will queue until the sleep finishes. Prefer `cliamp.timer.after()` for non-blocking delays.
|
||||
|
||||
### cliamp.timer
|
||||
|
||||
```lua
|
||||
-- Run once after 5 seconds
|
||||
local id = cliamp.timer.after(5.0, function()
|
||||
cliamp.log.info("timer fired")
|
||||
end)
|
||||
|
||||
-- Run every 30 seconds
|
||||
local id = cliamp.timer.every(30.0, function()
|
||||
-- periodic task
|
||||
end)
|
||||
|
||||
-- Cancel
|
||||
cliamp.timer.cancel(id)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Plugin-specific config goes in `config.toml` under `[plugins.<name>]`:
|
||||
|
||||
```toml
|
||||
[plugins.lastfm]
|
||||
api_key = "abc123"
|
||||
api_secret = "secret"
|
||||
session_key = "sk-xxx"
|
||||
|
||||
[plugins.webhook]
|
||||
url = "https://example.com/hook"
|
||||
```
|
||||
|
||||
Access in Lua:
|
||||
|
||||
```lua
|
||||
local api_key = p:config("api_key") --> "abc123" or nil
|
||||
```
|
||||
|
||||
### Disabling plugins
|
||||
|
||||
Disable a specific plugin:
|
||||
|
||||
```toml
|
||||
[plugins.webhook]
|
||||
enabled = false
|
||||
```
|
||||
|
||||
Or disable multiple at once:
|
||||
|
||||
```toml
|
||||
[plugins]
|
||||
disabled = webhook, discord-rpc
|
||||
```
|
||||
|
||||
## Visualizer plugins
|
||||
|
||||
Plugins with `type = "visualizer"` add custom visualizer modes that appear in the `v` key cycle alongside built-in modes.
|
||||
|
||||
```lua
|
||||
-- ~/.config/cliamp/plugins/simple-bars.lua
|
||||
local p = plugin.register({
|
||||
name = "simple-bars",
|
||||
type = "visualizer",
|
||||
})
|
||||
|
||||
-- Called every frame (~20 FPS during playback).
|
||||
-- bands: table of 10 numbers (0.0-1.0), indices 1-10
|
||||
-- frame: monotonic counter
|
||||
-- rows: available terminal rows (changes in fullscreen mode)
|
||||
-- cols: available terminal columns
|
||||
-- Must return a multi-line string.
|
||||
function p:render(bands, frame, rows, cols)
|
||||
local lines = {}
|
||||
local chars = { " ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" }
|
||||
|
||||
for row = 5, 1, -1 do
|
||||
local line = ""
|
||||
for i = 1, 10 do
|
||||
local level = bands[i]
|
||||
local threshold = (row - 1) / 5
|
||||
if level > threshold then
|
||||
line = line .. "██████ "
|
||||
else
|
||||
line = line .. " "
|
||||
end
|
||||
end
|
||||
table.insert(lines, line)
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
end
|
||||
```
|
||||
|
||||
### Visualizer callbacks
|
||||
|
||||
| Callback | Signature | Required |
|
||||
|----------|-----------|----------|
|
||||
| `p:render(bands, frame, rows, cols)` | Returns string | Yes |
|
||||
| `p:init(rows, cols)` | Setup when selected | No |
|
||||
| `p:destroy()` | Cleanup when deselected | No |
|
||||
|
||||
Render has a 10 ms budget per frame. If it exceeds this, the previous frame is reused to prevent UI jank.
|
||||
|
||||
## Sandbox
|
||||
|
||||
For security, plugins run with restricted access. The sandbox removes dangerous standard library functions and restricts file system access.
|
||||
|
||||
### Removed functions
|
||||
|
||||
| Removed | Replacement |
|
||||
|---------|-------------|
|
||||
| `os.execute`, `os.remove`, `os.rename`, `os.exit`, `os.setlocale`, `os.tmpname` | Use `cliamp.fs`, `cliamp.http`, or permission-gated `cliamp.exec` |
|
||||
| `io` module (all of it) | Use `cliamp.fs` |
|
||||
| `dofile`, `loadfile`, `load`, `loadstring`, `require`, `module`, `package`, `debug` | Not available |
|
||||
|
||||
### Kept functions
|
||||
|
||||
`os.time()`, `os.date()`, `os.clock()`, `os.getenv()` are available.
|
||||
|
||||
### File system restrictions
|
||||
|
||||
**Reads:** Allowed from any path (max 1 MB per read).
|
||||
|
||||
**Writes/removes/mkdir** are restricted to these directories only:
|
||||
|
||||
- `/tmp/` (and the system temp directory)
|
||||
- `~/.config/cliamp/`
|
||||
- `~/.local/share/cliamp/`
|
||||
- `~/Music/cliamp/`
|
||||
|
||||
Attempts to write outside these directories will raise a Lua error. Directory traversal (`..`) is blocked.
|
||||
|
||||
### Isolation
|
||||
|
||||
- Each plugin runs in its own Lua VM. Plugins cannot access each other's state or variables.
|
||||
- A crash in one plugin does not affect other plugins or the player.
|
||||
- Public network access is available via `cliamp.http` (no raw socket access). Private, loopback, link-local, multicast, and unspecified destinations are blocked after DNS resolution and across redirects.
|
||||
- `os.execute` is removed. Permission-gated `cliamp.exec` can spawn only configured allowlisted binaries.
|
||||
|
||||
## Debugging
|
||||
|
||||
Check `~/.config/cliamp/plugins.log` for plugin output and errors:
|
||||
|
||||
```
|
||||
2025-03-29 14:30:01 [now-playing] info: Now playing: Everything In Its Right Place
|
||||
2025-03-29 14:30:01 [webhook] error: track.change handler error: connection refused
|
||||
```
|
||||
|
||||
Use `cliamp.log.debug()` liberally during development.
|
||||
@@ -0,0 +1,194 @@
|
||||
# Creating a Provider
|
||||
|
||||
Providers live in `external/<name>/` (e.g. `external/jellyfin/`). A provider is
|
||||
a Go package that implements the base `playlist.Provider` interface and
|
||||
optionally implements capability interfaces from the `provider/` package. The UI
|
||||
discovers capabilities at runtime via type assertions and enables features
|
||||
accordingly.
|
||||
|
||||
See the existing providers for reference:
|
||||
- `external/navidrome/`: Subsonic API, browsing, scrobbling
|
||||
- `external/plex/`: Plex Media Server, search, album tracks
|
||||
- `external/spotify/`: Spotify, search, playlist management, custom streaming
|
||||
- `external/radio/`: internet radio, favorites
|
||||
- `external/local/`: local TOML playlist files
|
||||
|
||||
## Base Interface (required)
|
||||
|
||||
Every provider must implement `playlist.Provider`:
|
||||
|
||||
```go
|
||||
type Provider interface {
|
||||
Name() string
|
||||
Playlists() ([]playlist.PlaylistInfo, error)
|
||||
Tracks(playlistID string) ([]Track, error)
|
||||
}
|
||||
```
|
||||
|
||||
This gives the provider a name, a list of playlists, and the ability to return
|
||||
tracks for a playlist. That's enough for basic playback.
|
||||
|
||||
## Capability Interfaces (optional)
|
||||
|
||||
Implement any combination of these to unlock additional UI features. All
|
||||
interfaces are defined in `provider/interfaces.go`.
|
||||
|
||||
| Interface | What it enables | Methods |
|
||||
|---|---|---|
|
||||
| `Searcher` | Track search overlay | `SearchTracks(ctx, query, limit)` |
|
||||
| `ArtistBrowser` | Hierarchical artist browsing | `Artists()`, `ArtistAlbums(id)` |
|
||||
| `AlbumBrowser` | Paginated album browsing with sort | `AlbumList(sort, offset, size)`, `AlbumSortTypes()` |
|
||||
| `AlbumTrackLoader` | Album track listing | `AlbumTracks(albumID)` |
|
||||
| `Scrobbler` | Playback reporting | `Scrobble(track, submission)` |
|
||||
| `PlaylistWriter` | Add track to playlist | `AddTrackToPlaylist(ctx, playlistID, track)` |
|
||||
| `PlaylistCreator` | Create new playlist | `CreatePlaylist(ctx, name)` |
|
||||
| `PlaylistDeleter` | Remove playlists/tracks | `DeletePlaylist(name)`, `RemoveTrack(name, index)` |
|
||||
| `CustomStreamer` | Custom URI decode pipeline | `URISchemes()`, `NewStreamer(uri)` |
|
||||
| `FavoriteToggler` | Favorite toggling | `ToggleFavorite(id)` |
|
||||
| `Closer` | Cleanup on shutdown | `Close()` |
|
||||
| `Authenticator` | Interactive sign-in flow | `Authenticate() error` (in `playlist` package) |
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Create the package
|
||||
|
||||
Create `external/<name>/provider.go`:
|
||||
|
||||
```go
|
||||
package jellyfin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bjarneo/cliamp/playlist"
|
||||
"github.com/bjarneo/cliamp/provider"
|
||||
)
|
||||
|
||||
// Compile-time interface checks.
|
||||
var (
|
||||
_ provider.Searcher = (*Provider)(nil)
|
||||
_ provider.AlbumTrackLoader = (*Provider)(nil)
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
baseURL string
|
||||
token string
|
||||
}
|
||||
|
||||
func New(baseURL, token string) *Provider {
|
||||
return &Provider{baseURL: baseURL, token: token}
|
||||
}
|
||||
|
||||
func (p *Provider) Name() string { return "Jellyfin" }
|
||||
|
||||
func (p *Provider) Playlists() ([]playlist.PlaylistInfo, error) {
|
||||
// Fetch playlists from your server's API.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *Provider) Tracks(playlistID string) ([]playlist.Track, error) {
|
||||
// Fetch tracks for a playlist.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *Provider) SearchTracks(ctx context.Context, query string, limit int) ([]playlist.Track, error) {
|
||||
// Search the server's catalog.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *Provider) AlbumTracks(albumID string) ([]playlist.Track, error) {
|
||||
// Fetch tracks for an album.
|
||||
return nil, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Return tracks
|
||||
|
||||
When building `playlist.Track` values:
|
||||
|
||||
- **`Path`**: the playable URL or file path. For HTTP streams, use a full URL.
|
||||
For custom URI schemes (e.g. `spotify:track:xxx`), implement `CustomStreamer`.
|
||||
- **`Stream: true`**: set this for HTTP URLs so the player uses the streaming
|
||||
pipeline.
|
||||
- **`ProviderMeta`**: attach provider-specific metadata as a string map with
|
||||
namespaced keys. This is used for features like scrobbling:
|
||||
|
||||
```go
|
||||
playlist.Track{
|
||||
Path: "https://my-server/stream/123",
|
||||
Title: "Song Title",
|
||||
Artist: "Artist Name",
|
||||
Stream: true,
|
||||
ProviderMeta: map[string]string{"jellyfin.id": "123"},
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add configuration
|
||||
|
||||
Add a config struct to `config/config.go`:
|
||||
|
||||
```go
|
||||
type JellyfinConfig struct {
|
||||
URL string `toml:"url"`
|
||||
Token string `toml:"token"`
|
||||
}
|
||||
```
|
||||
|
||||
Add the field to the top-level `Config` struct and a TOML section:
|
||||
|
||||
```toml
|
||||
[jellyfin]
|
||||
url = "https://jellyfin.example.com"
|
||||
token = "your-api-key"
|
||||
```
|
||||
|
||||
### 4. Register in main.go
|
||||
|
||||
Wire up the provider in the `run()` function in `main.go`:
|
||||
|
||||
```go
|
||||
if cfg.Jellyfin.URL != "" && cfg.Jellyfin.Token != "" {
|
||||
jfProv := jellyfin.New(cfg.Jellyfin.URL, cfg.Jellyfin.Token)
|
||||
providers = append(providers, ui.ProviderEntry{
|
||||
Key: "jellyfin", Name: "Jellyfin", Provider: jfProv,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
If your provider needs a custom audio pipeline (like Spotify's `spotify:` URIs),
|
||||
register a streamer factory:
|
||||
|
||||
```go
|
||||
if cs, ok := myProv.(provider.CustomStreamer); ok {
|
||||
for _, scheme := range cs.URISchemes() {
|
||||
p.RegisterStreamerFactory(scheme, cs.NewStreamer)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your provider needs the buffered download pipeline for its stream URLs
|
||||
(like Navidrome's Subsonic endpoints), register a URL matcher:
|
||||
|
||||
```go
|
||||
p.RegisterBufferedURLMatcher(jellyfin.IsStreamURL)
|
||||
```
|
||||
|
||||
### 5. Add a `--provider` flag value
|
||||
|
||||
In `main.go`'s help text, add your provider key to the `--provider` line so
|
||||
users can set it as their default.
|
||||
|
||||
## What the UI Does Automatically
|
||||
|
||||
You don't need to touch the UI code. Based on which interfaces your provider
|
||||
implements, the UI will automatically:
|
||||
|
||||
- Show the browse overlay ("N") if any registered provider implements `ArtistBrowser` or `AlbumBrowser`
|
||||
- Show the search overlay ("F") if any registered provider implements `Searcher`
|
||||
- Enable add-to-playlist in search results if the searched provider implements `PlaylistWriter`
|
||||
- Scrobble playback if `Scrobbler` is implemented
|
||||
- Run interactive auth on first use if `Authenticator` is implemented
|
||||
- Call `Close()` on shutdown if `Closer` is implemented
|
||||
|
||||
The "N" and "F" shortcuts work regardless of which provider is currently active
|
||||
They find the first registered provider with the needed capability.
|
||||
@@ -0,0 +1,86 @@
|
||||
# Qobuz Integration
|
||||
|
||||
cliamp can stream your [Qobuz](https://www.qobuz.com/) library directly through its audio pipeline. EQ, visualizer, and all effects apply. Requires an active Qobuz subscription.
|
||||
|
||||
Qobuz delivers lossless FLAC, so cliamp streams it through the same buffer-while-playing + ffmpeg pipeline used for other lossless providers. `ffmpeg` must be on `PATH`.
|
||||
|
||||
## Setup
|
||||
|
||||
The fastest path is the interactive wizard: run `cliamp setup`, pick **Qobuz**, choose a stream quality, and it writes the `[qobuz]` block for you.
|
||||
|
||||
Or configure it manually in `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[qobuz]
|
||||
enabled = true
|
||||
quality = 6
|
||||
```
|
||||
|
||||
No developer credentials are needed. The `app_id`, signing secrets, and OAuth private key are scraped automatically from the Qobuz web player.
|
||||
|
||||
Run `cliamp`, select Qobuz as a provider, and press `Enter` to sign in. A browser window opens for Qobuz's OAuth login. Once you authorize, credentials are cached at `~/.config/cliamp/qobuz_credentials.json` and subsequent launches refresh silently.
|
||||
|
||||
> **Click "Back" to finish.** After you authorize, Qobuz shows a *"You are signed in, you can leave this page"* screen with a **Back** button rather than redirecting automatically. Click that **Back** button. It fires the redirect that hands the sign-in code to cliamp and completes authentication. cliamp waits (up to 5 minutes) for it.
|
||||
|
||||
### Quality
|
||||
|
||||
`quality` selects the Qobuz `format_id`. If omitted, cliamp uses `6` (FLAC CD). Supported values:
|
||||
|
||||
| Value | Format |
|
||||
|---|---|
|
||||
| `5` | MP3 320 kbps |
|
||||
| `6` | FLAC 16-bit / 44.1 kHz (CD) |
|
||||
| `7` | FLAC 24-bit up to 96 kHz (Hi-Res) |
|
||||
| `27` | FLAC 24-bit up to 192 kHz (Hi-Res) |
|
||||
|
||||
Hi-Res tiers require a Qobuz plan that includes them. Any other value falls back to `6`.
|
||||
|
||||
## Usage
|
||||
|
||||
Start directly on Qobuz:
|
||||
|
||||
```sh
|
||||
cliamp --provider qobuz
|
||||
```
|
||||
|
||||
Once authenticated, Qobuz appears as a provider alongside the others. Press `Q` to jump straight to Qobuz, or `Esc`/`b` to open the provider browser and select it.
|
||||
|
||||
The provider surfaces your Qobuz library:
|
||||
|
||||
- **Favorite Tracks**: your liked songs.
|
||||
- **Random Tracks**: a random sample of up to 500 tracks drawn from across all your playlists, with duplicates removed. Press `Ctrl+R` to reshuffle the sample.
|
||||
- **Your playlists**: playlists you created or subscribed to.
|
||||
- **Favorite albums**: browsable in the album view.
|
||||
- **Favorite artists**: browse an artist to see their albums.
|
||||
|
||||
Press `Ctrl+F` while Qobuz is active to search the Qobuz catalog for tracks.
|
||||
|
||||
## Controls
|
||||
|
||||
When focused on the provider panel:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Up` `Down` / `j` `k` | Navigate |
|
||||
| `Enter` | Load the selected playlist/album or play the selected track |
|
||||
| `Ctrl+F` | Search Qobuz tracks |
|
||||
| `Ctrl+R` | Refresh (re-resolves stream URLs) |
|
||||
| `Tab` | Switch between provider and playlist focus |
|
||||
| `Esc` / `b` | Open provider browser |
|
||||
|
||||
After loading a playlist or album you return to the standard playlist view with all the usual controls (seek, volume, EQ, shuffle, repeat, queue, search, lyrics).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"OAuth failed" / browser doesn't open**: cliamp opens a localhost redirect listener on a random port. Make sure nothing is blocking outbound access to `qobuz.com` and that a default browser is configured. The flow times out after 5 minutes.
|
||||
- **Sign-in seems to hang / "you can leave this page"**: after authorizing, the Qobuz OAuth page shows a confirmation screen with a **Back** button instead of redirecting automatically. Click **Back** to complete sign-in. cliamp keeps waiting (up to 5 minutes) until the redirect arrives.
|
||||
- **Re-authenticate**: run `cliamp qobuz reset` to clear stored credentials, then relaunch cliamp and select Qobuz to sign in again. (Equivalent to deleting `~/.config/cliamp/qobuz_credentials.json` manually.)
|
||||
- **Track is unplayable / skipped**: the track may not be streamable on your subscription tier or in your region. cliamp marks such tracks unplayable and moves on.
|
||||
- **Hi-Res not delivered**: setting `quality = 27` does not upgrade a tier that lacks Hi-Res. Qobuz returns the best your plan allows.
|
||||
- **Stalls after a long idle session**: signed stream URLs expire over time. Press `Ctrl+R` to refresh, which re-resolves the URLs.
|
||||
|
||||
## Requirements
|
||||
|
||||
- An active Qobuz subscription
|
||||
- `ffmpeg` on `PATH` for FLAC decoding
|
||||
- No developer/API registration: credentials are obtained automatically
|
||||
@@ -0,0 +1,40 @@
|
||||
# Quickshell Now-Playing Widget (Omarchy)
|
||||
|
||||
A notification-sized "now playing" card for [Quickshell](https://quickshell.org), tailored for [Omarchy](https://omarchy.org). Lives in the repo at [`contrib/quickshell/`](../contrib/quickshell).
|
||||
|
||||
It pulls live spectrum data from a running cliamp over the IPC socket, draws a Winamp 2-style segmented analyzer, and reads colors directly from the active Omarchy theme so it restyles in lock-step when you swap themes.
|
||||
|
||||
> This widget is built for an Omarchy setup. It uses Omarchy's `~/.config/omarchy/current/theme/colors.toml` as the source of truth for its palette. On a non-Omarchy system the panel still works but falls back to the default kanagawa-dragon-ish colors baked into `NowPlaying.qml`.
|
||||
|
||||
## What you get
|
||||
|
||||
- 300 x 72 card centered along the bottom of every screen
|
||||
- Full-width Winamp 2-style LED spectrum analyzer with falling peak caps
|
||||
- Track title + artist, click-to-seek progress bar, time readout
|
||||
- Pristine vector transport icons (prev / play-pause / next), no font dependency
|
||||
- Theme follows the active Omarchy theme (background, foreground, accent, color1..color8, selection_background)
|
||||
- Card border tracks the muted slot of the Omarchy palette (`selection_background`, falling back to `color8`)
|
||||
- Click the card and press `Esc` or `Q` to dismiss
|
||||
|
||||
## Quick start
|
||||
|
||||
Make sure cliamp is running first, then either run the widget directly:
|
||||
|
||||
```sh
|
||||
qs -p contrib/quickshell/shell.qml
|
||||
```
|
||||
|
||||
Or install it as a named Quickshell config:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/quickshell
|
||||
ln -s "$PWD/contrib/quickshell" ~/.config/quickshell/cliamp
|
||||
qs -c cliamp
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Quickshell 0.2+
|
||||
- A running cliamp (Linux only; the widget needs cliamp's MPRIS service and IPC socket)
|
||||
- Omarchy, for the theme integration. Without Omarchy the palette stays on the built-in defaults.
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Remote Control (IPC)
|
||||
|
||||
Control a running cliamp instance from another terminal, a shell script, or an AI coding assistant.
|
||||
|
||||
When cliamp starts, it listens on a local IPC socket at `~/.config/cliamp/cliamp.sock` (or `%APPDATA%\cliamp\cliamp.sock` on Windows when `HOME` is unset). CLI subcommands connect to this socket to send playback commands and receive status. On Windows 10/11, this uses the same local socket transport via Go's AF_UNIX support.
|
||||
|
||||
## Playback Commands
|
||||
|
||||
```sh
|
||||
cliamp play # resume playback
|
||||
cliamp pause # pause playback
|
||||
cliamp toggle # play/pause toggle
|
||||
cliamp next # next track
|
||||
cliamp prev # previous track
|
||||
cliamp stop # stop playback
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
```sh
|
||||
cliamp status # human-readable current state
|
||||
cliamp status --json # machine-readable JSON
|
||||
```
|
||||
|
||||
JSON output:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"state": "playing",
|
||||
"track": {
|
||||
"title": "Imperial March",
|
||||
"artist": "John Williams",
|
||||
"path": "/path/to/file.mp3"
|
||||
},
|
||||
"position": 42.5,
|
||||
"duration": 183.0,
|
||||
"volume": -3,
|
||||
"playlist": "Star Wars OT",
|
||||
"index": 12,
|
||||
"total": 59,
|
||||
"visualizer": "ClassicPeak",
|
||||
"theme": {
|
||||
"name": "Kanagawa Dragon",
|
||||
"accent": "#658594",
|
||||
"fg": "#c5c9c5",
|
||||
"green": "#8a9a7b",
|
||||
"yellow": "#c4b28a",
|
||||
"red": "#c4746e"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `theme` block carries the active cliamp theme's resolved hex colors. Empty hex fields mean the default ANSI fallback theme is active.
|
||||
|
||||
## Volume and Seek
|
||||
|
||||
```sh
|
||||
cliamp volume -5 # adjust volume in dB
|
||||
cliamp seek 30 # seek to position in seconds
|
||||
```
|
||||
|
||||
## Playlist Loading
|
||||
|
||||
```sh
|
||||
cliamp load "Playlist Name" # load a playlist into the player
|
||||
cliamp queue /path/to.mp3 # queue a single track
|
||||
```
|
||||
|
||||
## Spectrum Streaming
|
||||
|
||||
```sh
|
||||
cliamp visstream # NDJSON spectrum frames at 30 fps (default)
|
||||
cliamp visstream --fps 60 # up to 60 fps; clamped to [1, 60]
|
||||
```
|
||||
|
||||
`visstream` holds a single IPC connection open and emits one JSON line per frame containing the 10-band spectrum and the active visualizer mode name:
|
||||
|
||||
```json
|
||||
{"ok":true,"visualizer":"Bars","bands":[0.93,0.81,0.62,0.48,0.31,0.22,0.14,0.09,0.04,0.01]}
|
||||
```
|
||||
|
||||
Band values are normalized to [0, 1], in the same shape cliamp uses internally for spectrum visualizers. This is what powers the [Quickshell now-playing widget](quickshell.md). Consumers can pipe stdout directly into another process (`cliamp visstream | jq`) or use it from a long-lived subprocess in a UI toolkit.
|
||||
|
||||
Under the hood it issues a `{"cmd":"bands"}` request per tick over the existing IPC socket; you can also issue this command directly from your own client if you want frame-pulled access:
|
||||
|
||||
```json
|
||||
{"cmd": "bands"}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{"ok": true, "visualizer": "Bars", "bands": [0.93, 0.81, ...]}
|
||||
```
|
||||
|
||||
## Protocol
|
||||
|
||||
The IPC protocol is newline-delimited JSON over a local stream socket. Each request is a single JSON object followed by a newline. The server responds with a single JSON object followed by a newline.
|
||||
|
||||
Request format:
|
||||
|
||||
```json
|
||||
{"cmd": "status"}
|
||||
{"cmd": "next"}
|
||||
{"cmd": "volume", "value": -5}
|
||||
{"cmd": "load", "playlist": "Star Wars OT"}
|
||||
{"cmd": "queue", "path": "/path/to/file.mp3"}
|
||||
```
|
||||
|
||||
Response format:
|
||||
|
||||
```json
|
||||
{"ok": true}
|
||||
{"ok": true, "state": "playing", "track": {...}, ...}
|
||||
{"ok": false, "error": "cliamp is not running"}
|
||||
```
|
||||
|
||||
## Socket Details
|
||||
|
||||
- **Path**: `~/.config/cliamp/cliamp.sock` (or `%APPDATA%\cliamp\cliamp.sock` on Windows when `HOME` is unset; created on TUI start, removed on shutdown)
|
||||
- **Permissions**: `0600` (owner only)
|
||||
- **Stale detection**: A PID file (`cliamp.sock.pid`) tracks the owning process. If cliamp crashes, the next instance detects the stale socket and cleans it up.
|
||||
|
||||
## Scripting Examples
|
||||
|
||||
```sh
|
||||
# Skip to next track and show what's playing
|
||||
cliamp next && cliamp status --json | jq .track.title
|
||||
|
||||
# Pause from a tmux/cmux script
|
||||
cliamp pause
|
||||
|
||||
# Load a playlist and start playing
|
||||
cliamp load "Blade Runner" && cliamp play
|
||||
```
|
||||
|
||||
## Headless Daemon Mode
|
||||
|
||||
Run cliamp without a TUI and drive it entirely over this IPC interface — useful for status bars, hotkey scripts, cron jobs, and embedded boxes. See [Headless Daemon Mode](headless.md) for setup, use cases, and example configs (Waybar, Hyprland, systemd, cron).
|
||||
|
||||
```sh
|
||||
cliamp --daemon # no TUI, IPC only
|
||||
cliamp --daemon --auto-play --playlist Lofi # start playing on launch
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
If cliamp is not running:
|
||||
|
||||
```
|
||||
$ cliamp status
|
||||
cliamp is not running (no socket at /Users/you/.config/cliamp/cliamp.sock)
|
||||
```
|
||||
@@ -0,0 +1,76 @@
|
||||
# SoundCloud Integration
|
||||
|
||||
cliamp supports [SoundCloud](https://soundcloud.com) as an opt-in provider. Search, paste-to-play, browse a profile, and (with a browser cookie hookup) stream subscriber-gated tracks. Powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp), so it requires `yt-dlp` on `PATH`.
|
||||
|
||||
> SoundCloud closed its OAuth program to new applications in 2014, so the bring-your-own-`client_id` pattern Spotify uses isn't available. cliamp signs you in by reusing your browser's existing SoundCloud session — see [Sign in via browser cookies](#sign-in-via-browser-cookies) below.
|
||||
|
||||
## Enable
|
||||
|
||||
SoundCloud is **off by default**. To turn it on, add to `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[soundcloud]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
Once enabled:
|
||||
|
||||
- **Search** with `Ctrl+F` while SoundCloud is the active provider — runs `scsearch:` against SoundCloud's public index.
|
||||
- **Paste a URL** (`u`) — any `soundcloud.com/<artist>/<track>` URL plays.
|
||||
- **Browse list with curated genres** — when no profile is configured, the playlists pane is seeded with **Trending**, **Hip-Hop**, **Electronic**, **House**, **Lo-Fi**, **Indie**, and **Pop**. These are search-backed virtual playlists (real-time scsearch results), not editorial charts — SoundCloud's official chart endpoints all 404 through yt-dlp at present.
|
||||
|
||||
## Browse a profile
|
||||
|
||||
Set a username to expose that profile's content in the browse pane:
|
||||
|
||||
```toml
|
||||
[soundcloud]
|
||||
enabled = true
|
||||
user = "yourname"
|
||||
```
|
||||
|
||||
This replaces the curated Browse list with three playlists for `soundcloud.com/yourname`:
|
||||
|
||||
- **Tracks** — everything the user has uploaded
|
||||
- **Likes** — tracks they've liked
|
||||
- **Reposts** — tracks they've reposted
|
||||
|
||||
Works for any public profile. No SoundCloud sign-in required at this level.
|
||||
|
||||
## Sign in via browser cookies
|
||||
|
||||
For private likes, hidden uploads, or SoundCloud Go+ subscriber-gated tracks, point yt-dlp at your browser's cookie jar:
|
||||
|
||||
```toml
|
||||
[soundcloud]
|
||||
enabled = true
|
||||
user = "yourname"
|
||||
cookies_from = "firefox" # also: chrome, chromium, brave, edge, opera, safari, vivaldi
|
||||
```
|
||||
|
||||
cliamp passes `--cookies-from-browser <name>` to every yt-dlp invocation — search, browse, and playback. As long as you're signed into SoundCloud in that browser (no need to keep it open), yt-dlp acts as logged-in-you and can access content your account is authorized for.
|
||||
|
||||
This is the same mechanism `[ytmusic] cookies_from` uses. If you set both, the last one to initialize wins for the playback path; in practice users have one default browser they're signed into multiple sites with, so this is fine.
|
||||
|
||||
## CLI
|
||||
|
||||
```sh
|
||||
cliamp https://soundcloud.com/forss/flickermood # play a track
|
||||
cliamp https://soundcloud.com/forss/sets/album # play a set / playlist
|
||||
cliamp https://soundcloud.com/forss # play a profile's tracks
|
||||
cliamp --provider soundcloud # start with SoundCloud as the active provider
|
||||
cliamp search-sc "lofi beats" # legacy: SoundCloud search from the shell
|
||||
```
|
||||
|
||||
URL playback works regardless of the `[soundcloud]` toggle — yt-dlp resolves any SoundCloud link cliamp hands it. The `enabled` flag gates only the in-app provider entry.
|
||||
|
||||
## When playback fails
|
||||
|
||||
Some tracks 404 on SoundCloud's per-track format API even though the page and search index still show them. Common causes: subscriber-gated content (Go+), region-blocked streams, deleted-but-cached entries, or transient yt-dlp extractor glitches. cliamp surfaces yt-dlp's exit message and shows a status notification — *"Couldn't play X — track is gated, restricted, or unavailable."* — so you know it's an upstream issue rather than a cliamp bug.
|
||||
|
||||
If you hit this on tracks you expect to play, set `cookies_from` (above) and confirm you're signed into SoundCloud in that browser.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) on `PATH`
|
||||
- Optional: a browser with an active SoundCloud session, for `cookies_from`
|
||||
@@ -0,0 +1,97 @@
|
||||
# Spotify Integration
|
||||
|
||||
Cliamp can stream your [Spotify](https://www.spotify.com/) library directly through its audio pipeline. EQ, visualizer, and all effects apply. Requires a [Spotify Premium](https://www.spotify.com/premium/) account.
|
||||
|
||||
> **Windows:** Spotify is currently unavailable on Windows builds because the `go-librespot` playback backend used by cliamp does not compile there yet.
|
||||
>
|
||||
> **Quick start:** run `cliamp setup`, pick Spotify, and follow the prompts. The recommended path is to register your own Spotify Developer app and paste its `client_id` — it gives you a private rate-limit quota and works for playback, library, and playlists. There's also a built-in shared `client_id` available for users who specifically need Spotify search.
|
||||
|
||||
## Setup
|
||||
|
||||
### Recommended: bring your own client ID
|
||||
|
||||
Register a Spotify Developer app and set `client_id` in `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[spotify]
|
||||
client_id = "your_client_id_here"
|
||||
bitrate = 320
|
||||
```
|
||||
|
||||
To register one:
|
||||
|
||||
1. Go to [developer.spotify.com/dashboard](https://developer.spotify.com/dashboard) and log in
|
||||
2. Click **Create app**
|
||||
3. Fill in a name (e.g. "cliamp") and description (anything works)
|
||||
4. Add `http://127.0.0.1:19872/login` as a **Redirect URI**
|
||||
5. Check **Web API** under "Which API/SDKs are you planning to use?"
|
||||
6. Click **Save**
|
||||
7. Open your app's **Settings** and copy the **Client ID**
|
||||
|
||||
`bitrate` is optional. If omitted, cliamp uses `320`. Supported values are `96`, `160`, and `320`. Non-positive values (≤ 0) are treated as `320`. Other positive values are rounded to the nearest supported bitrate.
|
||||
|
||||
Run `cliamp`, select Spotify as a provider, and press Enter to sign in. Credentials are cached at `~/.config/cliamp/spotify_credentials.json`. Subsequent launches refresh silently.
|
||||
|
||||
### Newer apps and the search caveat
|
||||
|
||||
Apps registered in Development Mode (the default for anything created on developer.spotify.com after Nov 27, 2024) **still work for almost everything** — playback, your library, your playlists, save/follow actions, OAuth itself. The one specific thing they can't do is hit Spotify's **catalog endpoints**: `/v1/search` and a handful of related endpoints.
|
||||
|
||||
You'll see the catalog restriction as `400 "Invalid limit"` whenever you press <kbd>Ctrl+F</kbd> to search Spotify — Spotify [introduced this restriction on Nov 27, 2024](https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api) and rarely grants Extended Quota Mode to personal/non-commercial apps. Cliamp surfaces a friendlier error explaining what's actually wrong instead of the raw "Invalid limit" message.
|
||||
|
||||
If you don't use Spotify search often, your own `client_id` is the better choice — keep it.
|
||||
|
||||
### Alternative: built-in shared client ID
|
||||
|
||||
If Spotify search is essential to you and your own app hits the dev-mode restriction above, drop the `client_id` line:
|
||||
|
||||
```toml
|
||||
[spotify]
|
||||
bitrate = 320
|
||||
```
|
||||
|
||||
cliamp falls back to a built-in `client_id` (the same one [librespot](https://github.com/librespot-org/librespot) and [spotify-player](https://github.com/aome510/spotify-player) ship with) which predates the Nov 27, 2024 cutoff and retains catalog access.
|
||||
|
||||
> **Heads-up — shared rate limit:** The built-in `client_id` is shared with every librespot-, spotify-player-, and cliamp user worldwide. Spotify's per-app quota is global, so when the pool is busy you may see `429 Too Many Requests` errors during search or playlist loading. Cliamp retries with backoff, but persistent 429s mean the pool is hot — your own `client_id` doesn't share that problem.
|
||||
|
||||
## Usage
|
||||
|
||||
Once authenticated, Spotify appears as a provider alongside Navidrome and local playlists. Press `Esc`/`b` to open the provider browser and select Spotify.
|
||||
|
||||
Your Spotify playlists are listed in the provider panel. Navigate with the arrow keys and press `Enter` to load one. Tracks are streamed through cliamp's audio pipeline, so EQ, visualizer, mono, and all other effects work exactly as with local files.
|
||||
|
||||
## Controls
|
||||
|
||||
When focused on the provider panel:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Up` `Down` / `j` `k` | Navigate playlists |
|
||||
| `Enter` | Load the selected playlist |
|
||||
| `Tab` | Switch between provider and playlist focus |
|
||||
| `Esc` / `b` | Open provider browser |
|
||||
|
||||
After loading a playlist you return to the standard playlist view with all the usual controls (seek, volume, EQ, shuffle, repeat, queue, search, lyrics).
|
||||
|
||||
## Playlists
|
||||
|
||||
Only playlists in your Spotify library are shown. This includes playlists you've created and playlists you've saved (followed). If a public playlist doesn't appear, open Spotify and click **Save** on it first. There's no need to copy tracks to a new playlist.
|
||||
|
||||
## Podcasts
|
||||
|
||||
Podcast episodes work like tracks. Press `Ctrl+F` to search Spotify and matching episodes (for example "Joe Rogan") appear alongside songs; press `Enter` to play. Playlists that mix songs and episodes load and play both.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"OAuth failed"**: Make sure your redirect URI is exactly `http://127.0.0.1:19872/login` in the Spotify dashboard (no trailing slash).
|
||||
- **Playlist not showing**: You must save/follow the playlist in Spotify for it to appear. Only your library playlists are listed.
|
||||
- **Playback issues**: Spotify integration requires a Premium account. Free accounts cannot stream.
|
||||
- **Re-authenticate**: Run `cliamp spotify reset` to clear stored credentials, then relaunch cliamp and select Spotify to sign in again. (Equivalent to deleting `~/.config/cliamp/spotify_credentials.json` manually.)
|
||||
- **Persistent "rate-limited" errors on `/v1/me`**: Your stored auth has expired or been revoked. Cliamp will detect this on most launches and prompt you to sign in again, but if it does not, run `cliamp spotify reset` and re-authenticate. This is *not* a real Spotify rate limit — waiting will not resolve it.
|
||||
- **`429 Too Many Requests` on search or playlist loading (using the built-in fallback)**: The built-in `client_id` is shared with every librespot- and spotify-player-based client; when the global pool is busy, Spotify caps requests for everyone using it. Cliamp retries with exponential backoff, but if the errors keep returning the simplest fix is to register your own developer app and set `client_id` in `[spotify]` — your personal app gets its own quota.
|
||||
- **"search blocked — your client_id is too new" on <kbd>Ctrl+F</kbd>**: Your registered Spotify Developer app is in Development Mode and can't hit `/v1/search` (Spotify's Nov 27, 2024 change). Everything else on your app — playback, library, playlists, save/follow — still works fine. Either remove `client_id` from `[spotify]` to use the built-in fallback for search, or just don't use Spotify search and keep your own app.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Spotify Premium account
|
||||
- No additional system dependencies beyond cliamp itself
|
||||
- A registered app at [developer.spotify.com/dashboard](https://developer.spotify.com/dashboard) is **optional** — cliamp ships with a built-in fallback `client_id`
|
||||
@@ -0,0 +1,91 @@
|
||||
# SSH Streaming
|
||||
|
||||
Play music from a remote machine over SSH without mounting filesystems.
|
||||
|
||||
## How It Works
|
||||
|
||||
When a track path starts with `ssh://`, cliamp pipes the audio over SSH using the system `ssh` binary:
|
||||
|
||||
```
|
||||
ssh://hostname/absolute/path/to/file.mp3
|
||||
```
|
||||
|
||||
The player runs `ssh hostname cat /path/to/file.mp3` and feeds the output to the audio decoder. No temporary files, no filesystem mounts.
|
||||
|
||||
## Creating SSH Playlists
|
||||
|
||||
Use `--ssh HOST` with `playlist create` to walk a remote directory:
|
||||
|
||||
```sh
|
||||
cliamp playlist create "Blade Runner" --ssh nas "/Volumes/Music/Blade Runner/"
|
||||
# Created playlist "Blade Runner" (31 tracks, ssh://nas)
|
||||
```
|
||||
|
||||
This runs `ssh nas find /path -type f -name '*.mp3' ...` to discover audio files, then creates a TOML playlist with `ssh://` prefixed paths.
|
||||
|
||||
## TOML Format
|
||||
|
||||
SSH playlists look like regular playlists with `ssh://` paths:
|
||||
|
||||
```toml
|
||||
name = "Blade Runner"
|
||||
|
||||
[[track]]
|
||||
path = "ssh://nas/Volumes/Music/Blade Runner/01 - Prologue.mp3"
|
||||
title = "Prologue And Main Titles"
|
||||
|
||||
[[track]]
|
||||
path = "ssh://nas/Volumes/Music/Blade Runner/02 - Voight Kampff.mp3"
|
||||
title = "Voight Kampff Test"
|
||||
```
|
||||
|
||||
## SSH Configuration
|
||||
|
||||
cliamp uses the system `ssh` binary, which reads `~/.ssh/config`. Host aliases, keys, ports, and ProxyJump all work automatically:
|
||||
|
||||
```
|
||||
# ~/.ssh/config
|
||||
Host nas
|
||||
HostName 192.168.1.50
|
||||
User music
|
||||
IdentityFile ~/.ssh/nas_key
|
||||
|
||||
Host mac-mini-ts
|
||||
HostName 100.64.0.5
|
||||
```
|
||||
|
||||
## Supported Formats
|
||||
|
||||
SSH streaming works with all formats supported by the native decoders:
|
||||
|
||||
- `.mp3` (native decoder)
|
||||
- `.flac` (native decoder)
|
||||
- `.ogg` / `.opus` (native decoder)
|
||||
- `.wav` (native decoder)
|
||||
|
||||
Formats requiring ffmpeg (`.m4a`, `.wma`) may not work over SSH since the ffmpeg decoder expects a seekable file.
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Scenario | Behavior |
|
||||
|----------|----------|
|
||||
| Host unreachable | Player shows error, advances to next track |
|
||||
| Auth failure | SSH uses `BatchMode=yes` and never hangs on password prompts |
|
||||
| Connection drops mid-stream | Player detects EOF, advances to next track |
|
||||
| Unknown host key | Rejected. Add the host to `~/.ssh/known_hosts` first, or configure in `~/.ssh/config` |
|
||||
|
||||
## Mixing Local and SSH Tracks
|
||||
|
||||
A single playlist can mix local and SSH paths:
|
||||
|
||||
```toml
|
||||
name = "Mixed"
|
||||
|
||||
[[track]]
|
||||
path = "/local/path/track1.mp3"
|
||||
title = "Local Track"
|
||||
|
||||
[[track]]
|
||||
path = "ssh://nas/remote/path/track2.mp3"
|
||||
title = "Remote Track"
|
||||
```
|
||||
@@ -0,0 +1,80 @@
|
||||
# Streaming
|
||||
|
||||
cliamp can play audio from URLs, M3U/PLS playlists, and podcast RSS feeds.
|
||||
|
||||
## HTTP Streams
|
||||
|
||||
Play audio directly from URLs:
|
||||
|
||||
```sh
|
||||
cliamp https://example.com/song.mp3
|
||||
cliamp http://radio-station.com/stream.m3u
|
||||
cliamp local.mp3 https://example.com/remote.mp3 # mix local + remote
|
||||
```
|
||||
|
||||
For non-seekable HTTP streams, the UI shows `● Streaming` with a static seek bar, and seek keys are silently ignored.
|
||||
|
||||
## PLS Playlists
|
||||
|
||||
PLS playlist files are supported alongside M3U:
|
||||
|
||||
```sh
|
||||
cliamp https://radio.cliamp.stream/lofi/stream.pls
|
||||
```
|
||||
|
||||
## HLS Streams
|
||||
|
||||
HLS playlists (`.m3u8`, master or media) are supported, as used by large broadcasters such as Brazilian RBS/Wowza stations:
|
||||
|
||||
```sh
|
||||
cliamp "https://example.com/live/playlist.m3u8"
|
||||
```
|
||||
|
||||
cliamp hands the URL to ffmpeg, which resolves the relative chunklist/segment URIs and follows the live segment window. Requires `ffmpeg` (already needed for AAC/Opus).
|
||||
|
||||
Live HLS carries timed metadata rather than inline ICY, so the now-playing track title isn't updated for HLS streams.
|
||||
|
||||
## Podcasts
|
||||
|
||||
Play any podcast by passing its RSS feed URL:
|
||||
|
||||
```sh
|
||||
cliamp https://example.com/podcast/feed.xml
|
||||
```
|
||||
|
||||
Episode titles and the podcast name are extracted from the feed and shown in the playlist.
|
||||
|
||||
### Xiaoyuzhou (小宇宙)
|
||||
|
||||
Play individual episodes from [Xiaoyuzhou](https://www.xiaoyuzhoufm.com) by passing the episode URL:
|
||||
|
||||
```sh
|
||||
cliamp https://www.xiaoyuzhoufm.com/episode/xxxx
|
||||
```
|
||||
|
||||
## Radio Catalog
|
||||
|
||||
Press `R` in the player to browse and search 30,000+ online radio stations from the [Radio Browser](https://www.radio-browser.info/) directory. Use `/` to search by name, `Enter` to play, and `a` to append a station to the playlist.
|
||||
|
||||
## Track Info
|
||||
|
||||
For live radio, cliamp shows the current track from the stream's inline ICY metadata (`StreamTitle`). This works for most stations, in any codec (MP3, AAC, Opus, ...).
|
||||
|
||||
Some broadcasters send no inline metadata and publish now-playing through a separate API instead. cliamp pulls those automatically:
|
||||
|
||||
| Station | Source | Shown |
|
||||
| --- | --- | --- |
|
||||
| FIP (and FIP Jazz, Rock, Groove, Reggae, Electro, Metal, Monde, Nouveautes) | Radio France livemeta API | Artist - Title |
|
||||
| NTS 1 / NTS 2 | NTS live API | Current show |
|
||||
|
||||
NTS is live DJ radio with no per-track tagging, so it shows the show/host name rather than a song.
|
||||
|
||||
## Load URL at Runtime
|
||||
|
||||
Press `u` while playing to load a new stream or playlist URL without restarting. Supports the same URL types as CLI arguments: direct audio URLs, M3U/PLS playlists, RSS podcast feeds, and yt-dlp compatible links.
|
||||
|
||||
## Run Your Own Radio Station
|
||||
|
||||
Run your own internet radio with [cliamp-server](https://github.com/bjarneo/cliamp-server). Point it at a directory of audio files and it starts broadcasting. Supports multiple stations, live metadata, and on-the-fly transcoding.
|
||||
|
||||
See also: [playlists.md](playlists.md) for M3U playlist details.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Themes
|
||||
|
||||
cliamp ships with 20 built-in color themes and supports custom themes via simple TOML files.
|
||||
|
||||
Press `t` during playback to open the theme picker. Navigate with `↑`/`↓`, preview live as you move, confirm with `Enter`, or cancel with `Esc`.
|
||||
|
||||
Your selection is saved automatically and restored on next launch.
|
||||
|
||||
## Built-in themes
|
||||
|
||||
ayu-mirage-dark, catppuccin, catppuccin-latte, dracula, ember, ethereal, everforest, flexoki-light, gruvbox, hackerman, kanagawa, matte-black, miasma, neon-blade-runner, nord, osaka-jade, ristretto, rose-pine, tokyo-night, vantablack
|
||||
|
||||
## Creating a custom theme
|
||||
|
||||
Create a `.toml` file in `~/.config/cliamp/themes/`:
|
||||
|
||||
```
|
||||
mkdir -p ~/.config/cliamp/themes
|
||||
```
|
||||
|
||||
Each file needs 6 hex color values. The filename (minus `.toml`) becomes the theme name.
|
||||
|
||||
### Example: `~/.config/cliamp/themes/solarized.toml`
|
||||
|
||||
```toml
|
||||
accent = "#268bd2"
|
||||
bright_fg = "#eee8d5"
|
||||
fg = "#839496"
|
||||
green = "#859900"
|
||||
yellow = "#b58900"
|
||||
red = "#dc322f"
|
||||
```
|
||||
|
||||
That's it. Press `t` and your theme appears in the list immediately.
|
||||
|
||||
### Color reference
|
||||
|
||||
| Key | What it colors |
|
||||
|-------------|---------------------------------------------------|
|
||||
| `accent` | Title, track name, seek bar, selected items |
|
||||
| `bright_fg` | Primary text, time display, help key pill text |
|
||||
| `fg` | Muted/secondary text, help bar, inactive elements, help key pill background |
|
||||
| `green` | Playing indicator, volume bar, spectrum low |
|
||||
| `yellow` | Spectrum middle |
|
||||
| `red` | Spectrum top, error messages |
|
||||
|
||||
All values are hex strings (e.g. `"#ff5733"` or `"#F00"`).
|
||||
|
||||
## Overriding a built-in theme
|
||||
|
||||
If your custom file has the same name as a built-in theme, yours takes priority. For example, creating `~/.config/cliamp/themes/catppuccin.toml` replaces the built-in catppuccin.
|
||||
|
||||
## Setting a default theme
|
||||
|
||||
Add a `theme` line to `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
theme = "catppuccin"
|
||||
```
|
||||
|
||||
Use the filename without `.toml`. Leave empty or omit for terminal default colors.
|
||||
@@ -0,0 +1,113 @@
|
||||
# YouTube & YouTube Music Integration
|
||||
|
||||
Cliamp can browse your [YouTube](https://youtube.com/) and [YouTube Music](https://music.youtube.com/) playlists and play tracks through its audio pipeline. EQ, visualizer, and all effects apply. Playback uses yt-dlp, which must be installed.
|
||||
|
||||
Your playlists are automatically classified into two providers:
|
||||
- **YouTube Music**: playlists containing music content
|
||||
- **YouTube**: playlists containing non-music content (podcasts, vlogs, tutorials, etc.)
|
||||
|
||||
> **Quick start:** YouTube Music works out of the box with built-in fallback credentials — just install yt-dlp and select it in the provider browser. Run `cliamp setup` if you want to disable it, supply your own OAuth client, or configure cookie-based age-gated playback. Manual setup steps for the custom path are below.
|
||||
|
||||
## Setup
|
||||
|
||||
### Creating your client ID
|
||||
|
||||
1. Go to [console.cloud.google.com](https://console.cloud.google.com/) and log in
|
||||
2. Create a new project (or select an existing one)
|
||||
3. Navigate to **APIs & Services > Library**
|
||||
4. Search for **YouTube Data API v3** and click **Enable**
|
||||
5. Go to **APIs & Services > Credentials**
|
||||
6. Click **Create Credentials > OAuth client ID**
|
||||
7. If prompted, configure the OAuth consent screen first:
|
||||
- User Type: **External**
|
||||
- Fill in app name (e.g. "cliamp") and your email
|
||||
- Add scope: `https://www.googleapis.com/auth/youtube.readonly`
|
||||
- Add yourself as a test user (required while app is in "Testing" status)
|
||||
8. For the OAuth client ID:
|
||||
- Application type: **Desktop app**
|
||||
- Name: anything (e.g. "cliamp")
|
||||
9. Copy the **Client ID** and **Client Secret**
|
||||
|
||||
### Configuring cliamp
|
||||
|
||||
Add your client ID and client secret to `~/.config/cliamp/config.toml`:
|
||||
|
||||
```toml
|
||||
[ytmusic]
|
||||
client_id = "your_client_id_here"
|
||||
client_secret = "your_client_secret_here"
|
||||
```
|
||||
|
||||
Optional: to play uploaded/private tracks, add your browser for cookie access:
|
||||
|
||||
```toml
|
||||
[ytmusic]
|
||||
client_id = "your_client_id_here"
|
||||
client_secret = "your_client_secret_here"
|
||||
cookies_from = "chrome"
|
||||
```
|
||||
|
||||
Supported browsers: `chrome`, `firefox`, `brave`, `edge`, `opera`, `safari`, `chromium`.
|
||||
|
||||
You can also point at a specific profile or path using yt-dlp's `browser:path` syntax. For example, Zen browser (a Firefox fork) stores its profile outside the default location:
|
||||
|
||||
```toml
|
||||
[ytmusic]
|
||||
cookies_from = "firefox:~/.config/zen"
|
||||
```
|
||||
|
||||
Run `cliamp` (or `cliamp --provider ytmusic` / `cliamp --provider youtube`), select a provider, and press Enter to sign in. Credentials are cached at `~/.config/cliamp/ytmusic_credentials.json`. Subsequent launches refresh silently.
|
||||
|
||||
## Usage
|
||||
|
||||
Once authenticated, **YouTube** and **YouTube Music** appear as separate providers alongside Spotify, Navidrome, and Radio. Press `Esc`/`b` to open the provider browser.
|
||||
|
||||
- **YouTube Music** shows playlists classified as music (video category "Music")
|
||||
- **YouTube** shows all other playlists (podcasts, vlogs, tutorials, etc.)
|
||||
|
||||
Both share the same Google account login. Classification is automatic (based on video category) and cached to disk so subsequent launches are instant.
|
||||
|
||||
## Controls
|
||||
|
||||
When focused on the provider panel:
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Up` `Down` / `j` `k` | Navigate playlists |
|
||||
| `Enter` | Load the selected playlist |
|
||||
| `Tab` | Switch between provider and playlist focus |
|
||||
| `Esc` / `b` | Open provider browser |
|
||||
|
||||
After loading a playlist you return to the standard playlist view with all the usual controls (seek, volume, EQ, shuffle, repeat, queue, search, lyrics).
|
||||
|
||||
## Playlists
|
||||
|
||||
Playlists are automatically split between the two providers:
|
||||
|
||||
**YouTube Music** shows:
|
||||
- **Liked Music**: your liked songs (YouTube Music's special `LM` playlist)
|
||||
- Playlists containing music content (auto-classified by video category)
|
||||
|
||||
**YouTube** shows:
|
||||
- **Liked Videos**: your liked videos (YouTube's special `LL` playlist)
|
||||
- Playlists containing non-music content
|
||||
|
||||
Classification is determined by sampling a video from each playlist and checking its YouTube category. Results are cached at `~/.config/cliamp/ytmusic_classification.json`. Delete this file to reclassify.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **"ERR: waiting for audio data: EOF" / playback stops immediately**: yt-dlp couldn't produce a stream. cliamp now surfaces yt-dlp's real message (e.g. "Sign in to confirm you're not a bot") instead of the bare EOF, so read the full error. The common causes:
|
||||
- **Outdated yt-dlp**: update it (`yt-dlp -U`, or reinstall from the [official repo](https://github.com/yt-dlp/yt-dlp)). Distro and winget builds are frequently stale and break when YouTube changes.
|
||||
- **Bot detection**: YouTube blocks anonymous requests. Set `cookies_from` (see above) so yt-dlp reuses your logged-in browser session. For Zen browser use `cookies_from = "firefox:~/.config/zen"`.
|
||||
- **Wrong `cookies_from` value**: the browser must be installed and logged in to YouTube, and the profile path must be correct.
|
||||
- **"OAuth failed"**: Make sure your Google Cloud project has YouTube Data API v3 enabled and your OAuth client type is "Desktop app".
|
||||
- **"Access blocked"**: While your app is in "Testing" status, only test users you've added can sign in. Add your Google account as a test user in the OAuth consent screen settings.
|
||||
- **Playlist not showing**: Only playlists in your library are listed. Save/follow a playlist in YouTube Music for it to appear.
|
||||
- **Re-authenticate**: Delete `~/.config/cliamp/ytmusic_credentials.json` and restart cliamp to trigger a fresh login.
|
||||
- **Private/deleted videos**: These are automatically skipped when loading a playlist.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) installed and on your PATH (for audio playback)
|
||||
- A Google Cloud project with YouTube Data API v3 enabled
|
||||
- No Spotify Premium or other paid subscription required. YouTube Music free tier works
|
||||
@@ -0,0 +1,29 @@
|
||||
# YouTube, SoundCloud, NetEase, Bandcamp and Bilibili
|
||||
|
||||
Play from YouTube, SoundCloud, NetEase, Bandcamp, and Bilibili URLs if [yt-dlp](https://github.com/yt-dlp/yt-dlp) is installed:
|
||||
|
||||
```sh
|
||||
cliamp https://www.youtube.com/watch?v=dQw4w9WgXcQ
|
||||
cliamp https://soundcloud.com/artist/track
|
||||
cliamp 'https://music.163.com/#/song?id=1973665667'
|
||||
cliamp https://artist.bandcamp.com/album/name
|
||||
cliamp https://www.bilibili.com/video/BV1xxxxxxxxx
|
||||
cliamp https://space.bilibili.com/uid/lists/id # season/series playlists
|
||||
```
|
||||
|
||||
Playlists and albums are supported. Press `S` to save a downloaded track to `~/Music/cliamp/`.
|
||||
|
||||
## Search
|
||||
|
||||
Search and play directly from the command line:
|
||||
|
||||
```sh
|
||||
cliamp search "never gonna give you up" # search YouTube
|
||||
cliamp search-sc "lofi beats" # search SoundCloud
|
||||
```
|
||||
|
||||
Inside the TUI, press `Ctrl+F` to search the active provider — YouTube when you're on YouTube/YT-Music, SoundCloud when you're on SoundCloud, and NetEase when you're on NetEase. SoundCloud and NetEase also have dedicated provider docs covering signed-in playback: [SoundCloud](soundcloud.md), [NetEase](netease.md).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**Use at your own risk.** Downloading or streaming copyrighted content may violate the terms of service of these platforms. You are responsible for how you use this feature.
|
||||
Reference in New Issue
Block a user