A Python SDK for interacting with the YouTube Music Desktop App.
Project description
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.
Project details
Release history Release notifications | RSS feed
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 ytmd_sdk-1.2.1.tar.gz.
File metadata
- Download URL: ytmd_sdk-1.2.1.tar.gz
- Upload date:
- Size: 29.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bec845c815387275f389162bb4c01d27f2fc983e3d09267b46d751c12419640
|
|
| MD5 |
25feca988403f8cca4e336bb00fa4e82
|
|
| BLAKE2b-256 |
07f693319ff0d822fecfe76209389817a8ba13e5aca138a6e043c065d113a84c
|
Provenance
The following attestation bundles were made for ytmd_sdk-1.2.1.tar.gz:
Publisher:
publish_pypi.yml on DamienStaebler/YTMD_SDK
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ytmd_sdk-1.2.1.tar.gz -
Subject digest:
7bec845c815387275f389162bb4c01d27f2fc983e3d09267b46d751c12419640 - Sigstore transparency entry: 1603577217
- Sigstore integration time:
-
Permalink:
DamienStaebler/YTMD_SDK@e80df8a6105687d039ac54512e9cdad4ec77e5e3 -
Branch / Tag:
refs/tags/1.2.1 - Owner: https://github.com/DamienStaebler
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish_pypi.yml@e80df8a6105687d039ac54512e9cdad4ec77e5e3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file ytmd_sdk-1.2.1-py3-none-any.whl.
File metadata
- Download URL: ytmd_sdk-1.2.1-py3-none-any.whl
- Upload date:
- Size: 24.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9e3027f25a33701b66935d0b9615e08622457c03c3c9c74909b4a8e02ecd4a9
|
|
| MD5 |
5cd40f9dfbd75854f4940187a848266a
|
|
| BLAKE2b-256 |
5bb9caadcdcdc138cb57699c57a38cb47973c24fbf80e8504d5364cceb3f773b
|
Provenance
The following attestation bundles were made for ytmd_sdk-1.2.1-py3-none-any.whl:
Publisher:
publish_pypi.yml on DamienStaebler/YTMD_SDK
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ytmd_sdk-1.2.1-py3-none-any.whl -
Subject digest:
a9e3027f25a33701b66935d0b9615e08622457c03c3c9c74909b4a8e02ecd4a9 - Sigstore transparency entry: 1603577325
- Sigstore integration time:
-
Permalink:
DamienStaebler/YTMD_SDK@e80df8a6105687d039ac54512e9cdad4ec77e5e3 -
Branch / Tag:
refs/tags/1.2.1 - Owner: https://github.com/DamienStaebler
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish_pypi.yml@e80df8a6105687d039ac54512e9cdad4ec77e5e3 -
Trigger Event:
release
-
Statement type: