Skip to main content

🔊 sonosify

CI Python PyPI License

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.

sonosify banner

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 (sonosify command, via the cli extra): 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

sonosify-0.2.0.tar.gz (9.8 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sonosify-0.2.0-py3-none-any.whl (47.0 kB view details)

Uploaded Python 3

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

Hashes for sonosify-0.2.0.tar.gz
Algorithm Hash digest
SHA256 fedbb8853df057278d90bd269ca4ae2ea6a6752c0244ea4fcd9fe63bedf35ce1
MD5 9b38eb6df85138162e5aa542642d910c
BLAKE2b-256 1f11c18892620213ccb244f45ce6335626e8e93186584c826532b96b822dc5c3

See more details on using hashes here.

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

Hashes for sonosify-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 26040f208d303a13065fac803accd928af091a57568320e8ca8822804414fd45
MD5 8794aa559209c24121fc9b9cb591af41
BLAKE2b-256 9734536c52e6e4990f1e4fd1b07d4d4259f11fb16c5de3eeee46c5b7216ee405

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

This release

0.2.0 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page