File tree
Click any file to view its source. Updated at every build from the pinned tag.
Stack
Key patterns
Architecture
For users curious about how the app is organized, and contributors getting started. For dev workflow (lint, test, ruff order, etc.) see CONTRIBUTING.md.
File tree
src/ytm_player/
├── app/ # Main Textual application (mixin package)
│ ├── _app.py # Class def, __init__, compose, lifecycle
│ ├── _base.py # YTMHostBase TYPE_CHECKING stub for Pyright (mixin attrs)
│ ├── _playback.py # play_track, player events, history, download
│ ├── _keys.py # Key handling and action dispatch
│ ├── _sidebar.py # Sidebar toggling and playlist sidebar events
│ ├── _navigation.py # Page navigation and nav stack
│ ├── _ipc.py # IPC command handling for CLI
│ ├── _track_actions.py # Track selection, actions popup, radio
│ ├── _session.py # Session save/restore (queue, volume, last-playing track)
│ └── _mpris.py # MPRIS/media key callbacks
├── cli.py # Click CLI entry point
├── ipc.py # Unix socket IPC for CLI ↔ TUI
├── config/ # Settings, keymap, theme (TOML)
├── services/ # Backend services
│ ├── auth.py # Browser cookie auth (multi-account aware)
│ ├── ytmusic.py # YouTube Music API wrapper
│ ├── player.py # mpv audio playback
│ ├── stream.py # yt-dlp stream URL resolution
│ ├── queue.py # Playback queue with shuffle/repeat
│ ├── history.py # SQLite play/search history
│ ├── cache.py # LRU audio file cache
│ ├── lrclib.py # LRCLIB.net synced lyrics fallback (with title sanitization)
│ ├── mpris.py # D-Bus MPRIS media controls (Linux)
│ ├── macos_media.py # macOS Now Playing integration
│ ├── macos_eventtap.py # macOS hardware media key interception
│ ├── mediakeys.py # Cross-platform media key service
│ ├── download.py # Offline audio downloads
│ ├── discord_rpc.py # Discord Rich Presence
│ ├── lastfm.py # Last.fm scrobbling
│ ├── yt_dlp_options.py # yt-dlp config/cookie handling
│ └── spotify_import.py # Spotify playlist import
├── ui/
│ ├── header_bar.py # Top bar with sidebar toggle buttons
│ ├── playback_bar.py # Persistent bottom bar (track info, progress, controls, heart)
│ ├── theme.py # Textual theme integration + app-specific color overrides
│ ├── sidebars/ # Persistent playlist sidebar (left) and lyrics sidebar (right)
│ ├── pages/ # Library, Search, Browse, Context, Queue, Liked Songs, Recently Played, Help
│ # Queue/Liked/Recent share the project's TrackTable widget
│ # (right-click context menu, filter, sort, play indicator)
│ ├── popups/ # Actions menu, playlist picker, Spotify import, country picker (charts region — 68 entries with Global default)
│ └── widgets/ # TrackTable, PlaybackProgress, AlbumArt
└── utils/ # Terminal detection, formatting, BiDi text, transliteration
Stack
| Library | Purpose |
|---|---|
| Textual | TUI framework |
| ytmusicapi | YouTube Music HTTP API |
| yt-dlp | Stream URL resolution + offline downloads |
| python-mpv | mpv playback (libmpv wrapper) |
| aiosqlite | Async SQLite for history + cache index |
| click | CLI framework |
| Pillow | Album art rendering (downscaled to terminal half-blocks) |
| dbus-next | MPRIS D-Bus (Linux) — optional |
| pypresence | Discord Rich Presence — optional |
| pylast | Last.fm scrobbling — optional |
Key patterns
- Mixin architecture —
YTMPlayerAppis composed from 8 mixins (Playback, Session, Keys, Navigation, Sidebar, TrackActions, MPRIS, IPC). Each mixin extendsYTMHostBase(inapp/_base.py), aTYPE_CHECKING-only stub class that mirrors the runtime instance attribute surface so Pyright doesn't emitCannot access attribute X for class FooMixinnoise. At runtimeYTMHostBase = object— zero behaviour change. - Event-driven playback —
PlayeremitsPlayerEventenums (TRACK_END,TRACK_CHANGE, etc.) dispatched to the Textual event loop viacall_soon_threadsafe. The app registers callbacks to update the UI. - Thread safety —
PlayerandQueueManagerare singletons withthreading.Lock. Player events bridge from mpv's callback thread to asyncio. - Track format — All services use a standardized track dict (
video_id,title,artist,artists,album,album_id,duration,thumbnail_url,is_video). Thenormalize_tracks()helper inutils/formatting.pyconverts inconsistent ytmusicapi response shapes into this format. - Session persistence — Volume, queue, shuffle/repeat, theme, and the last-playing track + position are saved on every exit to
session.json. When[playback] resume_on_launchis true (default), the last-playing track + position are staged into the playback bar at launch and consumed the first time the user presses play. Per-playlist Shuffle lock state is stored separately inshuffle_prefs.json. - Playback bar keybindings — Standard transport keys plus
lto toggle the like state of the currently playing track. - Prefetching — Next track's stream URL is resolved in the background for instant skip.
- Page navigation —
app/_navigation.pymanages a nav stack (max 20). Each page implementshandle_action(action, count)for vim-style keybinding dispatch andget_nav_state()for state preservation across navigation. - LC_NUMERIC quirk —
cli.pyforcesLC_NUMERIC=Cat import time — mpv segfaults without it.