ttyplayer 0.4.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/PKG-INFO +112 -3
  2. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/README.md +109 -2
  3. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/pyproject.toml +3 -1
  4. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/pyproject.toml.orig +3 -1
  5. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/cli.py +238 -23
  6. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/player.py +125 -14
  7. ttyplayer-0.6.0/src/ttyplayer/remote.py +302 -0
  8. ttyplayer-0.6.0/src/ttyplayer/server.py +479 -0
  9. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/settings.py +12 -0
  10. ttyplayer-0.6.0/src/ttyplayer/spotify.py +337 -0
  11. ttyplayer-0.6.0/src/ttyplayer/static/icon.svg +6 -0
  12. ttyplayer-0.6.0/src/ttyplayer/static/index.html +78 -0
  13. ttyplayer-0.6.0/src/ttyplayer/static/manifest.webmanifest +14 -0
  14. ttyplayer-0.6.0/src/ttyplayer/static/remote.css +203 -0
  15. ttyplayer-0.6.0/src/ttyplayer/static/remote.js +440 -0
  16. ttyplayer-0.6.0/src/ttyplayer/stream.py +233 -0
  17. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/tui.py +43 -3
  18. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/tui.tcss +14 -1
  19. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/utils.py +12 -2
  20. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/LICENSE +0 -0
  21. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/__init__.py +0 -0
  22. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/control.py +0 -0
  23. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/favorites.py +0 -0
  24. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/history.py +0 -0
  25. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/models.py +0 -0
  26. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/playlists.py +0 -0
  27. {ttyplayer-0.4.0 → ttyplayer-0.6.0}/src/ttyplayer/youtube.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ttyplayer
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: ttyplayer: a modern YouTube player for the terminal — search, queue, and play audio or video via mpv
5
5
  Keywords: youtube,music,player,mpv,cli,terminal,tui,yt-dlp
6
6
  License-Expression: MIT
@@ -13,6 +13,8 @@ Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Topic :: Multimedia :: Sound/Audio :: Players
16
+ Requires-Dist: aiohttp>=3.10
17
+ Requires-Dist: qrcode>=8
16
18
  Requires-Dist: textual>=8.2
17
19
  Requires-Dist: typer>=0.27.2
18
20
  Requires-Dist: yt-dlp>=2026.8.19
@@ -44,12 +46,17 @@ ttyplayer favorites --play pick from favorites and play them
44
46
  ttyplayer favorites --remove 2 drop the second favorite as listed
45
47
  ttyplayer favorites --clear forget all favorites
46
48
  ttyplayer playlist ... your own named playlists (see Playlists below)
49
+ ttyplayer spotify ... bring Spotify playlists over, found on YouTube (see Spotify below)
47
50
  ttyplayer tui [--video] full-screen: search box, results list, now-playing bar
51
+ ttyplayer serve [--host] [--port] play headless, driven over HTTP from any device (see Server below)
52
+ ttyplayer serve --stream the same, but the sound goes to the web remote, not the speakers
48
53
  ttyplayer config list the settings (see Settings below)
49
- ttyplayer doctor check Python, yt-dlp, mpv and ttyplayer's folders
54
+ ttyplayer doctor check Python, yt-dlp, mpv, ffmpeg (optional) and ttyplayer's folders
50
55
  ttyplayer version
51
56
  ```
52
57
 
58
+ Channels and playlists in search results are skipped; a track that cannot be played is reported and skipped.
59
+
53
60
  While ttyplayer plays in one terminal, any other terminal can drive it:
54
61
 
55
62
  ```
@@ -97,10 +104,33 @@ Picks accept several numbers at once: `1 3 5` queues those three in that order.
97
104
 
98
105
  `TTYPLAYER_TIMING=1 ttyplayer play <words>` prints how long the YouTube lookup took and adds `started in 2.4s` (from `loadfile` to the first sound) to the status line.
99
106
 
