A terminal YouTube Music player built in Rust. Uses yt-dlp for extraction and rodio for playback, wrapped in a ratatui TUI. Started as a learning project, now a full-featured local music client.
- Play/Pause, Next, Previous, Seek (±10s)
- Volume control (±1% or ±5% with Shift), persisted across sessions
- Resume playback position on restart
- Background pre-downloading of upcoming tracks (lookahead)
- Persistent download cache — cached tracks play instantly on restart
- Music-only filter (
Shift+F) — filters tracks >7 min, toggle off for podcasts/long mixes
- Search YouTube for songs and videos
- Load any YouTube or YouTube Music playlist URL directly
- Queue management with history, delete, and clear
- Browse your full library directly in the TUI — Saved Mixes, owned playlists, saved playlists, Liked Music
- Fetches from
youtube.com/feed/playlists— returns everything in your library in one request - Three-column navigation: Sections (left) → Items (middle) → Tracks (right), vim-style
h/lto move between columns - Expand any playlist to see individual tracks and cherry-pick what to add
- Add a single track to the queue or play it immediately
- Add an entire playlist to the queue in one action — duplicates automatically skipped
- Status bar shows action feedback (added count, duplicates skipped, filtered count)
- 30-minute disk cache (
feed_cache.json) — reopening the feed is instant - Force-refresh with
rto bypass the cache - Fetches via browser cookies — no OAuth, no API keys required
- Browser cookie auth — Chrome, Chromium, Firefox, Zen Browser (multi-profile)
- Account switcher accessible at any time (
okey) — switch profiles or log out - Selected account persisted across sessions
- All state files written atomically (
tempfile+rename) — no torn writes on crash - Generic
CacheStore<T>with TTL and schema versioning for all cached data - History, queue, download cache, playback position all survive restarts
- File permissions restricted to
0o600(owner read/write only)
| Component | Library |
|---|---|
| TUI | ratatui + crossterm |
| Async | tokio |
| YouTube | yt-dlp (subprocess) |
| Audio | rodio (pure Rust) |
| JSON | serde + serde_json |
| Atomic writes | tempfile |
# yt-dlp (required)
yay -S yt-dlp # Arch/Manjaro
# or: pip install yt-dlp
# Rust toolchain
sudo pacman -S rustup # Arch/Manjaro
rustup default stable
# Other distros:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shgit clone https://github.com/jeppe119/Crusty.git
cd Crusty
# Build release binary
cargo build --release
# Run directly
./target/release/crusty
# Or install to PATH
cp target/release/crusty ~/.local/bin/crusty- Make sure you are logged into YouTube in Chrome, Chromium, Firefox, or Zen Browser
- Launch Crusty — you will be prompted to select a browser account
- Press
lto open the account picker and select your profile - Press
/to search,lto load a playlist URL, orfto open the feed browser
| Key | Action |
|---|---|
Space |
Play / Pause |
n |
Next track |
p |
Previous track |
↑ / Shift+↑ |
Volume up +1% / +5% |
↓ / Shift+↓ |
Volume down -1% / -5% |
→ |
Seek forward 10s |
← |
Seek backward 10s |
| Key | Action |
|---|---|
j / k |
Navigate lists down / up |
Enter |
Add selected item to queue |
t |
Toggle queue expand |
d |
Delete selected item (queue expanded) |
m |
Toggle My Mix expand |
Shift+M |
Refresh My Mix (when expanded) |
Shift+H |
Toggle history expand |
Shift+C |
Clear history (when expanded) |
h |
Go to Home view |
Esc |
Return to previous view |
| Key | Action |
|---|---|
f |
Open YouTube Music Feed Browser |
j / k |
Navigate sections (left column) or items (middle) or tracks (right) |
l / → |
Move focus right (Sections → Items → Tracks) |
h / ← |
Move focus left / collapse track view |
Enter |
Expand playlist into tracks / Play selected track |
a |
Add whole playlist to queue / Add single track (track view) |
r |
Force-refresh feed (bypasses 30-min cache) |
Esc / f |
Close feed browser |
| Key | Action |
|---|---|
/ |
Search YouTube |
l |
Load playlist from URL |
o |
Switch account / Log out |
Shift+F |
Toggle music-only filter (>7 min filtered) |
? |
Show help screen |
q |
Quit |
Crusty/
├── Cargo.toml
├── README.md
├── assets/
│ ├── Crusty.png
│ └── screenshots/
├── docs/
└── src/
├── main.rs
├── config.rs # Constants, paths, utilities
│
├── player/
│ ├── audio.rs # Audio playback (rodio)
│ └── queue.rs # Queue & history management
│
├── services/
│ ├── cache_store.rs # Generic TTL + schema-versioned file cache
│ ├── download.rs # Background download manager
│ ├── feed.rs # YouTube Music feed scraping (library, liked, mixes)
│ ├── persistence.rs # History/queue/state save/load (atomic JSON)
│ └── playlist.rs # Playlist fetching via yt-dlp
│
├── youtube/
│ ├── browser_auth.rs # Browser cookie authentication
│ └── extractor.rs # yt-dlp search interface
│
└── ui/
├── app.rs # Main TUI app (event loop, draw, channels)
├── input.rs # Keyboard input → command pattern
├── state.rs # UI state structs (feed, queue, search…)
├── playback.rs # Play/pause/seek/volume
├── navigation.rs # List cursor movement
├── actions.rs # Search, playlist, feed, login actions
└── views/ # Draw modules
├── feed.rs # YouTube Music feed browser (3-column)
├── help.rs # Help screen
├── history.rs # Playback history
├── login.rs # Login / account picker
├── player_bar.rs # Now-playing bar
├── playlist.rs # My Mix / loaded playlist
├── queue.rs # Queue view
└── search.rs # Search results
Crusty fetches your full YouTube library from youtube.com/feed/playlists using your browser cookies. This single endpoint returns everything in one request:
| Type | ID prefix | Example |
|---|---|---|
| Saved Mixes | RDCLAK*, RDAMPL* |
YouTube Music curated playlists |
| Liked Music | LM |
Your liked songs |
| Your playlists | PL* |
Playlists you created |
| Saved playlists | PL* |
Playlists saved from other creators |
System playlists (Watch Later, History) are filtered out automatically.
Track counts are not available from the listing endpoint — they appear after expanding a playlist with Enter.
- YouTube API / yt-dlp changes can break extraction (update yt-dlp if things stop working:
pip install -U yt-dlp) - UI may not render well in very small terminals (minimum ~80×24 recommended)
PRs, issues, and forks are welcome.
MIT
Warning
Use at your own risk. Automating YouTube playback and downloading content via yt-dlp may violate YouTube's Terms of Service. This project is intended for personal, non-commercial use only. The authors take no responsibility for any account suspension, legal action, or other consequences arising from its use. Always respect copyright and the rights of content creators.
Made by jeppe119




