🔊 sonosify
Programmatic Python API for discovering and controlling Sonos speakers on a local network — plus an optional, agent-friendly command-line interface built on top of it.
This package ports the API core of steipete/sonoscli into Python. It ships two
layers you can use independently:
- Core library (
sonosify): an async Python API for discovery, playback, volume, queue, groups, favorites, and live UPnP event subscriptions. - CLI (
sonosifycommand, via thecliextra): a scriptable, JSON-first command-line interface designed to be driven by humans and automation/agents alike.
Runnable scripts covering discovery, playback, volume, queue, groups, favorites,
playback modes, and live events are in examples/.
Installation
Requires Python 3.13 or 3.14. Sonos speakers must be reachable on the same local network (SSDP discovery uses UDP multicast).
Core library only:
uv add sonosify
# or: pip install sonosify
With the CLI (adds Typer + Rich):
uv add "sonosify[cli]"
# or: pip install "sonosify[cli]"
From a local checkout of this repository:
uv pip install -e ".[cli]"
Setting up a development environment (dependency groups, tests, linting, pre-commit) is covered in CONTRIBUTING.md.
Core Python API
The core API is fully async and centers on two entry points: SonosController
for household-wide operations (discovery, groups, watching) and SonosClient
for talking to one speaker directly.
import asyncio
from sonosify import SonosController
async def main():
sonos = SonosController()
system = await sonos.discover()
print([speaker.room_name for speaker in system.speakers])
async with await sonos.client("Kitchen") as kitchen:
await kitchen.pause()
await kitchen.set_volume(25)
print(await kitchen.now_playing())
asyncio.run(main())
Lower-level direct device access is also available, bypassing discovery entirely:
import asyncio
from sonosify import SonosClient
async def main():
async with SonosClient("192.168.1.42") as speaker:
await speaker.play_uri("https://example.com/live.mp3", radio=True, title="Example Radio")
asyncio.run(main())
SonosClient covers transport control (play, pause, stop, next,
previous, seek), volume/mute (get_volume, set_volume, adjust_volume,
set_mute, toggle_mute), the queue (queue, enqueue_uri, clear_queue,
remove_queue_item, seek_queue), playback modes (set_shuffle, set_repeat,
set_crossfade, configure_sleep_timer), grouping (join, unjoin), favorites
(favorites, open_favorite), and generic playback (open, open_track).
Live updates use Sonos UPnP event subscriptions:
import asyncio
from sonosify import AVTransportEvent, RenderingControlEvent, SonosController
async def main():
sonos = SonosController()
async with sonos.watch("Kitchen") as watcher:
async for event in watcher:
match event:
case AVTransportEvent(transport_state=state, track=track):
print(state, track.title if track else "")
case RenderingControlEvent(volume=volume, muted=muted):
print(volume, muted)
asyncio.run(main())
The watch API starts a local HTTP callback server. Your OS firewall may ask whether Python can accept incoming connections.
Errors are typed and derive from SonosifyError: SpeakerNotFoundError,
AmbiguousSpeakerError (carries matches), DiscoveryError, NetworkError,
UPnPError (carries code/description), and UnsupportedFeatureError.
See examples/ for runnable scripts:
uv run python examples/discover.py
uv run python examples/now_playing.py Kitchen
uv run python examples/watch.py Kitchen
uv run python examples/play_radio.py Kitchen https://example.com/live.mp3 "Example Radio"
uv run python examples/resume_playback.py Kitchen
uv run python examples/volume.py Kitchen 25
uv run python examples/favorites.py Kitchen
uv run python examples/favorites.py Kitchen "Jazz FM"
uv run python examples/track_queue.py Kitchen
uv run python examples/track_queue.py Kitchen add "x-rincon-mp3radio://example.com/live.mp3"
uv run python examples/groups.py
uv run python examples/group.py join Kitchen "Living Room"
uv run python examples/group.py leave Kitchen
uv run python examples/playback_modes.py Kitchen shuffle on
uv run python examples/playback_modes.py Kitchen sleep 0:30:00
Command-line interface
Install the optional CLI dependencies (Typer + Rich) as shown above, then use
the sonosify command:
sonosify discover --format json
sonosify status --room Kitchen --format json
sonosify now-playing --room Kitchen
sonosify play --room Kitchen
sonosify pause --all
sonosify next --room Kitchen
sonosify previous --room Kitchen
sonosify set-volume 25 --room Kitchen
sonosify set-volume 20 --group "Living Room"
sonosify volume-up Kitchen 10
sonosify volume-down Kitchen 10
sonosify mute --room Kitchen --on
sonosify queue --room Kitchen
sonosify queue jump 7 --room Kitchen
sonosify queue remove 3 --room Kitchen
sonosify queue clear --room Kitchen
sonosify enqueue "x-rincon-mp3radio://example.com/live.mp3" --room Kitchen
sonosify open "https://example.com/live.mp3" --radio --title "Example Radio" --room Kitchen
sonosify track SPOTIFY_TRACK_ID --room Kitchen
sonosify favorites list --room Kitchen
sonosify favorites play "Jazz FM" --room Kitchen
sonosify seek 1:30 --room Kitchen
sonosify shuffle on --room Kitchen
sonosify repeat all --room Kitchen
sonosify crossfade on --room Kitchen
sonosify sleep 30m --room Kitchen
sonosify group "Living Room" --with Kitchen --with Office
sonosify ungroup --room Kitchen
sonosify groups --format json
sonosify ping --room Kitchen --format json
sonosify doctor --room Kitchen --format json
sonosify watch --room Kitchen --count 5 --format json
sonosify commands --format json
sonosify --version --format json
Targeting a speaker
Every speaker command accepts --room/-r and --ip. Existing positional room
arguments remain available for compatibility. --ip opens the device directly and
does not perform SSDP discovery. A successful discover refreshes the persistent
room-to-IP cache; exact room names use that cache and fall back to discovery on a
cache miss.
Default speaker
Set a default speaker once and omit the room name from then on:
sonosify config set --room Kitchen # or: sonosify config set --ip 192.168.1.42
sonosify config show # show current defaults
sonosify config clear # remove defaults
sonosify play # now targets Kitchen automatically
sonosify volume 25 # set Kitchen to 25
sonosify volume-up
An explicit room name or --ip on a command always overrides the configured default.
The config is stored as JSON in your platform's app-config directory.
The same defaults can be supplied without writing a file:
$env:SONOSIFY_ROOM = "Kitchen"
$env:SONOSIFY_IP = "192.168.1.42"
$env:SONOSIFY_FORMAT = "json"
$env:SONOSIFY_TIMEOUT = "5"
$env:SONOSIFY_DEBUG = "1"
Precedence is command-line flag, then environment, then config file.
Output format
Use --format before or after a command:
sonosify --format json discover # JSON speaker-list envelope
sonosify --format tsv queue --room Kitchen # tab-separated rows
sonosify status --format json # JSON object
plain (the default) renders rich tables and colored text for interactive use.
Machine-readable stdout contains only result data. Diagnostics and debug traces go
to stderr.
JSON output has schema_version: 1. Object/action commands add their fields beside
it. List commands use this stable envelope:
{"schema_version": 1, "items": [{"room": "Kitchen", "ip": "192.168.1.42"}]}
now-playing and status include numeric position_s and duration_s.
status combines playback state, volume, mute state, track, group identifier, and
timing in one call.
watch --format json emits one object per line (NDJSON). It can terminate itself
with --count N, --duration 10s, or --until PLAYING.
Errors and exit codes
In JSON mode, failures are emitted as a JSON object on stderr while stdout remains empty:
{"schema_version": 1, "error": "no speaker matching 'Kitcen'", "code": "speaker_not_found", "query": "Kitcen"}
Stable exit codes are:
| Exit | Meaning |
|---|---|
0 |
Success |
1 |
Other sonosify error |
2 |
Speaker not found |
3 |
Ambiguous speaker (matches is included in JSON) |
4 |
Discovery or network failure / timeout |
5 |
Sonos UPnP error (upnp_code and description are included) |
Favorites and media
favorites list lists the Sonos favorites configured for a speaker's household;
favorites play NAME plays one by exact title or unique substring match (an
ambiguous match is rejected with the candidate titles listed). open URL starts
playback of an arbitrary stream URL or Sonos-playable URI (--radio/--title set
radio metadata for plain stream URLs), and track TRACK_ID plays a track by id,
URI, or URL from an already-linked music service (--next enqueues as the
next track, --enqueue only enqueues without starting playback).
Queue, groups, and playback modes
Queue management is available through queue (show), enqueue, queue clear,
queue remove POSITION, and queue jump POSITION. group, ungroup, and
groups expose multi-room grouping. Playback automation includes in-track
seek, shuffle, repeat, crossfade, and the sleep timer.
The legacy overloaded volume command remains available. Agents should prefer the
unambiguous get-volume and set-volume commands.
Use sonosify commands --format json for recursive command/parameter
introspection and sonosify --version --format json for feature detection.
sonosify doctor runs a basic connectivity and service health check on a
speaker (round-trip latency plus a live volume read), separate from the plain
reachability check of ping.
Debugging
Add --debug to print the underlying SOAP request/response
traces to stderr:
sonosify --debug volume Kitchen
Playback of track and open works when the corresponding music service is
already linked on your Sonos household.
Exports are collected in sonosify.__init__ for library consumers. The agent CLI
work also adds RepeatMode, NetworkError, SonosClient.seek,
get_play_mode/set_play_mode, set_shuffle, set_repeat,
get_crossfade/set_crossfade, and configure_sleep_timer to the Python API.
Contributing
Bug reports and pull requests are welcome. See CONTRIBUTING.md for the development setup, test/lint commands, and what CI checks on every push.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sonosify-0.2.0.tar.gz.
File metadata
- Download URL: sonosify-0.2.0.tar.gz
- Upload date:
- Size: 9.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fedbb8853df057278d90bd269ca4ae2ea6a6752c0244ea4fcd9fe63bedf35ce1
|
|
| MD5 |
9b38eb6df85138162e5aa542642d910c
|
|
| BLAKE2b-256 |
1f11c18892620213ccb244f45ce6335626e8e93186584c826532b96b822dc5c3
|
File details
Details for the file sonosify-0.2.0-py3-none-any.whl.
File metadata
- Download URL: sonosify-0.2.0-py3-none-any.whl
- Upload date:
- Size: 47.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
26040f208d303a13065fac803accd928af091a57568320e8ca8822804414fd45
|
|
| MD5 |
8794aa559209c24121fc9b9cb591af41
|
|
| BLAKE2b-256 |
9734536c52e6e4990f1e4fd1b07d4d4259f11fb16c5de3eeee46c5b7216ee405
|