Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 239 lines 7.9 kB view raw view rendered
1# rockbox-sdk 2 3[![PyPI](https://img.shields.io/pypi/v/rockbox-sdk?style=flat-square&logo=pypi&logoColor=white&color=3775A9)](https://pypi.org/project/rockbox-sdk/) 4[![Python](https://img.shields.io/pypi/pyversions/rockbox-sdk?style=flat-square&logo=python&logoColor=white&color=3776AB)](https://pypi.org/project/rockbox-sdk/) 5[![Downloads](https://img.shields.io/pypi/dm/rockbox-sdk?style=flat-square&logo=pypi&logoColor=white&color=blue)](https://pypi.org/project/rockbox-sdk/) 6[![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](./LICENSE) 7[![AsyncIO](https://img.shields.io/badge/asyncio-ready-brightgreen?style=flat-square&logo=python&logoColor=white)](https://docs.python.org/3/library/asyncio.html) 8[![Typed](https://img.shields.io/badge/typed-pydantic-e92063?style=flat-square&logo=pydantic&logoColor=white)](https://docs.pydantic.dev/) 9[![Ruff](https://img.shields.io/badge/lint-ruff-261230?style=flat-square&logo=ruff&logoColor=white)](https://github.com/astral-sh/ruff) 10[![mypy](https://img.shields.io/badge/mypy-strict-1F5082?style=flat-square)](http://mypy-lang.org/) 11[![GitHub](https://img.shields.io/badge/github-rockbox--zig-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/tsirysndr/rockbox-zig) 12 13Async Python SDK for [Rockbox Zig](https://github.com/tsirysndr/rockbox-zig) — a typed, batteries-included 14client for the GraphQL API exposed by `rockboxd`. 15 16```python 17import asyncio 18from rockbox_sdk import RockboxClient, PlaybackStatus 19 20async def main(): 21 async with RockboxClient(host="localhost") as client: 22 track = await client.playback.current_track() 23 if track: 24 print(f"Now: {track.title}{track.artist}") 25 if await client.playback.status() == PlaybackStatus.PAUSED: 26 await client.playback.resume() 27 28asyncio.run(main()) 29``` 30 31## Highlights 32 33- **Async-first** — built on `httpx` + `websockets`. Use `await` everywhere. 34- **Domain-namespaced API** — `client.playback.*`, `client.library.*`, `client.sound.*`, … 35- **Typed responses** — every reply is a Pydantic model with snake_case fields. 36- **Real-time events** — `connect()` opens a WebSocket and forwards 37 `track:changed` / `status:changed` / `playlist:changed` to listeners. 38- **Builder API** — `RockboxClient.builder().host(...).port(...).build()`. 39- **Plugin system** — Jellyfin-style install/uninstall lifecycle. 40- **Python-friendly** — context manager, decorator listeners, dataclass inputs. 41 42## Install 43 44```sh 45uv add rockbox-sdk 46# or 47pip install rockbox-sdk 48``` 49 50Requires Python 3.10+ and a running `rockboxd` (default port 6062). 51 52## Try it in the REPL 53 54The SDK is async-first. The recommended REPL is **IPython**`await` works at 55the top level, and you get tab-completion on models, inline docs with `?`, and 56`%timeit` for benchmarking: 57 58```sh 59uv run ipython 60``` 61 62```python 63In [1]: from rockbox_sdk import RockboxClient, PlaybackStatus 64In [2]: client = RockboxClient(host="localhost", port=6062) 65In [3]: await client.playback.status() 66Out[3]: <PlaybackStatus.PLAYING: 1> 67In [4]: track = await client.playback.current_track() 68In [5]: track.title, track.artist 69Out[5]: ('Money', 'Pink Floyd') 70In [6]: await client.sound.get_volume() 71Out[6]: VolumeInfo(volume=-12, min=-74, max=6) 72In [7]: await client.library.search("daft punk") 73In [8]: await client.aclose() 74``` 75 76You can also test offline — models, enums, and the builder don't need a server: 77 78```python 79In [1]: from rockbox_sdk import RockboxClient, Track, InsertPosition 80In [2]: Track.model_validate({"title": "Money", "albumArt": "x.jpg"}).album_art 81Out[2]: 'x.jpg' 82In [3]: RockboxClient.builder().host("nas.local").build()._config.resolve_http_url() 83Out[3]: 'http://nas.local:6062/graphql' 84``` 85 86If you prefer the stdlib REPL, `python -m asyncio` also supports top-level 87`await`, or wrap each call in `asyncio.run(...)` in a plain `python` session: 88 89```sh 90uv run python -m asyncio 91``` 92 93```python 94>>> from rockbox_sdk import RockboxClient 95>>> client = RockboxClient() 96>>> await client.playback.status() 97``` 98 99Subscriptions (`await client.connect()`) keep firing in the background between 100prompts in both REPLs. 101 102## Configure 103 104```python 105from rockbox_sdk import RockboxClient 106 107# Direct kwargs 108client = RockboxClient(host="192.168.1.42", port=6062) 109 110# Or fluent builder 111client = ( 112 RockboxClient.builder() 113 .host("nas.local") 114 .port(6062) 115 .timeout(15) 116 .build() 117) 118 119# Or full URL override 120client = RockboxClient( 121 http_url="http://nas.local:6062/graphql", 122 ws_url="ws://nas.local:6062/graphql", 123) 124``` 125 126Always call `await client.aclose()` when you're done — or use it as an 127async context manager: 128 129```python 130async with RockboxClient() as client: 131 ... 132``` 133 134## Domains 135 136| Namespace | What it does | 137| ------------------------ | ------------------------------------------------ | 138| `client.playback` | Transport (`play`/`pause`/`seek`), play helpers | 139| `client.library` | Albums, artists, tracks, search, likes, scan | 140| `client.playlist` | The active queue (insert/remove/shuffle/start) | 141| `client.saved_playlists` | Persistent playlists & folders | 142| `client.smart_playlists` | Rule-based playlists & listening stats | 143| `client.sound` | Volume control | 144| `client.settings` | Global EQ / replaygain / crossfade / shuffle / … | 145| `client.system` | Version, runtime info | 146| `client.browse` | Filesystem & UPnP browser | 147| `client.devices` | Cast / source device discovery | 148| `client.bluetooth` | Bluetooth pairing & scanning (Linux only) | 149 150## Real-time events 151 152```python 153from rockbox_sdk import RockboxClient, TRACK_CHANGED, STATUS_CHANGED 154 155async with RockboxClient() as client: 156 await client.connect() # opens the WebSocket 157 158 @client.on(TRACK_CHANGED) 159 async def on_track(track): 160 print(f"{track.title}{track.artist}") 161 162 @client.on(STATUS_CHANGED) 163 def on_status(raw_status): 164 print(f"◐ status = {raw_status}") 165 166 await asyncio.Event().wait() # run forever 167``` 168 169Convenience wrappers exist (`client.on_track_changed(...)`, 170`client.on_status_changed(...)`, `client.on_playlist_changed(...)`). 171 172## Plugins 173 174A plugin is anything matching the `RockboxPlugin` protocol — a name, version, 175`install(context)`, and optionally `uninstall()`: 176 177```python 178from rockbox_sdk import RockboxClient, PlaybackStatus, RockboxPlugin 179 180class SleepTimer: 181 name = "sleep-timer" 182 version = "1.0.0" 183 description = "Stop playback after N minutes" 184 185 def __init__(self, minutes: int) -> None: 186 self.minutes = minutes 187 self._task: asyncio.Task | None = None 188 189 def install(self, ctx): 190 async def fire(): 191 await asyncio.sleep(self.minutes * 60) 192 await ctx.query("mutation { hardStop }") 193 194 self._task = asyncio.create_task(fire()) 195 196 @ctx.events.on("status:changed") 197 def cancel_on_stop(status: int): 198 if status == PlaybackStatus.STOPPED and self._task: 199 self._task.cancel() 200 201 def uninstall(self): 202 if self._task: 203 self._task.cancel() 204 205async with RockboxClient() as client: 206 await client.connect() 207 await client.use(SleepTimer(30)) 208``` 209 210## Raw GraphQL escape hatch 211 212```python 213data = await client.query( 214 "query Volume { volume { volume min max } }" 215) 216``` 217 218## Examples 219 220See `examples/` for runnable scripts mirroring the TypeScript SDK examples: 221 222- `01-basic-playback.py` 223- `02-now-playing.py` 224- `03-library-search.py` 225- `04-queue-management.py` 226- `05-volume-control.py` 227- `06-plugin-sleep-timer.py` 228 229Run with: 230 231```sh 232uv run python examples/01_basic_playback.py 233``` 234 235--- 236 237## License 238 239MIT License. See [LICENSE](./LICENSE) for details.