107
+ ## Spotify
108
+
109
+ ttyplayer can bring a Spotify playlist over as one of its own playlists. It reads only the playlist's *track list* from Spotify, never its audio (Spotify's audio is DRM-protected): each track is searched on YouTube as `<first artist> <title>`, and the first hit is added. A track YouTube has no hit for is reported and skipped; local files and podcast episodes in the playlist are skipped too. The result is a normal playlist: `ttyplayer playlist play <name>`, or the Playlists tab in the TUI.
110
+
111
+ ```
112
+ ttyplayer spotify login log in to Spotify in the browser, once
113
+ ttyplayer spotify playlists your Spotify playlists: name, tracks, id
114
+ ttyplayer spotify import <link | id> [--as <name>] [--limit N] find each track on YouTube and save it
115
+ ```
116
+
117
+ `import` takes a playlist link (`https://open.spotify.com/playlist/<id>`), a `spotify:playlist:<id>` URI, or the bare id. It names the playlist after the Spotify one (unless `--as`), creating it, or appending to it if one by that name exists; it prints `[3/40] ✓ Artist – Title → <YouTube title>` (or `✗ … not found`) per track, and `Saved N of M tracks to <name>` at the end. Each YouTube search takes a second or two, so a long playlist takes a while; `--limit N` imports only the first N tracks. If the network fails part way, what was saved stays saved.
118
+
119
+ Logging in needs a Spotify app of your own (free, once):
120
+
121
+ 1. Open the [Spotify developer dashboard](https://developer.spotify.com/dashboard), log in, and press **Create app**.
122
+ 2. Give it any name and description, set the **Redirect URI** to exactly `http://127.0.0.1:8765/callback`, tick **Web API**, and save.
123
+ 3. Open the app's **Settings**, copy its **Client ID**, and run `ttyplayer config set spotify_client_id <client id>`.
124
+ 4. Run `ttyplayer spotify login`: the browser opens Spotify's consent page; approve it, and the terminal says `Logged in as <your name>`.
125
+
126
+ ttyplayer asks only for read access to your playlists (`playlist-read-private playlist-read-collaborative`) and uses no client secret (the login is OAuth with PKCE). The login is kept in `spotify.json` next to `settings.toml`, readable only by you; it holds a refresh token, so treat it like a password. To revoke ttyplayer's access, remove the app under **Manage apps** on your Spotify account page (spotify.com/account/apps) and delete `spotify.json`.
127
+
100
128
  ## TUI
101
129
 
102
130
  `ttyplayer tui` opens a full-screen player: a search box, Search / Queue / History / Favorites / Playlists tabs, and a now-playing panel. Type a search or paste a link and press Enter. Ctrl-P opens the command palette (search, playlists, save queue as playlist, next theme, settings, help, quit, pause, next, previous, mute, and Textual's own theme picker); `?` lists every key and command.
103
131
 
132
+ Under the volume, the panel's level meter shows two bars, `L` and `R`, that follow the sound's peaks about ten times a second (empty at −60 dBFS and below, full at 0 dBFS; empty while paused). `ttyplayer config set show_levels false`, or Enter on `show_levels` in the Settings screen, hides them and takes mpv's measuring filter out.
133
+
104
134
  | Where | Key | Action |
105
135
  |---|---|---|
106
136
  | table | `↑` / `↓` | move through the list (volume is `-` / `+`) |
@@ -148,6 +178,13 @@ ttyplayer keeps its preferences in `~/.config/ttyplayer/settings.toml` (`$XDG_CO
148
178
  | `show_clock` | `true` | the clock in the TUI's header |
149
179
  | `theme` | `textual-dark` | the TUI's color theme; `t` in the TUI picks the next one and saves it |
150
180
  | `search_limit` | `10` | how many results a TUI search fetches, and `m` adds (1–50) |
181
+ | `server_host` | `127.0.0.1` | the address `ttyplayer serve` listens on; `0.0.0.0` opens it to the network |
182
+ | `server_port` | `7700` | the port `ttyplayer serve` listens on (1–65535) |
183
+ | `server_token` | *generated* | the token every API request needs; `ttyplayer serve` makes one on first use |
184
+ | `remote_url` | *none* | the server `ttyplayer tui` drives instead of playing itself, e.g. `http://host:7700` |
185
+ | `stream_enabled` | `false` | `ttyplayer serve` streams the sound to the web remote instead of playing it, as `--stream` does |
186
+ | `spotify_client_id` | *none* | the Client ID of your own Spotify app, which `ttyplayer spotify login` needs (see Spotify above) |
187
+ | `show_levels` | `true` | the TUI's level meter: mpv measures the sound's peaks and the now-playing panel shows them |
151
188
 
152
189
  ```
153
190
  ttyplayer config every setting, (default) when unchanged
@@ -156,7 +193,79 @@ ttyplayer config set <key> <value> change it: ttyplayer config set show_clock
156
193
  ttyplayer config path where the file is
157
194
  ```
158
195
 
159
- In the TUI, `S` (or Settings… in Ctrl-P) lists the settings: Enter on a true / false one flips it and saves it (the clock shows or hides at once); the others are set with `ttyplayer config set`.
196
+ In the TUI, `S` (or Settings… in Ctrl-P) lists the settings: Enter on a true / false one flips it and saves it (the clock and the level meter show or hide at once); the others are set with `ttyplayer config set`.
197
+
198
+ ## Server
199
+
200
+ `ttyplayer serve` runs the player headless on the machine with the speakers (no keyboard, no TUI) and lets any device on the network drive it through a small HTTP + WebSocket API. The other terminals' `ttyplayer pause`, `next`, `status` and `stop` keep working too.
201
+
202
+ ```
203
+ ttyplayer serve listen on 127.0.0.1:7700 (this machine only)
204
+ ttyplayer serve --host 0.0.0.0 listen on every address, so a phone on the LAN can reach it
205
+ ttyplayer serve --port 8000 another port
206
+ ttyplayer serve --stream no sound here: Listen here on the web remote plays it (see below)
207
+ ```
208
+
209
+ It prints the address to open, with the token, and a QR code of it for a phone:
210
+
211
+ ```
212
+ Serving ttyplayer at http://192.168.1.20:7700/?token=…
213
+ ```
214
+
215
+ Open that address on a phone and it is a remote: what plays (with a moving progress bar), previous / play-pause / next, a volume slider and mute, a search box (a pasted link plays at once; each result has **Play** and **Queue**), the queue (tap a row to jump there, **×** removes it, **Clear** keeps only what plays), your favorites (the heart on any row adds or drops one) and your playlists. It follows the phone's dark or light mode, reconnects by itself when the server restarts, and "Add to Home Screen" makes it an app icon. The page keeps the token only for that browser tab; opened without one (from the home screen, say) it asks you to paste the token `serve` printed. Ctrl-C (or `ttyplayer stop`, or a `stop` command) stops the server and the player.
216
+
217
+ Every `/api/…` request and the socket need the token, as `Authorization: Bearer <token>` or `?token=<token>`; without it the reply is `401 {"error": "unauthorized"}`. Replies are JSON; errors are `{"error": "…"}`.
218
+
219
+ ```
220
+ curl -H "Authorization: Bearer $TOKEN" http://host:7700/api/status
221
+ curl -H "Authorization: Bearer $TOKEN" -d '{"query": "lofi beats"}' http://host:7700/api/play
222
+ curl -H "Authorization: Bearer $TOKEN" -d '{"name": "volume", "value": -5}' http://host:7700/api/command
223
+ ```
224
+
225
+ | Method | Path | Body / reply |
226
+ |---|---|---|
227
+ | GET | `/api/status` | the player's status, plus `queue` (the videos) and `index` (1-based) |
228
+ | POST | `/api/play` | `{"url": "…"}` or `{"query": "…"}`: the link's videos, or the first search result, replace the queue and play |
229
+ | POST | `/api/queue` | `{"url": "…"}` or `{"query": "…"}`: appended to the queue (played at once when nothing plays) |
230
+ | POST | `/api/command` | `{"name": "pause"\|"next"\|"prev"\|"stop"\|"mute"\|"seek"\|"volume"\|"jump"\|"remove"\|"move"\|"clear_others", "value"?}`; `seek` and `volume` take a number of seconds / steps, `jump` and `remove` a 0-based queue row, `move` two (`[from, to]`; the current track stays current); `clear_others` keeps only the current track → the new status |
231
+ | GET | `/api/commands` | the command names `/api/command` takes |
232
+ | GET | `/api/search?q=…` | the search results (`search_limit` of them) |
233
+ | GET | `/api/favorites` | the favorites, newest first |
234
+ | POST | `/api/favorites/<id>` | unfavorites that video, or favorites it (the body is the video: `{"title", "uploader", "duration"}`) → the favorites |
235
+ | GET | `/api/playlists` | `[{"name": …, "count": …}]` |
236
+ | POST | `/api/playlists/<name>/play` | that playlist becomes the queue |
237
+ | GET, PATCH | `/api/settings` | every setting but the token; PATCH `{"key": value}` changes and saves them, checked like `config set` |
238
+ | WS | `/ws?token=…` | sends the status on connect and on every change; takes the same `{"name", "value"?}` commands as `/api/command` and answers each with the status |
239
+ | GET | `/stream` | with `serve --stream`: the sound as an `audio/ogg` (Opus) stream, for as long as the client listens; 404 otherwise |
240
+ | GET | `/` | the web remote (needs no token itself; it reads the token from its address); its files are under `/static/`, plus `/manifest.webmanifest` |
241
+
242
+ ### Listen on another device
243
+
244
+ `ttyplayer serve --stream` is the "music box on a server" mode, for a machine with no speakers of its own (a Raspberry Pi, a VPS, a closet PC): nothing plays on the server; the web remote gets a **Listen here** button that plays what the server plays, in that browser, on any device. Press it again (**Stop listening**) to stop. Several devices can listen at once; one on a bad connection skips, the others do not. Pausing and changing tracks keep the stream open (you hear silence in between). What you hear runs a little behind the remote's controls: the server adds about half a second, the browser its own buffer. `ttyplayer config set stream_enabled true` makes it the default for `serve`.
245
+
246
+ ```
247
+ ttyplayer serve --stream --host 0.0.0.0
248
+ ```
249
+
250
+ It needs ffmpeg, which nothing else in ttyplayer does (`ttyplayer doctor` shows whether it is there), and runs on macOS and Linux, not Windows:
251
+
252
+ | System | Install ffmpeg |
253
+ |---|---|
254
+ | macOS | `brew install ffmpeg` |
255
+ | Debian, Ubuntu, Raspberry Pi OS | `sudo apt-get install -y ffmpeg` |
256
+ | Fedora | `sudo dnf install -y ffmpeg-free` |
257
+ | Arch | `sudo pacman -S --noconfirm ffmpeg` |
258
+ | Alpine | `sudo apk add ffmpeg` |
259
+
260
+ The stream is gated by the same token as the API and is plain HTTP; the security notes below apply to it too.
261
+
262
+ ### The TUI as a remote
263
+
264
+ `ttyplayer tui --remote http://host:7700` opens the same screen, with the same keys and tabs, on another machine (a laptop, say) while the server plays: Enter on a result makes the *server's* speakers play, and the now-playing panel and the Queue tab follow the server over its socket (the header says `remote: host:7700`). The token is the `server_token` setting, so copy it into the laptop's settings with `ttyplayer config set server_token <token>`; `--token <token>` works too, but leaves it in your shell history. `ttyplayer config set remote_url http://host:7700` makes the remote the default, so a plain `ttyplayer tui` drives it.
265
+
266
+ Searching still happens on the laptop; the server looks each picked video up again before it plays or queues it, so a long queue fills in over a few seconds. History stays the server's: the laptop records nothing. When the server cannot be reached the TUI says so once and keeps trying; a refused token says `Server rejected the token`.
267
+
268
+ Security: the token is the only gate, and plain HTTP carries it in clear text. That is fine on a home network you trust; it is why `serve` listens on 127.0.0.1 unless told otherwise. To reach it beyond your LAN (a VPS, say), put it behind a reverse proxy with HTTPS. Anyone with the token can control the player, including stopping it; change the token with `ttyplayer config set server_token <new>` and restart.
160
269
 
161
270
  ## Install
162
271
 
@@ -21,12 +21,17 @@ ttyplayer favorites --play pick from favorites and play them
21
21
  ttyplayer favorites --remove 2 drop the second favorite as listed
22
22
  ttyplayer favorites --clear forget all favorites
23
23
  ttyplayer playlist ... your own named playlists (see Playlists below)
24
+ ttyplayer spotify ... bring Spotify playlists over, found on YouTube (see Spotify below)
24
25
  ttyplayer tui [--video] full-screen: search box, results list, now-playing bar
26
+ ttyplayer serve [--host] [--port] play headless, driven over HTTP from any device (see Server below)
27
+ ttyplayer serve --stream the same, but the sound goes to the web remote, not the speakers
25
28
  ttyplayer config list the settings (see Settings below)
26
- ttyplayer doctor check Python, yt-dlp, mpv and ttyplayer's folders
29
+ ttyplayer doctor check Python, yt-dlp, mpv, ffmpeg (optional) and ttyplayer's folders
27
30
  ttyplayer version
28
31
  ```
29
32
 
33
+ Channels and playlists in search results are skipped; a track that cannot be played is reported and skipped.
34
+
30
35
  While ttyplayer plays in one terminal, any other terminal can drive it:
31
36
 
32
37
  ```
@@ -74,10 +79,33 @@ Picks accept several numbers at once: `1 3 5` queues those three in that order.
74
79
 
75
80
  `TTYPLAYER_TIMING=1 ttyplayer play <words>` prints how long the YouTube lookup took and adds `started in 2.4s` (from `loadfile` to the first sound) to the status line.
76
81
 
82
+ ## Spotify
83
+
84
+ ttyplayer can bring a Spotify playlist over as one of its own playlists. It reads only the playlist's *track list* from Spotify, never its audio (Spotify's audio is DRM-protected): each track is searched on YouTube as `<first artist> <title>`, and the first hit is added. A track YouTube has no hit for is reported and skipped; local files and podcast episodes in the playlist are skipped too. The result is a normal playlist: `ttyplayer playlist play <name>`, or the Playlists tab in the TUI.
85
+
86
+ ```
87
+ ttyplayer spotify login log in to Spotify in the browser, once
88
+ ttyplayer spotify playlists your Spotify playlists: name, tracks, id
89
+ ttyplayer spotify import <link | id> [--as <name>] [--limit N] find each track on YouTube and save it
90
+ ```
91
+
92
+ `import` takes a playlist link (`https://open.spotify.com/playlist/<id>`), a `spotify:playlist:<id>` URI, or the bare id. It names the playlist after the Spotify one (unless `--as`), creating it, or appending to it if one by that name exists; it prints `[3/40] ✓ Artist – Title → <YouTube title>` (or `✗ … not found`) per track, and `Saved N of M tracks to <name>` at the end. Each YouTube search takes a second or two, so a long playlist takes a while; `--limit N` imports only the first N tracks. If the network fails part way, what was saved stays saved.
93
+
94
+ Logging in needs a Spotify app of your own (free, once):
95
+
96
+ 1. Open the [Spotify developer dashboard](https://developer.spotify.com/dashboard), log in, and press **Create app**.
97
+ 2. Give it any name and description, set the **Redirect URI** to exactly `http://127.0.0.1:8765/callback`, tick **Web API**, and save.
98
+ 3. Open the app's **Settings**, copy its **Client ID**, and run `ttyplayer config set spotify_client_id <client id>`.
99
+ 4. Run `ttyplayer spotify login`: the browser opens Spotify's consent page; approve it, and the terminal says `Logged in as <your name>`.
100
+
101
+ ttyplayer asks only for read access to your playlists (`playlist-read-private playlist-read-collaborative`) and uses no client secret (the login is OAuth with PKCE). The login is kept in `spotify.json` next to `settings.toml`, readable only by you; it holds a refresh token, so treat it like a password. To revoke ttyplayer's access, remove the app under **Manage apps** on your Spotify account page (spotify.com/account/apps) and delete `spotify.json`.
102
+
77
103
  ## TUI
78
104
 
79
105
  `ttyplayer tui` opens a full-screen player: a search box, Search / Queue / History / Favorites / Playlists tabs, and a now-playing panel. Type a search or paste a link and press Enter. Ctrl-P opens the command palette (search, playlists, save queue as playlist, next theme, settings, help, quit, pause, next, previous, mute, and Textual's own theme picker); `?` lists every key and command.
80
106
 
107
+ Under the volume, the panel's level meter shows two bars, `L` and `R`, that follow the sound's peaks about ten times a second (empty at −60 dBFS and below, full at 0 dBFS; empty while paused). `ttyplayer config set show_levels false`, or Enter on `show_levels` in the Settings screen, hides them and takes mpv's measuring filter out.
108
+
81
109
  | Where | Key | Action |
82
110
  |---|---|---|
83
111
  | table | `↑` / `↓` | move through the list (volume is `-` / `+`) |
@@ -125,6 +153,13 @@ ttyplayer keeps its preferences in `~/.config/ttyplayer/settings.toml` (`$XDG_CO
125
153
  | `show_clock` | `true` | the clock in the TUI's header |
126
154
  | `theme` | `textual-dark` | the TUI's color theme; `t` in the TUI picks the next one and saves it |
127
155
  | `search_limit` | `10` | how many results a TUI search fetches, and `m` adds (1–50) |
156
+ | `server_host` | `127.0.0.1` | the address `ttyplayer serve` listens on; `0.0.0.0` opens it to the network |
157
+ | `server_port` | `7700` | the port `ttyplayer serve` listens on (1–65535) |
158
+ | `server_token` | *generated* | the token every API request needs; `ttyplayer serve` makes one on first use |
159
+ | `remote_url` | *none* | the server `ttyplayer tui` drives instead of playing itself, e.g. `http://host:7700` |
160
+ | `stream_enabled` | `false` | `ttyplayer serve` streams the sound to the web remote instead of playing it, as `--stream` does |
161
+ | `spotify_client_id` | *none* | the Client ID of your own Spotify app, which `ttyplayer spotify login` needs (see Spotify above) |
162
+ | `show_levels` | `true` | the TUI's level meter: mpv measures the sound's peaks and the now-playing panel shows them |
128
163
 
129
164
  ```
130
165
  ttyplayer config every setting, (default) when unchanged
@@ -133,7 +168,79 @@ ttyplayer config set <key> <value> change it: ttyplayer config set show_clock
133
168
  ttyplayer config path where the file is
134
169
  ```
135
170
 
136
- In the TUI, `S` (or Settings… in Ctrl-P) lists the settings: Enter on a true / false one flips it and saves it (the clock shows or hides at once); the others are set with `ttyplayer config set`.
171
+ In the TUI, `S` (or Settings… in Ctrl-P) lists the settings: Enter on a true / false one flips it and saves it (the clock and the level meter show or hide at once); the others are set with `ttyplayer config set`.
172
+
173
+ ## Server
174
+
175
+ `ttyplayer serve` runs the player headless on the machine with the speakers (no keyboard, no TUI) and lets any device on the network drive it through a small HTTP + WebSocket API. The other terminals' `ttyplayer pause`, `next`, `status` and `stop` keep working too.
176
+
177
+ ```
178
+ ttyplayer serve listen on 127.0.0.1:7700 (this machine only)
179
+ ttyplayer serve --host 0.0.0.0 listen on every address, so a phone on the LAN can reach it
180
+ ttyplayer serve --port 8000 another port
181
+ ttyplayer serve --stream no sound here: Listen here on the web remote plays it (see below)
182
+ ```
183
+
184
+ It prints the address to open, with the token, and a QR code of it for a phone:
185
+
186
+ ```
187
+ Serving ttyplayer at http://192.168.1.20:7700/?token=…
188
+ ```
189
+
190
+ Open that address on a phone and it is a remote: what plays (with a moving progress bar), previous / play-pause / next, a volume slider and mute, a search box (a pasted link plays at once; each result has **Play** and **Queue**), the queue (tap a row to jump there, **×** removes it, **Clear** keeps only what plays), your favorites (the heart on any row adds or drops one) and your playlists. It follows the phone's dark or light mode, reconnects by itself when the server restarts, and "Add to Home Screen" makes it an app icon. The page keeps the token only for that browser tab; opened without one (from the home screen, say) it asks you to paste the token `serve` printed. Ctrl-C (or `ttyplayer stop`, or a `stop` command) stops the server and the player.
191
+
192
+ Every `/api/…` request and the socket need the token, as `Authorization: Bearer <token>` or `?token=<token>`; without it the reply is `401 {"error": "unauthorized"}`. Replies are JSON; errors are `{"error": "…"}`.
193
+
194
+ ```
195
+ curl -H "Authorization: Bearer $TOKEN" http://host:7700/api/status
196
+ curl -H "Authorization: Bearer $TOKEN" -d '{"query": "lofi beats"}' http://host:7700/api/play
197
+ curl -H "Authorization: Bearer $TOKEN" -d '{"name": "volume", "value": -5}' http://host:7700/api/command
198
+ ```
199
+
200
+ | Method | Path | Body / reply |
201
+ |---|---|---|
202
+ | GET | `/api/status` | the player's status, plus `queue` (the videos) and `index` (1-based) |
203
+ | POST | `/api/play` | `{"url": "…"}` or `{"query": "…"}`: the link's videos, or the first search result, replace the queue and play |
204
+ | POST | `/api/queue` | `{"url": "…"}` or `{"query": "…"}`: appended to the queue (played at once when nothing plays) |
205
+ | POST | `/api/command` | `{"name": "pause"\|"next"\|"prev"\|"stop"\|"mute"\|"seek"\|"volume"\|"jump"\|"remove"\|"move"\|"clear_others", "value"?}`; `seek` and `volume` take a number of seconds / steps, `jump` and `remove` a 0-based queue row, `move` two (`[from, to]`; the current track stays current); `clear_others` keeps only the current track → the new status |
206
+ | GET | `/api/commands` | the command names `/api/command` takes |
207
+ | GET | `/api/search?q=…` | the search results (`search_limit` of them) |
208
+ | GET | `/api/favorites` | the favorites, newest first |
209
+ | POST | `/api/favorites/<id>` | unfavorites that video, or favorites it (the body is the video: `{"title", "uploader", "duration"}`) → the favorites |
210
+ | GET | `/api/playlists` | `[{"name": …, "count": …}]` |
211
+ | POST | `/api/playlists/<name>/play` | that playlist becomes the queue |
212
+ | GET, PATCH | `/api/settings` | every setting but the token; PATCH `{"key": value}` changes and saves them, checked like `config set` |
213
+ | WS | `/ws?token=…` | sends the status on connect and on every change; takes the same `{"name", "value"?}` commands as `/api/command` and answers each with the status |
214
+ | GET | `/stream` | with `serve --stream`: the sound as an `audio/ogg` (Opus) stream, for as long as the client listens; 404 otherwise |
215
+ | GET | `/` | the web remote (needs no token itself; it reads the token from its address); its files are under `/static/`, plus `/manifest.webmanifest` |
216
+
217
+ ### Listen on another device
218
+
219
+ `ttyplayer serve --stream` is the "music box on a server" mode, for a machine with no speakers of its own (a Raspberry Pi, a VPS, a closet PC): nothing plays on the server; the web remote gets a **Listen here** button that plays what the server plays, in that browser, on any device. Press it again (**Stop listening**) to stop. Several devices can listen at once; one on a bad connection skips, the others do not. Pausing and changing tracks keep the stream open (you hear silence in between). What you hear runs a little behind the remote's controls: the server adds about half a second, the browser its own buffer. `ttyplayer config set stream_enabled true` makes it the default for `serve`.
220
+
221
+ ```
222
+ ttyplayer serve --stream --host 0.0.0.0
223
+ ```
224
+
225
+ It needs ffmpeg, which nothing else in ttyplayer does (`ttyplayer doctor` shows whether it is there), and runs on macOS and Linux, not Windows:
226
+
227
+ | System | Install ffmpeg |
228
+ |---|---|
229
+ | macOS | `brew install ffmpeg` |
230
+ | Debian, Ubuntu, Raspberry Pi OS | `sudo apt-get install -y ffmpeg` |
231
+ | Fedora | `sudo dnf install -y ffmpeg-free` |
232
+ | Arch | `sudo pacman -S --noconfirm ffmpeg` |
233
+ | Alpine | `sudo apk add ffmpeg` |
234
+
235
+ The stream is gated by the same token as the API and is plain HTTP; the security notes below apply to it too.
236
+
237
+ ### The TUI as a remote
238
+
239
+ `ttyplayer tui --remote http://host:7700` opens the same screen, with the same keys and tabs, on another machine (a laptop, say) while the server plays: Enter on a result makes the *server's* speakers play, and the now-playing panel and the Queue tab follow the server over its socket (the header says `remote: host:7700`). The token is the `server_token` setting, so copy it into the laptop's settings with `ttyplayer config set server_token <token>`; `--token <token>` works too, but leaves it in your shell history. `ttyplayer config set remote_url http://host:7700` makes the remote the default, so a plain `ttyplayer tui` drives it.
240
+
241
+ Searching still happens on the laptop; the server looks each picked video up again before it plays or queues it, so a long queue fills in over a few seconds. History stays the server's: the laptop records nothing. When the server cannot be reached the TUI says so once and keeps trying; a refused token says `Server rejected the token`.
242
+
243
+ Security: the token is the only gate, and plain HTTP carries it in clear text. That is fine on a home network you trust; it is why `serve` listens on 127.0.0.1 unless told otherwise. To reach it beyond your LAN (a VPS, say), put it behind a reverse proxy with HTTPS. Anyone with the token can control the player, including stopping it; change the token with `ttyplayer config set server_token <new>` and restart.
137
244
 
138
245
  ## Install
139
246
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ttyplayer"
3
- version = "0.4.0"
3
+ version = "0.6.0"
4
4
  description = "ttyplayer: a modern YouTube player for the terminal — search, queue, and play audio or video via mpv"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -27,6 +27,8 @@ classifiers = [
27
27
  "Topic :: Multimedia :: Sound/Audio :: Players",
28
28
  ]
29
29
  dependencies = [
30
+ "aiohttp>=3.10",
31
+ "qrcode>=8",
30
32
  "textual>=8.2",
31
33
  "typer>=0.27.2",
32
34
  "yt-dlp>=2026.8.19",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ttyplayer"
3
- version = "0.4.0"
3
+ version = "0.6.0"
4
4
  description = "ttyplayer: a modern YouTube player for the terminal — search, queue, and play audio or video via mpv"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -18,6 +18,8 @@ classifiers = [
18
18
  "Topic :: Multimedia :: Sound/Audio :: Players",
19
19
  ]
20
20
  dependencies = [
21
+ "aiohttp>=3.10",
22
+ "qrcode>=8",
21
23
  "textual>=8.2",
22
24
  "typer>=0.27.2",
23
25
  "yt-dlp>=2026.8.19",