ytm-player

v1.9.3

2026-05-04 · pip install ytm-player==1.9.3

A small follow-up release: two crash fixes caught by manual smoke after v1.9.2, two CLI/utility fixes, plus three community PRs.

New features

  • Configurable default theme — set theme under [ui] in config.toml to control which Textual theme loads on startup (default ytm-dark). Changing theme via Ctrl+PTheme updates the current session only; the startup default is no longer overwritten by session state. To persist the active theme as the new default, use Ctrl+PTheme: Set Current as Default. Thanks @Villoh (#85).
  • Theme: Set Current as Default command palette action — saves the currently active theme to config.toml so it becomes the startup default. Includes rollback on save failure (e.g. read-only filesystem) with an error toast.
  • Discovery round-robinD now cycles deterministically through Charts → Trending → For You → Liked → Artist → Recently Played (was random source selection). Charts sub-rotates through its shelves between presses, and the Discovery label shows the active source (e.g. Discovery (US Daily Top 100)). Mood source dropped (the Moods & Genres tab was removed in v1.9.1 due to upstream crashes). Thanks @wgordon17 (#75).
  • Radio queues prepend their seed tracks — radio playback now starts with the seed before suggestions, matching YouTube Music's native behaviour. Append-mode background refill is unchanged. Thanks @wgordon17 (#75).
  • Persistent queue source header — Queue page shows Generated from: … beneath Now Playing for radio and discovery queues, with up to three seed titles inline and a tooltip for the full list. Toggle via [ui] show_queue_source (default on). Thanks @wgordon17 (#75).

Fixes

  • Crash on Go to Artist / Album / PlaylistTrackTable and _ArtistAlbumList set up their columns in on_mount, but context._build_artist's nested-mount chain calls load_tracks / load_albums synchronously before on_mount fires, so add_row ran with 0 columns and raised ValueError: More values provided than there are columns. Both widgets now eager-init columns in __init__, eliminating the mount-order race.
  • Update check version comparison_is_newer now uses packaging.version.Version for proper PEP 440 comparison. The hand-rolled tuple parser dropped non-numeric chunks and got post-releases (e.g. 1.6.0.post1) wrong.
  • ytm config with multi-arg $EDITOREDITOR="code -w" and similar now work; previously the whole string was passed as a single argv entry, so subprocess looked for an executable literally named code -w and failed. Malformed quoting (e.g. unbalanced quotes) now exits cleanly via the existing error path instead of crashing.
  • Search race conditions — fixed four interacting bugs that caused searches to require a second Enter, the suggestion overlay to re-appear on top of incoming results, and selecting a suggestion to cancel the in-flight search worker. Thanks @wgordon17 (#84).

Changed

  • Theme persistence modelconfig.toml is now the authoritative source for the startup theme; session.json stores runtime state only and no longer restores the theme on launch. This separates user-authored configuration from app-managed session state.
  • Command palette provider architecture — app-specific commands moved from get_system_commands() to a dedicated YTMCommandProvider registered in App.COMMANDS. Isolates command definitions in src/ytm_player/app/_commands.py and keeps _app.py focused on app logic.

Infrastructure

  • Pre-commit hooks + pyright in CI + dbus-fast migration — new .pre-commit-config.yaml runs ruff-format / ruff / pyright on commit and pytest on push (pre-commit install to activate). New CI job runs pyright at standard strictness. The Linux MPRIS service migrates from the unmaintained dbus-next to the maintained dbus-fast fork (identical API). Thanks @wgordon17 (#83).
  • Settings save Windows fallbackSettings.save() now falls back to a direct path.write_text() when os.replace() raises PermissionError (e.g. when config.toml is held open by an external editor on Windows).