- Python 100%
'?' toggles a Textual Footer widget showing all active keybindings, docked under the status bar (wrapped in a shared #bottom-bar container so the two don't overlap - Textual's dock doesn't auto-stack multiple docked siblings on the same edge). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| ytui | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
| TODO.md | ||
ytui
A personal terminal YouTube client built with Textual. Search YouTube, browse your real playlists and subscriptions (via Google login), save videos to a local library, and hand off playback to mpv.
Loosely modeled on youtube-tui's layout, but with a more reliable backend: public browsing goes through yt-dlp directly instead of a public Invidious instance, and account data (playlists/subscriptions) uses real OAuth against the YouTube Data API instead of not being supported at all.
Features
- Search — powered by
yt-dlp, no API key or quota involved. - Playlists — log in with your Google account to see your actual playlists and drill into them.
- Subscriptions — list of channels you're subscribed to; drill into one for its recent uploads.
- Library — save videos locally (no login needed) for quick access later.
- Play all from here — queue playback from any list (search results, a playlist, a channel, your library) starting at the highlighted video;
mpvauto-advances through the rest natively. - View channel — jump from any video to that channel's recent uploads, whether or not you're subscribed.
Requirements
- Python 3.11+
mpv— used for all playbackffmpeg— used byyt-dlp/mpvto merge separate video/audio streams
Both mpv and ffmpeg are expected to already be on your PATH.
Setup
git clone https://git.ventoz.ca/trevor/ytui.git
cd ytui
python3 -m venv .venv
.venv/bin/pip install .
Logging in (optional, needed for Playlists/Subscriptions)
Search and channel browsing work with no setup. To see your own playlists and subscriptions, you need an OAuth client so the app can ask Google for permission on your behalf:
- In Google Cloud Console, open (or create) a project with the YouTube Data API v3 enabled.
- APIs & Services → OAuth consent screen:
- User type: External (unless your account is on a Workspace org).
- Fill in app name / support email / developer contact email — anything reasonable.
- Under Test users, add your own Google account email. This is required — while the app is in "Testing" publishing status, only accounts listed here can complete login; everyone else gets a 403.
- APIs & Services → Credentials → Create Credentials → OAuth client ID, application type Desktop app. Download the JSON.
- Save it as
~/.config/ytui/client_secret.json.
The first time you press l in the app, it opens your browser for the Google consent screen and caches a token at ~/.config/ytui/token.json so you don't have to log in again.
Running
.venv/bin/ytui
Keybindings
| Key | Action |
|---|---|
/ |
Focus the search box |
Enter |
Submit search / select item / drill in |
j / k or ↓ / ↑ |
Move down / up a list |
l |
Log in (or refresh playlists/subscriptions if already logged in) |
s |
Stop playback |
? |
Toggle the keybinds bar at the bottom |
Escape |
Go back a level, or return to search |
q |
Quit |
Search runs regardless of which tab is active and switches you to the Search tab. Every video list has an action menu (Enter again once a list item is selected) with: Play video, Play audio, Play all from here, Play all from here (audio), Shuffle play all, Shuffle play all (audio), View channel, Open in browser, Save to library — plus Remove from library when viewing your Library.
Project layout
ytui/
app.py Textual UI: tabs, panes, keybindings, wiring
app.tcss Styling
player.py mpv playback (single video or queued "play all")
models.py Shared Video/Channel/Playlist dataclasses
sources/
browse.py Public search/metadata via yt-dlp (no login, no quota)
account.py OAuth + YouTube Data API v3 (playlists, subscriptions)
library.py Local JSON-backed saved-videos store
Architecture notes
- Hybrid backend: anything public (search, a channel's uploads, a public playlist) goes through
yt_dlpas a library, with no API key and no quota ceiling. Anything tied to your account (your playlists, your subscriptions) goes through OAuth + the official YouTube Data API v3, where call volume is low enough that the free quota is a non-issue. - Playback:
player.pyshells out tompv --ytdl-format=... <url> [<url> ...]. Passing multiple URLs is what makes "Play all from here" work — mpv queues and auto-advances through them on its own, no custom playback-queue logic needed on our end.