9.4 KiB
LRCGET Tauri Architecture
Architecture Overview
Stack: Tauri v2 (Rust) + SQLite + Kira audio + LRCLIB API
Patterns:
| Pattern | Implementation |
|---|---|
| State | Global AppState with Mutex<Connection> and Mutex<Player> |
| DB Access | ServiceAccess trait on AppHandle for read/write operations |
| Events | Async backend→frontend via app.emit() |
| Commands | All FFI in main.rs, organized by domain |
| Scanning | Single-pass streaming with batch processing (100 files) |
| Export | Manual sidecar (.txt/.lrc) and embedded metadata export |
Key Dependencies: tauri, rusqlite+rusqlite_migration, lofty, kira, reqwest, rayon, xxhash-rust
Project Structure
src-tauri/
├── src/
│ ├── main.rs # Entry point, Tauri commands
│ ├── state.rs # AppState, ServiceAccess trait
│ ├── db.rs # SQLite operations, migrations
│ ├── library.rs # High-level library API
│ ├── scanner/ # Incremental file scanning (NEW)
│ │ ├── scan.rs # Single-pass streaming scanner
│ │ ├── hasher.rs # xxhash3 content hashing
│ │ ├── metadata.rs # Audio metadata extraction
│ │ └── models.rs # ScanResult, ScanProgress
│ ├── export.rs # Manual sidecar/embed export helpers
│ ├── lyricsfile.rs # YAML lyricsfile helpers
│ ├── player.rs # Kira audio playback
│ ├── persistent_entities.rs # Track/Album/Artist structs
│ ├── utils.rs # Text normalization
│ └── lrclib/ # LRCLIB API client
│ ├── search.rs # GET /api/search
│ ├── get.rs # GET /api/get
│ ├── get_by_id.rs # GET /api/get/{id}
│ ├── publish.rs # POST /api/publish (with PoW)
│ ├── flag.rs # POST /api/flag (with PoW)
│ └── challenge_solver.rs # SHA256 proof-of-work
├── migrations/ # SQL files (v1-v9), rusqlite_migration
└── Cargo.toml / tauri.conf.json
Core Components
State Management (state.rs)
struct AppState {
db: Mutex<Option<Connection>>, // SQLite
player: Mutex<Option<Player>>, // Kira audio
queued_notifications: Mutex<Vec<Notify>>,
}
trait ServiceAccess {
fn db<F, T>(&self, f: F) -> T; // Read-only
fn db_mut<F, T>(&self, f: F) -> T; // Mutable
}
Database Schema (v10)
Tables:
| Table | Purpose |
|---|---|
directories |
Watched music paths |
library_data |
Init flag (single row) |
config_data |
Settings (embed, skip flags, theme, LRCLIB instance) |
artists |
name, name_lower (search) |
albums |
name, album_artist_name, image_path |
tracks |
file_path, title, duration, lrc_lyrics, txt_lyrics, instrumental, has_plain_lyrics, has_synced_lyrics, has_word_synced_lyrics |
lyricsfiles |
Persisted YAML lyrics (decoupled from tracks) |
Migration v8 (scanning): Added file_size, modified_time, content_hash, scan_status
Migration v9: Added lyricsfiles table with denormalized track metadata
Migration v10: Added indexed lyrics-presence booleans on tracks (has_plain_lyrics, has_synced_lyrics, has_word_synced_lyrics) used for filtering
Migration v11: Added volume column to config_data for persisting player volume level
Indexes: All *_lower columns + content_hash, scan_status, modified_time+file_size (fingerprint) + lyrics-presence indexes
File Scanning (scanner/)
ScanResult: { total_files, added, modified, deleted, moved, unchanged, is_initial_scan, duration_ms }
ScanProgress: { phase: "discovering"|"updating", progress: f64, files_processed, files_total, message }
Process:
- Mark existing tracks as "pending" (scan_status=0)
- Single-pass streaming: discover + process simultaneously
- Batch size: 100 files
- Detection modes:
- Hash (default): xxhash3 of first 64KB - detects moves, 100% accurate
- Metadata: mtime+size only - faster, may duplicate on metadata changes
- Delete remaining "pending" tracks
Performance: 100K files HDD ~30-90s faster, SSD ~5-10s faster; Memory ~10MB vs ~200MB old
Audio Player (player.rs)
struct Player {
manager: AudioManager, // Kira
sound_handle: Option<StreamingSoundHandle>,
track: Option<PersistentTrack>,
status: PlayerStatus, // Playing/Paused/Stopped
progress: f64, duration: f64, volume: f64,
}
Background loop (40ms) in main.rs emits player-state event.
Volume persistence:
- Player initializes with volume from
config_dataon startup set_volume()command updates both the player and persists to config- Frontend receives volume updates via
player-stateevents
Export Module (export.rs)
Manual lyrics export to sidecar files (.txt, .lrc) and embedded metadata.
pub enum ExportFormat {
Txt, // Plain text sidecar file
Lrc, // Synced LRC sidecar file
Embedded, // Embedded in audio metadata (MP3/FLAC)
}
pub struct ExportResult {
pub format: ExportFormat,
pub path: Option<PathBuf>,
pub success: bool,
pub message: String,
}
Key Functions:
export_track()- Export a single track to multiple formatsexport_track_format()- Export to a specific formatembed_lyrics()- Embed lyrics into MP3 (ID3v2 USLT/SYLT) or FLAC (Vorbis comments)
Note: Sidecar exports overwrite existing files silently. Embedded exports use lofty for tag writing.
LRCLIB API (lrclib/)
Endpoints: search, get, get_by_id, publish, flag, request_challenge
Challenge-Response (publish/flag):
- Request challenge → prefix + target hash
- Solve PoW (SHA256): find nonce where hash(prefix+nonce) < target
- Submit with token
Data Entities (persistent_entities.rs)
PersistentTrack: id, file_path, file_name, title, album_name, artist_name, album_id, artist_id, image_path, track_number, txt_lyrics, lrc_lyrics, lyricsfile, duration, instrumental
PersistentAlbum: id, name, artist_name, tracks_count
PersistentArtist: id, name, tracks_count
PersistentConfig: skip_synced, skip_plain, show_line_count, try_embed, theme_mode, lrclib_instance, volume
Commands
Library
| Command | Description |
|---|---|
scan_library(use_hash?) |
Incremental scan. Emits scan-progress, scan-complete |
uninitialize_library() |
Clear all library data |
Data Queries
| Command | Returns |
|---|---|
get_track(s/ids)() |
PersistentTrack (includes joined lyricsfile) |
get_album(s/ids)() |
PersistentAlbum |
get_artist(s/ids)() |
PersistentArtist |
get_album_tracks/ids() |
Filtered tracks |
get_artist_tracks/ids() |
Filtered tracks |
Lyrics
| Command | Purpose |
|---|---|
download_lyrics() |
Auto-download from LRCLIB |
retrieve_lyrics/by_id() |
Get raw LRCLIB response |
search_lyrics() |
Search LRCLIB database |
apply_lyrics() |
Save a selected LRCLIB result into database-backed lyrics storage |
save_lyrics(id, plain?, synced?, lyricsfile?) |
Save edits (prefers lyricsfile) |
publish_lyrics(title, album, artist, duration, plain?, synced?, lyricsfile?) |
Upload to LRCLIB (with PoW; accepts Lyricsfile-only payloads) |
export_lyrics(track_id, formats, lyricsfile?) |
Manual export to .txt, .lrc, or embedded tags |
export_track_lyrics(track_id, formats) |
Export single track, returns summary for mass export |
get_track_ids_with_lyrics() |
Get all track IDs that have lyrics for mass export |
flag_lyrics() |
Report to LRCLIB (with PoW) |
Playback & Config
play_track(),pause/resume_track(),seek_track(),stop_track(),set_volume()(persists volume to config)get/set_directories(),get/set_config(),get_init()- Volume is loaded from config on startup and auto-saved when changed via
set_volume() open_devtools(),drain_notifications()
Events (Backend → Frontend)
| Event | Payload | Purpose |
|---|---|---|
scan-progress |
{ phase, progress, filesProcessed, filesTotal, message } |
Real-time scan updates |
scan-complete |
{ totalFiles, added, modified, deleted, moved, unchanged, isInitialScan, durationMs } |
Scan finished |
player-state |
Player status | Playback updates (40ms loop) |
reload-track-id |
track_id | Request refresh |
publish-lyrics-progress |
Status | Publishing updates |
flag-lyrics-progress |
Status | Flagging updates |
export-progress |
{ trackId, status, message } |
Export progress for single track |
export-complete |
{ exported, skipped, errors } |
Mass export complete |
Configuration
tauri.conf.json: Window 1024x768 min, CSP for asset protocol + media
Cargo.toml: Rust 2021, Tauri v2 (dialog, shell, global-shortcut, os plugins)
Notes
- Lyrics Storage:
lyricsfilestable is the persistence source of truth; sidecar files and embedded tags are manual exports - Filtering Source of Truth: Lyrics filters now use derived
tracks.has_*_lyricsbooleans (from Lyricsfile content), not null checks ontxt_lyrics/lrc_lyrics - Instrumental:
[au: instrumental]marker in lrc_lyrics - Security: PoW for LRCLIB writes, user-agent in requests, DB in app_data_dir