-
-
Notifications
You must be signed in to change notification settings - Fork 236
Expand file tree
/
Copy pathCargo.toml
More file actions
261 lines (236 loc) · 13.5 KB
/
Copy pathCargo.toml
File metadata and controls
261 lines (236 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
[package]
name = "spotatui"
description = "A Spotify client for the terminal written in Rust, powered by Ratatui"
homepage = "https://github.com/LargeModGames/spotatui"
documentation = "https://github.com/LargeModGames/spotatui"
repository = "https://github.com/LargeModGames/spotatui"
keywords = ["spotify", "tui", "cli", "terminal"]
categories = ["command-line-utilities"]
version = "0.40.3"
authors = ["LargeModGames <LargeModGames@gmail.com>"]
edition = "2021"
license = "MIT"
exclude = [
".github/demo.gif",
".github/workflows",
".github/ISSUE_TEMPLATE",
"target/",
"snap/",
]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rspotify = { version = "0.16", default-features = false, features = ["cli", "env-file", "client-reqwest", "reqwest-rustls-tls"] }
ratatui = { version = "0.30", features = ["crossterm", "layout-cache"], default-features = false }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9"
dirs = "6.0"
clap = { version = "4.6", features = ["cargo"] }
clap_complete = "4.6"
unicode-width = "0.2.2"
backtrace = "0.3.76"
crossterm = "0.29"
tui-equalizer = "0.2.0-alpha"
tui-bar-graph = "0.3.3"
colorgrad = "0.8.0"
tokio = { version = "1.52", features = ["full"] }
rand = "0.10.2"
anyhow = "1.0.102"
chrono = "0.4.45"
log = "0.4.32"
fern = "0.7.1"
open = "5.3"
self_update = { version = "0.44", features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate"], optional = true }
sha2 = { version = "0.11", optional = true }
hex = { version = "0.4", optional = true }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "blocking"], default-features = false }
openssl = { version = "0.10", optional = true }
cpal = { version = "0.17", optional = true }
realfft = { version = "3.4", optional = true }
discord-rich-presence = { version = "1.1", optional = true }
ratatui-image = { version = "11.0.4", optional = true, default-features = false, features = ["crossterm"] }
image = { version = "0.25", optional = true }
mlua = { version = "0.12", features = ["lua54", "vendored", "serde"], optional = true }
md-5 = { version = "0.11", optional = true }
# Subsonic streams each track to a tempfile before playing it through the shared
# rodio sink. Also a dev-dependency (used by tests across features).
tempfile = { version = "3", optional = true }
# Internet-radio dependencies: stream-download buffers an infinite HTTP (ICY)
# stream behind a Read+Seek adapter that rodio can decode without seeking;
# icy-metadata parses in-band StreamTitle now-playing metadata. Both are only
# compiled under the opt-in `internet-radio` feature.
stream-download = { version = "0.24", optional = true, default-features = false, features = ["reqwest-rustls"] }
icy-metadata = { version = "0.6", optional = true, default-features = false }
# Local-files media source dependencies
symphonia = { version = "0.6", features = ["mp3", "flac", "ogg", "wav", "aiff", "isomp4", "mkv", "pcm", "vorbis"], optional = true, default-features = false }
lofty = { version = "0.24", optional = true }
# Local-files audio output. Defaults pull in cpal `playback` plus the common
# codecs (flac/mp3/mp4-aac/vorbis/wav) via symphonia. Project-validated on
# Linux and Windows (librespot uses rodio-backend on both); the macOS output
# path is gated off in `LocalPlayer` because rodio crashes on CoreAudio/
# Bluetooth (see the portaudio note above and issues #9/#20).
rodio = { version = "0.22", optional = true }
# Streaming dependencies (librespot)
librespot-core = { version = "0.8", optional = true }
librespot-connect = { version = "0.8", optional = true }
librespot-oauth = { version = "0.8", optional = true }
librespot-metadata = { version = "0.8", optional = true }
librespot-protocol = { version = "0.8", optional = true, default-features = false }
protobuf = { version = "3.7", optional = true }
futures = "0.3.32"
tokio-tungstenite = { version = "0.30", features = ["rustls-tls-webpki-roots"] }
url = "2.5"
keepawake = "0.6"
[target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies]
arboard = { version = "3.4", features = ["wayland-data-control"] }
pipewire = { version = "0.10", optional = true }
librespot-playback = { version = "0.8", optional = true, default-features = false, features = ["alsa-backend"] }
[target.'cfg(all(target_os = "linux", target_env = "musl"))'.dependencies]
arboard = { version = "3.4", features = ["wayland-data-control"] }
librespot-playback = { version = "0.8", optional = true, default-features = false, features = ["rodio-backend"] }
[target.'cfg(target_os = "windows")'.dependencies]
arboard = "3.4"
# On Windows we default to rodio for audio output.
# Without this, librespot-playback falls back to the `pipe` sink which writes raw audio bytes
# to stdout (breaking the TUI and producing no audible output).
librespot-playback = { version = "0.8", optional = true, default-features = false, features = ["rodio-backend"] }
smtc-tokio = { version = "0.1.0", optional = true }
# MPRIS D-Bus support (Linux only)
[target.'cfg(target_os = "linux")'.dependencies]
mpris-server = { version = "0.10", optional = true }
# macOS: librespot-playback + Now Playing / Media Key support
# On macOS we use portaudio-backend by default to avoid the pipe sink fallback (which outputs to stdout).
# Rodio has compatibility issues with macOS CoreAudio and Bluetooth devices (AirPods, etc.)
# causing SIGSEGV crashes. See Issue #9 and #20.
[target.'cfg(target_os = "macos")'.dependencies]
arboard = "3.4"
librespot-playback = { version = "0.8", optional = true, default-features = false, features = ["portaudio-backend"] }
objc2-media-player = { version = "0.3", optional = true }
objc2-foundation = { version = "0.3", optional = true }
objc2-app-kit = { version = "0.3", optional = true, default-features = false, features = ["NSImage"] }
objc2 = { version = "0.6", optional = true }
block2 = { version = "0.6", optional = true }
[features]
default = ["telemetry", "streaming", "audio-viz-cpal", "macos-media", "discord-rpc", "mpris", "self-update", "windows-media", "scripting"]
telemetry = []
self-update = ["dep:self_update", "dep:sha2", "dep:hex"]
streaming = ["librespot-core", "librespot-playback", "librespot-connect", "librespot-oauth", "librespot-metadata", "librespot-protocol", "protobuf"]
# Audio backend features
alsa-backend = ["streaming", "librespot-playback/alsa-backend"]
pulseaudio-backend = ["streaming", "librespot-playback/pulseaudio-backend"]
rodio-backend = ["streaming", "librespot-playback/rodio-backend"]
portaudio-backend = ["streaming", "librespot-playback/portaudio-backend"]
jackaudio-backend = ["streaming", "librespot-playback/jackaudio-backend"]
rodiojack-backend = ["streaming", "librespot-playback/rodiojack-backend"]
sdl-backend = ["streaming", "librespot-playback/sdl-backend"]
gstreamer-backend = ["streaming", "librespot-playback/gstreamer-backend"]
audio-viz = ["realfft", "pipewire"]
audio-viz-cpal = ["realfft", "cpal"] # Alternative for Windows/macOS or if pipewire issues
mpris = ["mpris-server"] # MPRIS D-Bus integration (Linux only)
macos-media = ["objc2-media-player", "objc2-foundation", "objc2-app-kit", "objc2", "block2", "streaming"] # macOS Now Playing integration
windows-media = ["smtc-tokio", "streaming"] # windows SMTC integration
discord-rpc = ["discord-rich-presence"]
cover-art = ["ratatui-image", "image"]
scripting = ["dep:mlua"]
# Shared audio decode/output engine (rodio bundles symphonia). Pulled by any
# source that plays decoded audio through the local sink (local files, Subsonic).
audio-decode = ["dep:rodio"]
# Convenience alias to pull in every alternative (non-Spotify) source at once,
# e.g. `cargo run --features all-sources`. Deliberately NOT part of `default`:
# the release binaries enable these explicitly (see .github/workflows/cd.yml) and
# adding them to `default` would pull lofty/symphonia/md-5/stream-download into
# every plain `cargo build`.
all-sources = ["local-files", "subsonic", "internet-radio", "youtube"]
local-files = ["audio-decode", "dep:lofty", "dep:symphonia"]
subsonic = ["dep:md-5", "audio-decode", "dep:tempfile", "reqwest/stream"]
internet-radio = ["audio-decode", "dep:stream-download", "dep:icy-metadata"]
# YouTube via an external `yt-dlp` binary (no new crates): search with
# `--flat-playlist --dump-json`, download the AAC/M4A audio (format 140, which
# rodio's default `mp4` feature decodes) to a tempfile, play through the shared
# LocalPlayer. Unofficial-fragile by design — see plan-multi-source.md.
youtube = ["audio-decode", "dep:tempfile"]
# --- AI DJ / MCP -------------------------------------------------------------
# Shared tool layer behind both front doors: the taste brief built from the local
# listening history, the name -> URI resolver, and the bulk enqueue. Pure logic,
# no new crates (reqwest / tokio "full" / serde_json are already non-optional).
# Pulled by `mcp-server` and `ai-dj` the same way `audio-decode` is pulled by the
# media sources.
dj-core = []
# Expose the player and listening history as MCP tools over a `spotatui mcp`
# stdio server, so Claude Code / Codex / any MCP client can drive playback. No
# LLM client and no API key are compiled in: `--features mcp-server` alone is a
# complete "drive it from your coding agent" build.
mcp-server = ["dj-core"]
# The in-TUI DJ: a DJ screen with a chat prompt, continuous auto-queue and a
# vibe-shift key, backed by a locally-installed agent CLI, an API key, or a local
# model. Deliberately not in `default`: it egresses to a third-party provider.
ai-dj = ["dj-core"]
[dev-dependencies]
tempfile = "3"
# Constraint-only pin (spotatui has no build script): vergen 9.1.0 breaks
# librespot-core 0.8.0's build script on a fresh resolve (#350). Remove once
# librespot ships a fix.
[build-dependencies]
vergen = { version = ">=9.0.6, <9.1", default-features = false }
[target.'cfg(target_env = "musl")'.dependencies]
openssl-sys = { version = "0.9", features = ["vendored"] }
[[bin]]
bench = false
path = "src/main.rs"
name = "spotatui"
[profile.release]
codegen-units = 1 # Better optimization
lto = true # Link-time optimization
opt-level = "s" # Prioritize small binary size
strip = true # Remove debug symbols
[package.metadata.deb]
maintainer = "LargeModGames <LargeModGames@gmail.com>"
copyright = "2025, LargeModGames"
license-file = ["LICENSE", "4"]
depends = "$auto"
# The YouTube source shells out to yt-dlp at runtime (ffmpeg improves the
# downloaded audio container); both are optional — spotatui works without them.
suggests = "yt-dlp, ffmpeg"
section = "utils"
priority = "optional"
# cargo-binstall: fetch the prebuilt release binaries instead of compiling.
# cargo binstall spotatui
# Our release assets use friendly names (spotatui-<os>-<arch>) rather than the
# Rust target triple, and the binary sits at the archive root, so every target
# needs an explicit override with an archive-root bin-dir.
[package.metadata.binstall]
bin-dir = "{ bin }{ binary-ext }"
[package.metadata.binstall.overrides.x86_64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/spotatui-linux-x86_64.tar.gz"
pkg-fmt = "tgz"
[package.metadata.binstall.overrides.aarch64-unknown-linux-gnu]
pkg-url = "{ repo }/releases/download/v{ version }/spotatui-linux-aarch64.tar.gz"
pkg-fmt = "tgz"
[package.metadata.binstall.overrides.x86_64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/spotatui-macos-x86_64.tar.gz"
pkg-fmt = "tgz"
[package.metadata.binstall.overrides.aarch64-apple-darwin]
pkg-url = "{ repo }/releases/download/v{ version }/spotatui-macos-aarch64.tar.gz"
pkg-fmt = "tgz"
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
pkg-url = "{ repo }/releases/download/v{ version }/spotatui-windows-x86_64.zip"
pkg-fmt = "zip"
# Native streaming fixes: librespot 0.8.0 + backport of upstream PR #1722 (fall back to the
# next CDN URL when one returns a non-206 status, e.g. HTTP 530) + port of upstream PR #1692
# (dealer reconnects handled in place without restarting spirc; prompt spirc-task exit with
# saved playback state on session TCP loss instead of hanging, enabling seamless recovery)
# + cherry-pick of upstream PR #1706 and follow-up hardening of the OAuth callback listener,
# which used to accept one connection and give up if it was not the redirect, closing port
# 8989 before the real callback arrived (issue #414, upstream issue #1705).
# Pinned to our maintained fork (spotatui-librespot) until a fixed librespot is published to
# crates.io. Pinned by immutable `rev` (not a branch) so release builds stay reproducible even
# as the `spotatui` branch advances. Note: crates.io publishes ignore [patch], so `cargo install`
# users need the upstream release.
[patch.crates-io]
librespot-core = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }
librespot-audio = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }
librespot-playback = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }
librespot-connect = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }
librespot-oauth = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }
librespot-metadata = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }
librespot-protocol = { git = "https://github.com/LargeModGames/spotatui-librespot", rev = "514061e4df71f5827105611eab1b2b92a87c430a" }