A fast, interactive TUI application for fetching and storing synchronized lyrics from lrclib.net with atomic session persistence and smart caching.
- 🎵 Multi-format Support - FLAC, Opus, MP3, AAC, OGG, APE, WAV, M4A
- 🚀 Async Processing - Non-blocking worker with rate-limited API calls (10 req/s)
- 💾 Smart Caching - SQLite-backed negative cache prevents redundant lookups
- 📦 Atomic Session Persistence - Resume interrupted scans from exactly where you left off
- 🎨 Beautiful TUI - Real-time progress tracking with color-coded status
- 📁 XDG Compliant - Follows Linux standards (
~/.local/share/getlrc/)
- 🧠 Metadata Normalization - Cleans track numbers, punctuation, and extra whitespace
- 🎯 Fuzzy Matching - Uses Jaro-Winkler algorithm to match similar titles (>85% similarity)
- 🔄 Fallback Search - Automatically retries with stripped metadata if first attempt fails
- ✨ Featuring Artist Handling - Normalizes "feat.", "ft.", "featuring", "with" variations
- 📝 Potential Match Detection - Logs matches with 60-85% similarity for manual review
- 🎼 Version Preservation - Keeps "Remix", "Live", "Acoustic" info (different lyrics/timing)
- 💾 Auto-save on Pause - Session state saved atomically when paused
- 🔄 Session Recovery - Automatically resumes from saved sessions
- 📊 Real-time Progress - Live progress bar with 100% completion guarantee
- 📜 Auto-scrolling Logs - Latest entries always visible
- ⚛️ Atomic Saves - Crash-safe session persistence using temp files
- 🔍 Integrity Checks - Validates session files before resuming
- 🛡️ Permission Verification - Checks write access before starting
- 📝 Comprehensive Logging - All operations logged to file for debugging
- 🎯 100% Progress Accuracy - Progress bar always reaches completion
- ⚡ Parallel Directory Scanning - Multi-threaded filesystem traversal with
jwalk - 🔄 Concurrent Worker Pool - 5 async workers process files simultaneously
- 🚦 Token-Bucket Rate Limiting - Strict 10 req/s limit using
governorcrate - 🔒 Thread-Safe State - Work-stealing queue with atomic session updates
- 📈 Optimized for Large Libraries - Handles 40,000+ files efficiently
# Clone the repository
git clone https://github.com/c0mpile/getlrc.git
cd getlrc
# Install locally
cargo install --path .The binary will be installed to ~/.cargo/bin/getlrc (or ~/.local/bin if configured).
# Check if installed
which getlrc
# Verify PATH (getlrc will warn if ~/.local/bin is missing)
echo $PATH | grep -o "$HOME/.local/bin"If ~/.cargo/bin or ~/.local/bin is not in your PATH:
Bash/Zsh (~/.bashrc or ~/.zshrc):
export PATH="$HOME/.cargo/bin:$PATH"
# or
export PATH="$HOME/.local/bin:$PATH"Fish (~/.config/fish/config.fish):
set -gx PATH $HOME/.cargo/bin $PATHThen reload your shell:
source ~/.bashrc # or ~/.zshrc# Scan a music directory
getlrc ~/Music
# Scan a specific album
getlrc ~/Music/Artist/Album
# Force retry: bypass negative cache and retry all files
getlrc --force-retry ~/Music
getlrc -f ~/Music
# Show help
getlrc --helpThe --force-retry (or -f) flag bypasses the negative cache and retries fetching lyrics for all files, even those previously marked as "not found":
getlrc --force-retry ~/MusicBehavior:
- ✅ Bypasses Cache: Ignores negative cache entries and queries the API for every file
- ✅ Removes on Success: If lyrics are found during a forced retry, the file is removed from the negative cache
- ✅ Updates on Failure: If still not found, updates the timestamp in the cache
- ✅ Session Persistence: The flag is preserved when resuming a paused session
- 📝 Logged: All cache bypasses are logged to
~/.local/share/getlrc/logs/getlrc.log
Use Cases:
- New lyrics were added to lrclib.net since your last scan
- You want to re-check files that were previously unavailable
- Testing or debugging cache behavior
While the TUI is running:
| Key | Action | Description |
|---|---|---|
q |
Quit | Exit application (saves session if paused) |
p |
Pause | Pause processing and save session |
r |
Resume | Resume processing from paused state |
Note: When you press p (Pause), the current session is automatically saved. You can safely quit with q and resume later by running getlrc again on the same directory.
Pause and Resume Workflow:
# Start scanning
$ getlrc ~/Music
# Processing: 45/200 files...
# Press 'p' to pause
# Session saved to ~/.local/share/getlrc/session.json
# Press 'q' to quit
# Application exits, state preserved
# Later, resume the scan
$ getlrc ~/Music
📂 Resuming previous session...
# Continues from file 46/200 with full log historySession Features:
- ✅ Atomic saves (crash-safe)
- ✅ Integrity checks (detects stale sessions)
- ✅ Log history restoration (visual continuity)
- ✅ Progress count preservation
- ✅ Automatic cleanup on completion
The TUI displays concise status updates for each file:
| Symbol | Meaning | Description |
|---|---|---|
[✓] |
Downloaded | Lyrics downloaded and saved successfully |
[~] |
Cached | Previously not found, skipped API call |
[○] |
Existing | Already has .lrc file, skipped |
[✗] |
Not Found | Lyrics not available on lrclib.net |
[!] |
Error | Processing error (see logs for details) |
| Color | Meaning |
|---|---|
| 🟢 Green | Lyrics downloaded from API |
| 🟡 Yellow | Cached (previously not found) |
| 🔵 Blue | Already has .lrc file |
| ⚫ Dark Gray | Not yet processed |
┌─────────────────────────────────────────────────────────┐
│ getlrc - Processing... │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Progress │
│ ████████████████████████████████████████████████░░░░░░░ │ 80%
│ ● Downloaded: 45 ● Cached: 12 ● Existing: 8 │
└─────────────────────────────────────────────────────────┘
[✓] Downloaded | [~] Cached | [○] Existing | [✗] Not Found | [!] Error
┌─────────────────────────────────────────────────────────┐
│ Logs │
│ [✓] song1.mp3 │
│ [~] song2.flac │
│ [○] song3.m4a │
│ ... (auto-scrolls to latest) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ q Quit | p Pause │
└─────────────────────────────────────────────────────────┘
- Environment Verification - Checks directories and permissions
- Session Check - Looks for existing session to resume
- Parallel Directory Scan - Multi-threaded traversal finds all audio files using
jwalk - Skip Existing - Ignores files that already have
.lrcsidecars - Work Queue Population - Pending files added to thread-safe work-stealing queue
- Worker Pool Spawning - 5 concurrent async workers start processing
- Metadata Extraction - Reads artist, title, album, duration using
lofty - Cache Lookup - Checks SQLite database for previously unfound tracks
- Rate-Limited API Query - Fetches lyrics from lrclib.net (10 req/s via
governor) - Atomic Write - Saves synchronized lyrics as
.lrcfiles - Session Update - Thread-safe updates to session state for resume capability
┌─────────────────────────────────────────────────────────┐
│ TUI (Ratatui) │
│ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Header │ │ Progress │ │ Logs │ │ Footer │ │
│ └─────────┘ └──────────┘ └──────────┘ └─────────┘ │
└───────────────────┬─────────────────────────────────────┘
│ mpsc channels (bidirectional)
▼
┌─────────────────────────────────────────────────────────┐
│ Worker Pool (Tokio + Arc) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Parallel Scanner (jwalk) │ │
│ │ - Multi-threaded directory traversal │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Work Queue (Arc<Mutex<VecDeque>>) │ │
│ │ - Work-stealing pattern │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌──┐ │
│ │Worker 1│ │Worker 2│ │Worker 3│ │Worker 4│ │W5│ │
│ └────┬───┘ └────┬───┘ └────┬───┘ └────┬───┘ └─┬┘ │
│ │ │ │ │ │ │
│ └───────────┴───────────┴───────────┴─────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Governor Rate Limiter (10 req/s) │ │
│ └──────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Cache │→ │ LRCLIB │→ │ .lrc │ │Session │ │
│ │ (SQLite) │ │ API │ │ Writer │ │ (Mutex)│ │
│ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
│ │
│ Thread-Safe Shared State (Arc<Mutex>): │
│ - Session persistence (atomic saves) │
│ - Progress counts (downloaded/cached/failed) │
│ - Log history (500 entries) │
└─────────────────────────────────────────────────────────┘
All persistent data follows XDG Base Directory specification:
| File | Path | Purpose |
|---|---|---|
| Cache Database | ~/.local/share/getlrc/negative_cache.db |
Stores tracks not found on lrclib.net |
| Session File | ~/.local/share/getlrc/session.json |
Saves progress when paused |
| Debug Logs | ~/.local/share/getlrc/logs/getlrc.log |
All operations and errors |
{
"root_path": "/home/user/Music",
"pending_files": ["file3.mp3", "file4.flac"],
"downloaded_count": 42,
"cached_count": 15,
"existing_count": 8,
"failed_count": 3,
"log_history": [
{ "filename": "song1.mp3", "status": "Downloaded" },
{ "filename": "song2.flac", "status": "Cached" }
]
}Features:
- ⚛️ Atomic saves (temp file + rename)
- 🔍 Integrity checks (validates on load)
- 📝 Log capping (max 500 entries)
- 🧹 Auto-cleanup (deleted on completion)
All debug output is written to the log file to keep the TUI clean:
Log Contents:
- Full file paths
- API request URLs
- Metadata extraction details
- Detailed error messages
- Timing information
- Session operations
View Logs:
# Real-time monitoring
tail -f ~/.local/share/getlrc/logs/getlrc.log
# Search for errors
grep ERROR ~/.local/share/getlrc/logs/getlrc.log
# View session operations
grep "Session" ~/.local/share/getlrc/logs/getlrc.log# Debug build
cargo build
# Release build (optimized)
cargo build --release
# Run tests
cargo test
# Lint (zero warnings required)
cargo clippy -- -D warnings
# Format code
cargo fmt
# Install locally for testing
cargo install --path . --forcegetlrc/
├── src/
│ ├── main.rs # Entry point, CLI handling
│ ├── lib.rs # Module exports
│ ├── api.rs # lrclib.net API client
│ ├── cache.rs # SQLite negative cache
│ ├── env.rs # Environment verification
│ ├── messages.rs # Worker ↔ TUI messages
│ ├── paths.rs # XDG path utilities
│ ├── scanner/ # Directory scanning, metadata
│ ├── session.rs # Session persistence
│ ├── tui/ # Terminal UI (Ratatui)
│ └── worker.rs # Async processing logic
├── Cargo.toml # Dependencies and metadata
└── README.md # This file
| Crate | Purpose |
|---|---|
tokio |
Async runtime |
ratatui |
Terminal UI framework |
crossterm |
Terminal control |
clap |
CLI argument parsing |
lofty |
Audio metadata extraction |
rusqlite |
SQLite database |
reqwest |
HTTP client (rustls) |
walkdir |
Sequential directory traversal (legacy) |
jwalk |
Parallel directory traversal |
governor |
Token-bucket rate limiting |
regex |
String normalization and cleaning |
strsim |
Fuzzy string matching (Jaro-Winkler) |
tracing |
Structured logging |
serde |
Serialization |
anyhow |
Error handling |
- Basic lyrics fetching and saving
- XDG Base Directory compliance
- SQLite negative cache
- Interactive TUI with real-time updates
- Pause/Resume controls
- Atomic session persistence
- Log history restoration
- Progress bar 100% completion
- Environment verification
- Integrity checks for stale sessions
- Comprehensive logging
- Parallel directory walking with
jwalk - Concurrent API worker pool (5 workers) with
governorrate limiting - Thread-safe session management with work-stealing queue
- Force retry mode with
--force-retryflag - Smart metadata normalization with regex cleaning
- Fuzzy matching using Jaro-Winkler algorithm (>85% auto-accept)
- Automatic fallback search with stripped metadata
- Exponential backoff for API retries
- Configurable retry limits
- Progress estimation and ETA
- Configurable API rate limits
- Multiple API source support
- Lyrics quality scoring
- Batch processing modes
- Configuration file support
Issue: Binary not found after installation
# Check if ~/.cargo/bin is in PATH
echo $PATH | grep cargo
# Add to PATH if missing (see Installation section)Issue: Permission denied errors
# Check data directory permissions
ls -la ~/.local/share/getlrc/
# Fix permissions if needed
chmod 755 ~/.local/share/getlrc/
chmod 644 ~/.local/share/getlrc/*.dbIssue: Stale session detected
# This happens if files were deleted while app was closed
# The app will automatically start a fresh scan
# To force a fresh scan, delete the session file:
rm ~/.local/share/getlrc/session.jsonIssue: Progress bar stuck before 100%
# This was fixed in v0.1.0
# Update to the latest version:
cargo install --path . --forceFor detailed debugging, check the log file:
# View full log
cat ~/.local/share/getlrc/logs/getlrc.log
# Monitor in real-time
tail -f ~/.local/share/getlrc/logs/getlrc.log
# Search for specific errors
grep -i "error\|warn" ~/.local/share/getlrc/logs/getlrc.logMIT
- Lyrics provided by lrclib.net
- Built with Rust 🦀
- UI powered by Ratatui
Contributions are welcome! Please feel free to submit issues or pull requests.
- Follow Rust idioms and best practices
- Maintain zero
cargo clippywarnings - Add tests for new features
- Update documentation
- Preserve surgical precision (minimal diff noise)
Current Version: 0.4.0
Status: Production Ready (Multi-threaded + Fuzzy Matching)
Next Milestone: Error Resilience (Exponential Backoff)