Skip to content

feat(lyrics): add waveflow-syncedlyrics crate with multi-provider lookup#207

Open
InstaZDLL wants to merge 4 commits into
mainfrom
feat/syncedlyrics-crate
Open

feat(lyrics): add waveflow-syncedlyrics crate with multi-provider lookup#207
InstaZDLL wants to merge 4 commits into
mainfrom
feat/syncedlyrics-crate

Conversation

@InstaZDLL

@InstaZDLL InstaZDLL commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Résumé

Ajoute la nouvelle crate waveflow-syncedlyrics — un portage Rust du projet Python syncedlyrics (projet original local : /home/nayeon/workspace/syncedlyrics) qui fournit un lookup de paroles multi-providers : Musixmatch, LRCLIB, NetEase, Megalobiz, Genius.

L'intégration est branchée dans src-tauri/crates/app/src/commands/lyrics.rs :

  • Musixmatch Enhanced est tenté avant LRCLIB, mais uniquement s'il renvoie réellement du word-level.
  • LRCLIB exact reste le match strict.

Conformité licence

WaveFlow reste sous GPL-3.0-only. Ajout d'un THIRD_PARTY_NOTICES.md qui préserve la notice MIT originale (Copyright (c) 2022 Momo, Copyright (c) 2026 InstaZDLL), lié depuis le README. MIT est compatible GPL-3.

