YTMD SDK
Python SDK for controlling YouTube Music Desktop App (YTMD) v2+ via its Companion Server API.
Requirements
- Python 3.8+
- YTMD v2.0.0 or later (Companion Server must be enabled in YTMD settings)
Installation
pip install ytmd-sdk
Quick Start
Your application is responsible for persisting and loading the token. Pass it to the SDK
via update_token().
import os
from ytmd_sdk import Events, YTMD, Parser
from time import sleep
TOKEN_PATH = "/path/to/token.txt"
# --- Your application loads the token ---
token = None
if os.path.exists(TOKEN_PATH):
token = open(TOKEN_PATH).read().strip() or None
# --- Pass it to the SDK ---
ytmd = YTMD("my-app-id", "My App Name", "1.0.0")
if token:
ytmd.update_token(token)
if not token or not ytmd.is_token_valid():
# Opens an approval popup in YTMD. The user has ~30 seconds to approve.
token = ytmd.authenticate()
# --- Your application persists the new token ---
with open(TOKEN_PATH, "w") as f:
f.write(token)
def on_connect():
print("Connected to YTMD")
def on_disconnect():
print("Disconnected from YTMD")
def on_state_update(data):
parser = Parser(data)
print(f"Track: {parser.video_state.title} by {parser.video_state.author}")
print(f"State: {parser.player_state.state}")
print(f"Progress: {parser.player_state.video_progress}s")
print(f"Volume: {parser.player_state.volume}")
print(f"Repeat: {parser.player_state.repeatMode}")
ytmd.register_event(Events.connect, on_connect)
ytmd.register_event(Events.disconnect, on_disconnect)
ytmd.register_event(Events.state_update, on_state_update)
ytmd.connect()
sleep(60)
If YTMD rejects the token (e.g. after a server restart), call revoke_token() to clear
it in-memory, then handle the file and re-authenticate in your application:
ytmd.revoke_token()
os.remove(TOKEN_PATH) # your application cleans up the stale file
token = ytmd.authenticate()
with open(TOKEN_PATH, "w") as f:
f.write(token)
API Reference
YTMD(app_id, app_name, app_version, host="127.0.0.1", port=9863, token=None, logging=False)
Constructs the client. All network calls use a shared requests.Session for connection pooling.
| Parameter | Type | Description |
|---|---|---|
| app_id | str | Unique identifier for your application (e.g. "my-app") |
| app_name | str | Human-readable application name shown in YTMD's approval popup |
| app_version | str | Application version string |
| host | str | YTMD host address (default "127.0.0.1") |
| port | int | YTMD Companion Server port (default 9863) |
| token | str | Pre-existing token; skips authenticate() if provided |
| logging | bool | Enable python-socketio debug logging (default False) |
Authentication
| Method | Returns | Description |
|---|---|---|
authenticate() |
str |
Full auth handshake — requests a code, waits for user approval in YTMD (~30s), returns token |
update_token(token) |
None |
Register a token on this instance and set the Authorization header |
revoke_token() |
None |
Clear the in-memory token and Authorization header; does not touch the filesystem |
is_token_valid() |
bool |
Returns False only on a definitive HTTP 401; treats network errors as valid to avoid unnecessary re-auth |
Token persistence (reading and writing token files) is the caller's responsibility.
The SDK provides update_token() to register a loaded token and revoke_token() to
clear it; file I/O should be implemented in your application layer.
Connection
| Method | Description |
|---|---|
register_event(event, callback) |
Bind a callback to a Socket.IO event (use Events.* constants) |
connect() |
Open a WebSocket connection to the YTMD realtime namespace |
update_endpoint(host, port=9863) |
Change the target host/port after construction (e.g. from user settings) |
Player Commands
| Method | Description |
|---|---|
toggle_playback() |
Play or pause |
play() |
Resume playback |
pause() |
Pause playback |
next() |
Skip to the next track |
previous() |
Go back to the previous track |
volume_up() |
Increase volume by 10% |
volume_down() |
Decrease volume by 10% |
set_volume(volume) |
Set volume (0-100) |
mute() |
Mute the player |
unmute() |
Unmute the player |
seek_to(time) |
Seek to a position in seconds |
shuffle() |
Toggle shuffle |
repeat_mode(mode) |
Set repeat mode: 0 = None, 1 = All, 2 = One |
play_index(index) |
Play a specific item in the queue by index |
toggle_like() |
Toggle the like status of the current track |
toggle_dislike() |
Toggle the dislike status of the current track |
change_video(video_id=None, playlist_id=None) |
Play a specific YouTube video or playlist by ID |
State and Playlists
| Method | Returns | Description |
|---|---|---|
get_state() |
Response |
Current player and video state as a JSON response |
get_playlists() |
Response |
User's playlists as a JSON response |
get_version() |
list[str] |
API versions supported by the connected YTMD instance |
Cover Art
| Method | Returns | Description |
|---|---|---|
fetch_cover_art(url, timeout=5) |
bytes |
Fetch raw image bytes from a thumbnail URL provided in the state update. Encoding (e.g. base64) is left to the caller. Raises requests.HTTPError on non-2xx responses. |
Events
Use the Events class constants when calling register_event():
| Constant | Socket.IO Event | Fired when |
|---|---|---|
Events.connect |
connect |
WebSocket connection established |
Events.disconnect |
disconnect |
WebSocket connection lost |
Events.connect_error |
connect_error |
Connection attempt failed |
Events.error |
error |
Server-side error |
Events.state_update |
state-update |
Player state changes (track, volume, position, etc.) |
Events.playlist_created |
playlist-created |
A new playlist was created |
Events.playlist_deleted |
playlist-deleted |
A playlist was deleted |
Parser
Parser(data) wraps the raw JSON dict received from a state-update event into typed objects.
parser = Parser(data)
# PlayerState fields
parser.player_state.state # "Playing", "Paused", "Buffering", or "Unknown"
parser.player_state.video_progress # float, seconds elapsed
parser.player_state.volume # int, 0-100
parser.player_state.muted # bool
parser.player_state.adPlaying # bool
parser.player_state.repeatMode # "None", "All", "One", or "Unknown"
parser.player_state.auto_play # bool
parser.player_state.isGenerating # bool
parser.player_state.isInfinite # bool
parser.player_state.selectedItemIndex # int, index of the active item in the queue
parser.player_state.queue # list[queueItem]
# VideoState fields
parser.video_state.id # str, YouTube video ID
parser.video_state.title # str
parser.video_state.author # str, channel/artist name
parser.video_state.album # str
parser.video_state.album_id # str
parser.video_state.channel_id # str
parser.video_state.like_status # str: "Like", "Dislike", "Indifferent", or "Unknown"
parser.video_state.duration_seconds # int
parser.video_state.thumbnails # list[Thumbnail], ordered smallest to largest
Each Thumbnail has .url (str), .width (int), and .height (int).
The last item in thumbnails is always the highest-resolution image available.
Change Log
1.2.1
Token persistence moved to the application layer
save_token(path), load_token(path), and clear_token(path) have been removed from
the YTMD class. Token file I/O is an application-level concern and does not belong in
a generic client library.
Callers are responsible for reading and writing the token file. Use update_token(token)
to register a loaded token on the SDK instance, and revoke_token() to clear it when a
token is rejected.
revoke_token()replacesclear_token(path)for the in-memory side: it setsself.token = Noneand removes theAuthorizationheader from the session. It does not touch the filesystem.
See the Quick Start section for a complete token persistence pattern.
1.2.0
Token lifecycle management (removed in 1.2.1 — see above)
The following methods were added in 1.2.0 and removed in 1.2.1 when token persistence was moved to the application layer:
save_token(path),load_token(path),clear_token(path)
Still present from this release:
is_token_valid()makes a lightweightGET /statecall to verify the current token is accepted by YTMD. It returnsFalseonly on a definitive HTTP 401; transient network failures returnTrueso a temporary connection drop does not force unnecessary re-authentication.
Cover art retrieval
-
fetch_cover_art(url, timeout=5)fetches raw image bytes from any thumbnail URL provided in thestate-updatepayload. The SDK's shared HTTP session is reused for connection pooling. Encoding (e.g. converting to base64 for embedding in a UI) is intentionally left to the caller so the SDK remains format-agnostic. Raisesrequests.HTTPErroron non-2xx responses andrequests.RequestExceptionon network failures.Note: YTMD does provide the asset URI so you can simply use the URI for retrieving cover art, pulling it as a byte array may improve performance in some applications.
Video and playlist control
change_video(video_id=None, playlist_id=None)sends achangeVideocommand to YTMD, allowing the caller to start a specific YouTube video or playlist by ID. Either argument may beNoneif not needed.
Runtime endpoint reconfiguration
update_endpoint(host, port=9863)allows the target YTMD host and port to be changed after the client is constructed. This is useful when the server address is read from user-facing settings at runtime rather than being fixed at startup.
Parser: VideoState thumbnails
The VideoState object now exposes a thumbnails field containing a list of Thumbnail
objects parsed from the YTMD state payload. Each Thumbnail carries a .url, .width,
and .height. The thumbnails are ordered from smallest to largest, so thumbnails[-1]
is always the highest-resolution image available and is the recommended source for
cover art display.
1.0.0
Initial release. Covered basic connection, authentication handshake,
core playback commands, and the Parser class for PlayerState and VideoState.
Bugs and Suggestions
If you have any bugs or suggestions, feel free to open an issue or a pull request.
Metadata
Release files for ytmd-sdk 1.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ytmd_sdk-1.2.1.tar.gz | 29.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ytmd_sdk-1.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.1 kB
Release files / ytmd_sdk-1.2.1.tar.gz
| Download URL | ytmd_sdk-1.2.1.tar.gz |
|---|---|
| Size | 29.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7bec845c815387275f389162bb4c01d27f2fc983e3d09267b46d751c12419640
|
|
BLAKE2b-256 checksum How to use checksums |
07f693319ff0d822fecfe76209389817a8ba13e5aca138a6e043c065d113a84c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 22, 2026.
Transparency logRelease files / ytmd_sdk-1.2.1-py3-none-any.whl
| Download URL | ytmd_sdk-1.2.1-py3-none-any.whl |
|---|---|
| Size | 24.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a9e3027f25a33701b66935d0b9615e08622457c03c3c9c74909b4a8e02ecd4a9
|
|
BLAKE2b-256 checksum How to use checksums |
5bb9caadcdcdc138cb57699c57a38cb47973c24fbf80e8504d5364cceb3f773b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 22, 2026.
Transparency logSecurity for ytmd-sdk 1.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for ytmd_sdk-1.2.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.