S2 of the Lyrics epic: new shared crate crates/qbz-lyrics (headless, frontend-agnostic per ADR-006), the whole fetch/parse/cache engine in one testable home for Slint, Tauri and the TUI. - Domain model: LyricsDoc/LyricsLine/Word (native Qobuz wsync word stamps preserved) + the Tauri-wire-compatible LyricsPayload (camelCase serde); LyricsProvider gains the qobuz variant and round-trips it. - LRC parser ported from the Tauri TS semantics (gap markers as end-of-vocal, 8s MAX_SUNG cap, median last-line end) with the F5 defects fixed forward: multi-stamp lines split per stamp, [offset:] honored. Emitter renders wsync line bounds as LRC-with-gap-markers (Q5 persistence form, lossy at word level only). - Providers ported verbatim from src-tauri/src/lyrics/providers.rs: LRCLIB search-first + scorer and lyrics.ovh, request shapes byte-identical. - Cache: same per-user SQLite schema/path Tauri uses (WAL per ADR-002, no TTL) plus the additive qobuz_wsync_json column (amended Q5) with an idempotent in-place migration for Tauri-era DBs; readers prefer wsync. Stats read entries AND size from the per-user path (F1 fix). - Orchestrator LyricsService: cache probe with uniform plain-only soft miss (Q4) -> Qobuz primary (wsync authoritative; plain held while LRCLIB is probed for synced, the no-sync-regression rule) -> LRCLIB with one retry on transport error -> lyrics.ovh; offline = cache-only with typed NotAvailableOffline (F3); in-flight dedupe via shared futures (F6); request-key echo on every response for the caller stale guard (F2). - 55 unit tests: parser/emitter round-trips, scorer matrix, cache lifecycle + schema migration, chain priority/offline/dedupe with mocked providers. cc
37 lines
1.0 KiB
TOML
37 lines
1.0 KiB
TOML
[package]
|
|
name = "qbz-lyrics"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
description = "Lyrics engine for QBZ — Qobuz-first source chain, LRC parser/emitter, external providers, SQLite cache (headless, frontend-agnostic)"
|
|
authors = ["blitzkriegfc"]
|
|
|
|
[dependencies]
|
|
# Shared QBZ crates (Qobuz lyrics endpoint + wire DTOs live in qbz-qobuz)
|
|
qbz-qobuz = { path = "../qbz-qobuz" }
|
|
|
|
# Database (same pin as every other QBZ crate)
|
|
rusqlite = { version = "0.31", features = ["bundled"] }
|
|
|
|
# HTTP (external fallback providers: LRCLIB, lyrics.ovh)
|
|
reqwest = { workspace = true }
|
|
urlencoding = "2"
|
|
|
|
# Serialization
|
|
serde = { workspace = true }
|
|
serde_json = { workspace = true }
|
|
|
|
# LRC timestamp parsing
|
|
regex = { workspace = true }
|
|
|
|
# Async (service mutex/state + provider trait + in-flight dedupe)
|
|
tokio = { workspace = true }
|
|
async-trait = { workspace = true }
|
|
futures-util = { workspace = true }
|
|
|
|
# Logging
|
|
log = { workspace = true }
|
|
|
|
[dev-dependencies]
|
|
tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] }
|
|
tempfile = "3"
|