Vérifications

  • cargo fmt --manifest-path src-tauri/Cargo.toml --all --check
  • cargo check --manifest-path src-tauri/Cargo.toml -p waveflow-syncedlyrics --all-targets
  • cargo test --manifest-path src-tauri/Cargo.toml -p waveflow-syncedlyrics
  • cargo check --manifest-path src-tauri/Cargo.toml -p waveflow --all-targets (passe sans workaround ALSA depuis l'installation de libasound2-dev)

Summary by CodeRabbit

  • Nouvelles Fonctionnalités

    • Paroles : prise en charge étendue de fournisseurs (Musixmatch, NetEase, Megalobiz, Genius + LRCLIB), recherche multi‑fournisseurs avec fallback, support des paroles enrichies (word‑level) et meilleure détection/formatage.
    • Intégration d’un moteur multi‑fournisseurs local pour les recherches (améliore prélecture et préchargement).
  • Documentation

    • Guides mis à jour : sources de paroles, rules de cache, timeouts, options cookies.
    • Notices tierces et référence de licence centralisée (renvoi vers LICENSE / THIRD_PARTY_NOTICES).

Rust port of the syncedlyrics Python project providing multi-provider
lyrics lookup (Musixmatch, LRCLIB, NetEase, Megalobiz, Genius). Wired
into commands/lyrics.rs: Musixmatch Enhanced is tried before LRCLIB but
only when it returns true word-level lyrics; LRCLIB exact stays the
strict match.

Adds THIRD_PARTY_NOTICES.md preserving the original MIT notice
(Copyright (c) 2022 Momo, Copyright (c) 2026 InstaZDLL); WaveFlow
remains GPL-3.0-only.
@InstaZDLL InstaZDLL added scope: backend Rust/Tauri backend (src-tauri/) scope: deps Dependencies scope: docs Docs, README, assets type: feat New feature labels Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aacdb0c6-d44b-403d-9bfa-262a3b0d14d4

📥 Commits

Reviewing files that changed from the base of the PR and between 83afff8 and 1344987.

📒 Files selected for processing (1)
  • src-tauri/crates/syncedlyrics/src/lib.rs

📝 Walkthrough

Walkthrough

Cette PR ajoute une crate Rust indépendante waveflow-syncedlyrics implémentant la recherche multi-fournisseurs de paroles (Musixmatch, LRCLIB, NetEase, Megalobiz, Genius), puis l'intègre dans le flux de commandes existant avec un waterfall étendu, support des formats Enhanced LRC et gestion améliorée du cache.

Changes

Architecture multi-fournisseurs de paroles

Layer / File(s) Summary
Documentation et configuration du workspace
README.md, THIRD_PARTY_NOTICES.md, docs/README.md, docs/architecture/crates.md, docs/features/integrations.md, src-tauri/Cargo.toml, src-tauri/crates/app/Cargo.toml
Mise à jour des documents pour refléter l'ajout du crate waveflow-syncedlyrics et l'élargissement des fournisseurs de paroles (LRCLIB + Musixmatch/NetEase/Megalobiz/Genius); actualisation des manifestes Cargo pour inclure le nouveau crate en workspace et comme dépendance de l'app.
Fondation de la crate waveflow-syncedlyrics
src-tauri/crates/syncedlyrics/Cargo.toml, src-tauri/crates/syncedlyrics/src/lib.rs
Manifeste Cargo du crate avec dépendances (reqwest, serde, thiserror, tracing); définition des types publics (SearchMode, SearchOptions, LyricsResult), modèle d'erreurs, et client HTTP SyncedLyricsClient avec orchestration de la recherche multi-fournisseurs.
Implémentations des fournisseurs
src-tauri/crates/syncedlyrics/src/providers/mod.rs, .../lrclib.rs, .../musixmatch.rs, .../genius.rs, .../netease.rs, .../megalobiz.rs
Énumération Provider sérialisable avec méthode defaults(); implémentation des modules providers : LRCLIB (heuristique de sélection), Musixmatch (token, richsync/subtitle/translations), Genius (extraction HTML), NetEase (search + lrc), Megalobiz (parsing HTML + classification).
Utilitaires de traitement de paroles
src-tauri/crates/syncedlyrics/src/utils.rs
Détection de format (Plain/Lrc/EnhancedLrc) incluant analyse de word stamps, conversion synced→plaintext, décodage HTML ad-hoc, formatage mm:ss.cc, calcul de similarité textuelle (str_score) et tests unitaires.
Intégration dans le flux de récupération de paroles
src-tauri/crates/app/src/commands/lyrics.rs
Ajout du tier Musixmatch enhanced dans fetch_lyrics et prefetch_library_lyrics; intégration d'une chaîne de providers query-based en fallback lorsque LRCLIB ne produit pas de résultat; ajustement de la logique de cache, comptage hits/misses/failed, throttling et support de cookies via variables d'environnement.

Sequence Diagram

sequenceDiagram
  participant App as App::fetch_lyrics / prefetch
  participant Synced as SyncedLyricsClient
  participant Provider as Provider (Musixmatch / LRCLIB / NetEase / Megalobiz / Genius)
  participant Cache as LyricsCache

  App->>Cache: check existing entry
  alt miss
    App->>Synced: search(SearchOptions)
    Synced->>Provider: call provider.search(...)
    Provider-->>Synced: Candidate (synced/unsynced)
    Synced-->>App: LyricsResult | None
    alt result
      App->>Cache: upsert result
    else no result
      App->>Cache: upsert empty miss
    end
  else hit
    Cache-->>App: return cached content
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • InstaZDLL/WaveFlow#117: Modifications antérieures du waterfall de récupération des paroles (sidecar / préfetch) qui se recoupent avec l'élargissement des providers ici.
  • InstaZDLL/WaveFlow#169: Modifications récentes du pipeline LRCLIB / import du client ; potentiellement impacté par l'intégration de waveflow-syncedlyrics.
  • InstaZDLL/WaveFlow#44: Changements autour du préfetch/rapport de progression ; se situe sur les mêmes points d'intégration prefetch/run_prefetch.

Poem

🎵 Cinq sources chantent sous un seul fil,
Musixmatch, Genius et NetEase au profil,
Megalobiz fouille, LRCLIB fournit le filigrane,
Le client assemble, détecte l'Enhanced LRC,
La prélecture fredonne, la bibliothèque s'organise.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Le titre suit précisément le standard Conventional Commits avec le scope 'lyrics' en kebab-case et décrit fidèlement l'ajout principal du PR : la nouvelle crate waveflow-syncedlyrics avec lookup multi-providers.
Description check ✅ Passed La description couvre les points essentiels : objectif (portage Rust de syncedlyrics), intégration dans le code, conformité licence GPL-3.0, et détails des vérifications effectuées localement.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/syncedlyrics-crate

Comment @coderabbitai help to get the list of available commands and usage tips.

@InstaZDLL InstaZDLL added the size: xl > 500 lines label Jun 8, 2026
@InstaZDLL InstaZDLL self-assigned this Jun 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/crates/app/src/commands/lyrics.rs`:
- Around line 289-292: Le match arm Err(err) => { tracing::debug!(?err,
"external lyrics search failed"); None } wrongly maps provider errors to “no
result” causing transient failures to be cached as empty; change the function to
propagate errors instead of returning None on Err: update the function signature
to return Result<Option<...>, E> (or propagate the provider error type), replace
the Err(...) branch with returning Err(err) (or use the ? operator where
appropriate), and ensure the caching path only stores an empty entry for
explicit Ok(None) results so transient provider errors are not cached as misses.
- Around line 249-253: The external_query function builds a search string using
artist_name but incorrectly splits multi-artist tags with split(", "), which
breaks names containing commas; update the logic in external_query (the branch
that defines primary_artist) to split on "; " instead (i.e., use split(";
").next().unwrap_or(artist)) so multi-artist queries follow the project's "; "
separator convention while preserving artists containing commas.
- Around line 268-294: Add a process-wide offline guard inside
external_lyrics_search so outbound network calls are short-circuited: before
constructing SyncedLyricsClient or calling client.search(...), check
offline::is_offline() and immediately return None when offline; this ensures
external_lyrics_search enforces the “offline process-wide” contract regardless
of callers and prevents the SyncedLyricsClient::search path from performing HTTP
requests while offline.

In `@src-tauri/crates/syncedlyrics/src/lib.rs`:
- Around line 116-123: Replace the panicking SyncedLyricsClient::new() with a
fallible constructor: add SyncedLyricsClient::try_new() -> Result<Self,
crate::Error> that builds the reqwest::Client and maps reqwest::Error into
Error::Http (or your crate::Error::Http variant); update all callsites that
construct the client (notably external_lyrics_search and the lyrics command at
src-tauri/crates/app/src/commands/lyrics.rs:275) to call try_new() and propagate
or handle the Result instead of letting it panic; keep or adapt impl Default to
avoid a global panic by using try_new().unwrap_or_else(|err| { log the error and
return a safe fallback Self { http: reqwest::Client::new() } }) so Default
remains usable without aborting the process.

In `@src-tauri/crates/syncedlyrics/src/providers/genius.rs`:
- Around line 17-23: The code currently uses a fixed index sections[1] when
extracting the Genius URL which breaks if section order changes; instead
iterate/search the sections array to find the first section that has a non-empty
"hits" array and extract the first hit's "result.url". Replace the chain
starting with response["response"]["sections"].as_array().and_then(|sections|
sections.get(1)) with logic that calls .as_array() on sections and then
.iter().find_map(|section| section["hits"].as_array().and_then(|hits|
hits.first()).and_then(|hit| hit["result"]["url"].as_str())) so you return the
first available URL reliably (referencing the response variable and the
"hits"/"result.url" accessors).

In `@src-tauri/crates/syncedlyrics/src/providers/lrclib.rs`:
- Around line 54-83: Ajoute un seuil minimal de score pour éviter de choisir des
pistes hors sujet : dans la fonction best_track, après calculer score via
utils::str_score (et/ou avant d'appeler is_better), ignore tout track dont score
est inférieur à un nouveau MIN_SCORE constant (ou config) et/ou retourne None si
best_score final est < MIN_SCORE; référencez best_track, utils::str_score et
is_better pour localiser l'endroit à modifier et définir MIN_SCORE en haut du
module ou en paramètre de la fonction.

In `@src-tauri/crates/syncedlyrics/src/providers/megalobiz.rs`:
- Around line 39-43: The loop that scans HTML using while let Some(idx) =
html[pos..].find("href=\"/lrc/maker/") currently uses the ? operator on computed
indices (href_end, tag_end, close) which causes the function to return None on a
single malformed link; change each of those uses so they don’t propagate None
but instead handle missing finds locally (e.g., use if let Some(...) or match
for href_end, tag_end and close) and skip the malformed link by advancing pos
(e.g., pos += idx + "href=\"".len() or similar) and continue the loop; update
the logic around the href_start/href_end/tag_end/close variables in that while
loop so it keeps scanning rather than returning early.

In `@src-tauri/crates/syncedlyrics/src/providers/musixmatch.rs`:
- Around line 142-149: La boucle qui utilise lrc.replace(original, ...) remplace
globalement toutes les occurrences et peut corrompre le LRC; instead iterate the
LRC by lines and replace only the specific line that equals
translation["subtitle_matched_line"] with the original plus "\n({translated})"
(or the first match per translation) rather than calling String::replace; locate
the translations loop (the for item in translations block) and replace the
global replace with a line-based mapping (split/join or iterate lines) that
compares exact equality to original and substitutes only that line.
- Around line 110-112: Le problème : l'appel add_translations(...).await? fait
échouer tout le provider si la traduction optionnelle échoue; remplacez le ? par
une gestion d'erreur non-fataile (par ex. match/if let Ok(result) =
add_translations(...).await { lrc = result } else { garder lrc existant et
logger l'erreur avec un warning}). Ciblez l'appel dans le bloc where if let
Some(lang) = lang { ... } et utilisez les symboles add_translations, lrc, http,
token, track_id, lang pour localiser le code; assurez-vous de ne pas perdre
l'original lrc en cas d'erreur et d'enregistrer l'erreur pour debug.

In `@src-tauri/crates/syncedlyrics/src/utils.rs`:
- Around line 8-20: La fonction synced_to_plaintext laisse des "word-stamps"
Enhanced LRC du type <mm:ss.xx> dans la sortie; mettez à jour la logique (soit
en étendant strip_lrc_stamp pour reconnaître et retirer aussi les word-stamps,
soit en ajoutant un nettoyage post-traitement dans synced_to_plaintext) pour
supprimer toutes les occurrences de <mm:ss.xx> dans chaque ligne avant de les
collecter; référez-vous à la fonction synced_to_plaintext et à strip_lrc_stamp
pour localiser où appliquer ce filtre afin que la sortie plaintext ne contienne
plus ces balises.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c564ce24-fdbe-4153-9b3e-62d46f8d5d61

📥 Commits

Reviewing files that changed from the base of the PR and between 4bec4bf and 02339e6.

⛔ Files ignored due to path filters (1)
  • src-tauri/Cargo.lock is excluded by !**/*.lock, !src-tauri/Cargo.lock
📒 Files selected for processing (17)
  • README.md
  • THIRD_PARTY_NOTICES.md
  • docs/README.md
  • docs/architecture/crates.md
  • docs/features/integrations.md
  • src-tauri/Cargo.toml
  • src-tauri/crates/app/Cargo.toml
  • src-tauri/crates/app/src/commands/lyrics.rs
  • src-tauri/crates/syncedlyrics/Cargo.toml
  • src-tauri/crates/syncedlyrics/src/lib.rs
  • src-tauri/crates/syncedlyrics/src/providers/genius.rs
  • src-tauri/crates/syncedlyrics/src/providers/lrclib.rs
  • src-tauri/crates/syncedlyrics/src/providers/megalobiz.rs
  • src-tauri/crates/syncedlyrics/src/providers/mod.rs
  • src-tauri/crates/syncedlyrics/src/providers/musixmatch.rs
  • src-tauri/crates/syncedlyrics/src/providers/netease.rs
  • src-tauri/crates/syncedlyrics/src/utils.rs

Comment thread src-tauri/crates/app/src/commands/lyrics.rs
Comment thread src-tauri/crates/app/src/commands/lyrics.rs
Comment thread src-tauri/crates/app/src/commands/lyrics.rs Outdated
Comment thread src-tauri/crates/syncedlyrics/src/lib.rs Outdated
Comment thread src-tauri/crates/syncedlyrics/src/providers/genius.rs
Comment thread src-tauri/crates/syncedlyrics/src/providers/lrclib.rs
Comment thread src-tauri/crates/syncedlyrics/src/providers/megalobiz.rs Outdated
Comment thread src-tauri/crates/syncedlyrics/src/providers/musixmatch.rs
Comment thread src-tauri/crates/syncedlyrics/src/providers/musixmatch.rs
Comment thread src-tauri/crates/syncedlyrics/src/utils.rs
- propagate provider errors instead of caching transient failures as empty
  misses (lib.rs surfaces last_error; external_lyrics_search returns Result)
- split multi-artist tags on "; " not ", " per project convention
- enforce the offline contract inside external_lyrics_search
- add SyncedLyricsClient::try_new() so client init no longer panics
- genius: pick the "song" section instead of a hardcoded index
- lrclib: drop results below a 65.0 score floor (matches sibling providers)
- megalobiz: skip a malformed anchor instead of aborting the whole scan
- utils: strip inline <mm:ss.xx> word-stamps from plaintext output

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/crates/syncedlyrics/src/lib.rs`:
- Around line 127-136: The current pub fn new() -> Self pretends to be
infallible but falls back to reqwest::Client::new() which can panic; change
new() to be fallible and propagate the try_new() error instead (i.e. change
signature to pub fn new() -> Result<Self, <the error type returned by
try_new()>) and implement it as simply Self::try_new(), removing the panic-prone
unwrap_or_else fallback; alternatively, if you must keep an infallible API,
replace the fallback with a safe build path (e.g. use
reqwest::Client::builder().build() and handle its Result) and update the doc
comment to reflect the real behavior — refer to new(), try_new(), and
reqwest::Client::new()/builder().build() when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 61ee2a13-3347-4102-8ca6-f87372675adf

📥 Commits

Reviewing files that changed from the base of the PR and between aa87363 and 83afff8.

📒 Files selected for processing (6)
  • src-tauri/crates/app/src/commands/lyrics.rs
  • src-tauri/crates/syncedlyrics/src/lib.rs
  • src-tauri/crates/syncedlyrics/src/providers/genius.rs
  • src-tauri/crates/syncedlyrics/src/providers/lrclib.rs
  • src-tauri/crates/syncedlyrics/src/providers/megalobiz.rs
  • src-tauri/crates/syncedlyrics/src/utils.rs

Comment thread src-tauri/crates/syncedlyrics/src/lib.rs Outdated
reqwest::Client::new() can itself panic on TLS/backend init failure, so the
"infallible" new() never truly was. Remove new() and the Default impl (both
unused — the only callsite uses try_new()), leaving try_new() as the sole,
fallible constructor.
@InstaZDLL

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: backend Rust/Tauri backend (src-tauri/) scope: deps Dependencies scope: docs Docs, README, assets size: xl > 500 lines type: feat New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant