Skip to content

lrclib: skip tracks where both plainLyrics and syncedLyrics are null#6767

Open
stefanvanburen wants to merge 1 commit into
beetbox:masterfrom
stefanvanburen:fix/lrclib-null-lyrics
Open

lrclib: skip tracks where both plainLyrics and syncedLyrics are null#6767
stefanvanburen wants to merge 1 commit into
beetbox:masterfrom
stefanvanburen:fix/lrclib-null-lyrics

Conversation

@stefanvanburen

Copy link
Copy Markdown

lrclib occasionally returns records with instrumental: false but plainLyrics: null and syncedLyrics: null — tracks that simply have no lyrics stored yet. A concrete example is Daft Punk - Nightvision (id 249829): lrclib has the record but no lyrics text for it:

$ curl 'https://lrclib.net/api/get?artist_name=Daft+Punk&track_name=Nightvision&album_name=Discovery' | jq
{
  "id": 249829,
  "name": "Nightvision",
  "trackName": "Nightvision",
  "artistName": "Daft Punk",
  "albumName": "Discovery",
  "duration": 104.0,
  "instrumental": false,
  "plainLyrics": null,
  "syncedLyrics": null,
  "lyricsfile": null
}

Before this change, LRCLyrics.get_text() would return None (the value of self.plain) and LRCLib.fetch() would pass it straight to Lyrics(None, ...), causing an AttributeError in Lyrics._split_lines when it called None.splitlines().

Fix both layers:

  • LRCLib.fetch: guard the Lyrics construction with if lyrics := item.get_text(...) so a null result is treated as "not found" and the backend continues searching other candidates.
  • Lyrics._split_lines: add a defensive (self.text or "") guard so the dataclass does not crash if it is ever constructed with text=None through another code path.

Add a test case that exercises the null/null response path.

  • Documentation.
  • Changelog.
  • Tests.

lrclib occasionally returns records with `instrumental: false` but
`plainLyrics: null` and `syncedLyrics: null` — tracks that simply have
no lyrics stored yet.  A concrete example is Daft Punk - Nightvision
(id 249829): lrclib has the record but no lyrics text for it.

Before this change, `LRCLyrics.get_text()` would return `None` (the
value of `self.plain`) and `LRCLib.fetch()` would pass it straight to
`Lyrics(None, ...)`, causing an `AttributeError` in `Lyrics._split_lines`
when it called `None.splitlines()`.

Fix both layers:

- `LRCLib.fetch`: guard the `Lyrics` construction with
  `if lyrics := item.get_text(...)` so a null result is treated as "not
  found" and the backend continues searching other candidates.
- `Lyrics._split_lines`: add a defensive `(self.text or "")` guard so
  the dataclass does not crash if it is ever constructed with `text=None`
  through another code path.

Add a test case that exercises the null/null response path.
Copilot AI review requested due to automatic review settings June 23, 2026 23:53
@stefanvanburen stefanvanburen requested a review from a team as a code owner June 23, 2026 23:53
@github-actions github-actions Bot added the lyrics lyrics plugin label Jun 23, 2026
@github-actions

Copy link
Copy Markdown

Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

grug see PR try make lrclib backend not crash when API say instrumental=false but both lyrics fields null. goal good: treat that like “not found” and keep searching, plus add extra guard in Lyrics split.

Changes:

  • LRCLib.fetch now skip building Lyrics(...) when item.get_text(...) return null-ish.
  • Lyrics._split_lines now tolerate text=None by using empty string.
  • Add pytest case for lrclib response where both plainLyrics and syncedLyrics are null.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
test/plugins/test_lyrics.py add coverage for lrclib response with both lyrics fields null
beetsplug/lyrics.py change LRCLib.fetch to skip null lyrics text
beets/util/lyrics.py guard _split_lines against text=None

Comment thread beetsplug/lyrics.py
Comment on lines 400 to +404
if item := self.pick_best_match(candidates):
lyrics = item.get_text(self.config["synced"].get(bool))
return Lyrics(
lyrics, self.__class__.name, f"{self.GET_URL}/{item.id}"
)
if lyrics := item.get_text(self.config["synced"].get(bool)):
return Lyrics(
lyrics, self.__class__.name, f"{self.GET_URL}/{item.id}"
)
Comment thread beets/util/lyrics.py
Comment on lines 106 to 109
return [
(m[1], m[2]) if (m := self.LINE_PARTS_PAT.match(line)) else ("", "")
for line in self.text.splitlines()
for line in (self.text or "").splitlines()
]
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 74.95%. Comparing base (7c90645) to head (e6be20e).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #6767   +/-   ##
=======================================
  Coverage   74.95%   74.95%           
=======================================
  Files         163      163           
  Lines       20893    20893           
  Branches     3291     3292    +1     
=======================================
  Hits        15661    15661           
  Misses       4474     4474           
  Partials      758      758           
Files with missing lines Coverage Δ
beets/util/lyrics.py 96.70% <ø> (ø)
beetsplug/lyrics.py 89.78% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

lyrics lyrics plugin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants