Repository: beetbox/beets

Issue:
lyrics: TypeError: object of type 'NoneType' has no len()
<!--
Describe your problem, feature request, or discussion topic here.

Please fill out this and the "Setup" section below and remember to include
enough detail so that other people can reproduce the problem.
-->

### Problem

Running this command in verbose (`-vv`) mode:

```sh
$ beet -vv import /mnt/media/downloads/Audio/Clay\ Aiken/Measure\ of\ a\ Man\ \[2003\]/
```

Led to this problem: Started from album choice

```
Correcting tags from:
    Clay Aiken - Measure Of A Man
To:
    Clay Aiken - Measure of a Man
URL:
    https://musicbrainz.org/release/c3820351-a467-491e-91b7-326c2ceadf60
(Similarity: 74.9%) (missing tracks, tracks) (2003)
 * Run To Me                     -> Run to Me
 * This Is The Night (Bonus Cut) -> This Is the Night (title)
Missing tracks (7/13 - 53.8%):
 ! When You Say You Love Me   (# 4) (4:07)
 ! No More Sad Songs          (# 5) (4:01)
 ! I Survived You             (# 8) (3:34)
 ! Perfect Day                (#10) (3:52)
 ! Measure of a Man           (#11) (3:58)
 ! Touch                      (#12) (3:51)
 ! Bridge Over Troubled Water (#13) (4:01)
Apply, More candidates, Skip, Use as-is, as Tracks, Group albums,
Enter search, enter Id, aBort, plaY? a
Sending event: import_task_choice
Sending event: import_task_apply
0 of 6 items replaced
Sending event: database_change
Sending event: database_change
Sending event: database_change
smartplaylist: all.m3u8 will be updated because of Clay Aiken - Measure of a Man - Invisible - 4:03
smartplaylist: recently_added.m3u8 will be updated because of Clay Aiken - Measure of a Man - Invisible - 4:03
smartplaylist: car mix.m3u8 will be updated because of Clay Aiken - Measure of a Man - Invisible - 4:03
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
replaygain: analyzing Clay Aiken - Measure of a Man
replaygain: analyzing Clay Aiken - Measure of a Man - Invisible - 4:03
replaygain: executing ffmpeg -nostats -hide_banner -i /mnt/media/downloads/Audio/Clay Aiken/Measure of a Man [2003]/01 Invisible.flac -map a:0 -filter ebur128=peak=true -f null -
replaygain: Clay Aiken - Measure of a Man - Invisible - 4:03: 2269 blocks over -18.7 LUFS
replaygain: Clay Aiken - Measure of a Man - Invisible - 4:03: gain -14.5 LU, peak 1.1748975549395295
replaygain: analyzing Clay Aiken - Measure of a Man - I Will Carry You - 3:44
replaygain: executing ffmpeg -nostats -hide_banner -i /mnt/media/downloads/Audio/Clay Aiken/Measure of a Man [2003]/02-clay_aiken-i_will_carry_you.flac -map a:0 -filter ebur128=peak=true -f null -
replaygain: Clay Aiken - Measure of a Man - I Will Carry You - 3:44: 2122 blocks over -18.3 LUFS
replaygain: Clay Aiken - Measure of a Man - I Will Carry You - 3:44: gain -14.8 LU, peak 1.109174815262401
replaygain: analyzing Clay Aiken - Measure of a Man - The Way - 4:06
replaygain: executing ffmpeg -nostats -hide_banner -i /mnt/media/downloads/Audio/Clay Aiken/Measure of a Man [2003]/03-clay_aiken-the_way.flac -map a:0 -filter ebur128=peak=true -f null -
replaygain: Clay Aiken - Measure of a Man - The Way - 4:06: 2315 blocks over -20.2 LUFS
replaygain: Clay Aiken - Measure of a Man - The Way - 4:06: gain -13.0 LU, peak 1.109174815262401
replaygain: analyzing Clay Aiken - Measure of a Man - Run to Me - 3:33
replaygain: executing ffmpeg -nostats -hide_banner -i /mnt/media/downloads/Audio/Clay Aiken/Measure of a Man [2003]/06-clay_aiken-run_to_me.flac -map a:0 -filter ebur128=peak=true -f null -
replaygain: Clay Aiken - Measure of a Man - Run to Me - 3:33: 1918 blocks over -20.0 LUFS
replaygain: Clay Aiken - Measure of a Man - Run to Me - 3:33: gain -13.4 LU, peak 1.1885022274370185
replaygain: analyzing Clay Aiken - Measure of a Man - Shine - 4:10
replaygain: executing ffmpeg -nostats -hide_banner -i /mnt/media/downloads/Audio/Clay Aiken/Measure of a Man [2003]/07-clay_aiken-shine.flac -map a:0 -filter ebur128=peak=true -f null -
replaygain: Clay Aiken - Measure of a Man - Shine - 4:10: 2317 blocks over -18.6 LUFS
replaygain: Clay Aiken - Measure of a Man - Shine - 4:10: gain -14.6 LU, peak 1.0839269140212036
replaygain: analyzing Clay Aiken - Measure of a Man - This Is the Night - 3:32
replaygain: executing ffmpeg -nostats -hide_banner -i /mnt/media/downloads/Audio/Clay Aiken/Measure of a Man [2003]/09-clay_aiken-this_is_the_night_(bonus_cut).flac -map a:0 -filter ebur128=peak=true -f null -
replaygain: Clay Aiken - Measure of a Man - This Is the Night - 3:32: 1989 blocks over -19.8 LUFS
replaygain: Clay Aiken - Measure of a Man - This Is the Night - 3:32: gain -13.4 LU, peak 1.109174815262401
replaygain: Clay Aiken - Measure of a Man: gain -14.023134753586442 LU, peak 1.1885022274370185
Sending event: database_change
replaygain: applied track gain -14.5 LU, peak 1.1748975549395295 of FS
Sending event: database_change
replaygain: applied album gain -14.023134753586442 LU, peak 1.1885022274370185 of FS
replaygain: done analyzing Clay Aiken - Measure of a Man - Invisible - 4:03
Sending event: database_change
replaygain: applied track gain -14.8 LU, peak 1.109174815262401 of FS
Sending event: database_change
replaygain: applied album gain -14.023134753586442 LU, peak 1.1885022274370185 of FS
replaygain: done analyzing Clay Aiken - Measure of a Man - I Will Carry You - 3:44
Sending event: database_change
replaygain: applied track gain -13.0 LU, peak 1.109174815262401 of FS
Sending event: database_change
replaygain: applied album gain -14.023134753586442 LU, peak 1.1885022274370185 of FS
replaygain: done analyzing Clay Aiken - Measure of a Man - The Way - 4:06
Sending event: database_change
replaygain: applied track gain -13.4 LU, peak 1.1885022274370185 of FS
Sending event: database_change
replaygain: applied album gain -14.023134753586442 LU, peak 1.1885022274370185 of FS
replaygain: done analyzing Clay Aiken - Measure of a Man - Run to Me - 3:33
Sending event: database_change
replaygain: applied track gain -14.6 LU, peak 1.0839269140212036 of FS
Sending event: database_change
replaygain: applied album gain -14.023134753586442 LU, peak 1.1885022274370185 of FS
replaygain: done analyzing Clay Aiken - Measure of a Man - Shine - 4:10
Sending event: database_change
replaygain: applied track gain -13.4 LU, peak 1.109174815262401 of FS
Sending event: database_change
replaygain: applied album gain -14.023134753586442 LU, peak 1.1885022274370185 of FS
replaygain: done analyzing Clay Aiken - Measure of a Man - This Is the Night - 3:32
lastgenre: added last.fm album genre (album): Pop
Sending event: database_change
Sending event: database_change
smartplaylist: pop.m3u8 will be updated because of Clay Aiken - Measure of a Man - Invisible - 4:03
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
Sending event: database_change
lastgenre: added last.fm item genre (track): Pop
Sending event: database_change
lastgenre: added last.fm item genre (track): Pop
Sending event: database_change
lastgenre: added last.fm item genre (track): Pop
Sending event: database_change
lastgenre: added last.fm item genre (track): Pop
Sending event: database_change
lastgenre: added last.fm item genre (track): Pop
Sending event: database_change
lastgenre: added last.fm item genre (track): Pop
Sending event: database_change
oldestdate: Applying changes to Clay Aiken - Invisible
Sending event: database_change
oldestdate: Applying changes to Clay Aiken - I Will Carry You
Sending event: database_change
oldestdate: Applying changes to Clay Aiken - The Way
Sending event: database_change
oldestdate: Applying changes to Clay Aiken - Run to Me
Sending event: database_change
oldestdate: Applying changes to Clay Aiken - Shine
Sending event: database_change
oldestdate: Applying changes to Clay Aiken - This Is the Night
Sending event: database_change
Traceback (most recent call last):
  File "/home/phire/.local/bin/beet", line 8, in <module>
    sys.exit(main())
  File "/home/phire/.local/lib/python3.10/site-packages/beets/ui/__init__.py", line 1301, in main
    _raw_main(args)
  File "/home/phire/.local/lib/python3.10/site-packages/beets/ui/__init__.py", line 1288, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/home/phire/.local/lib/python3.10/site-packages/beets/ui/commands.py", line 1056, in import_func
    import_files(lib, paths, query)
  File "/home/phire/.local/lib/python3.10/site-packages/beets/ui/commands.py", line 996, in import_files
    session.run()
  File "/home/phire/.local/lib/python3.10/site-packages/beets/importer.py", line 353, in run
    pl.run_parallel(QUEUE_SIZE)
  File "/home/phire/.local/lib/python3.10/site-packages/beets/util/pipeline.py", line 446, in run_parallel
    raise exc_info[1].with_traceback(exc_info[2])
  File "/home/phire/.local/lib/python3.10/site-packages/beets/util/pipeline.py", line 311, in run
    out = self.coro.send(msg)
  File "/home/phire/.local/lib/python3.10/site-packages/beets/util/pipeline.py", line 193, in coro
    func(*(args + (task,)))
  File "/home/phire/.local/lib/python3.10/site-packages/beets/importer.py", line 1604, in plugin_stage
    func(session, task)
  File "/home/phire/.local/lib/python3.10/site-packages/beets/plugins.py", line 145, in wrapper
    return func(*args, **kwargs)
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 942, in imported
    self.fetch_item_lyrics(session.lib, item,
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 956, in fetch_item_lyrics
    lyrics = [self.get_lyrics(artist, title) for title in titles]
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 956, in <listcomp>
    lyrics = [self.get_lyrics(artist, title) for title in titles]
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 989, in get_lyrics
    lyrics = backend.fetch(artist, title)
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 709, in fetch
    lyrics = scrape_lyrics_from_html(html)
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 583, in scrape_lyrics_from_html
    soup = try_parse_html(html,
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 224, in try_parse_html
    return bs4.BeautifulSoup(html, 'html.parser', **kwargs)
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/__init__.py", line 362, in __init__
    self._feed()
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/__init__.py", line 448, in _feed
    self.builder.feed(self.markup)
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/builder/_htmlparser.py", line 392, in feed
    parser.feed(markup)
  File "/usr/lib/python3.10/html/parser.py", line 110, in feed
    self.goahead(0)
  File "/usr/lib/python3.10/html/parser.py", line 170, in goahead
    k = self.parse_starttag(i)
  File "/usr/lib/python3.10/html/parser.py", line 344, in parse_starttag
    self.handle_starttag(tag, attrs)
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/builder/_htmlparser.py", line 151, in handle_starttag
    tag = self.soup.handle_starttag(
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/__init__.py", line 713, in handle_starttag
    or not self.parse_only.search_tag(name, attrs))):
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/element.py", line 2108, in search_tag
    if not self._matches(attr_value, match_against):
  File "/home/phire/.local/lib/python3.10/site-packages/bs4/element.py", line 2177, in _matches
    return match_against(markup)
  File "/home/phire/.local/lib/python3.10/site-packages/beetsplug/lyrics.py", line 575, in is_text_notcode
    length = len(text)
TypeError: object of type 'NoneType' has no len()
```

Here's a link to the music files that trigger the bug (if relevant):


### Setup

* OS: Ubuntu 22.04
* Python version:  3.10.12
* beets version: reports 1.6.1 but installed from master as of today
* Turning off plugins made problem go away (yes/no): This is a plugin issue

<!--
You can turn off plugins temporarily by passing --plugins= on the command line:

$ beet --plugins= version
-->

My configuration (output of `beet config`) is:

```yaml
lyrics:
    bing_lang_from: []
    google_API_key: REDACTED
    sources: google lyricwiki musixmatch genius
    force: no
    auto: yes
    bing_client_secret: REDACTED
    bing_lang_to:
    google_engine_ID: REDACTED
    genius_api_key: REDACTED
    fallback:
    local: no
    dist_thresh: 0.1
directory: /mnt/media/Audio/Music
library: /mnt/media/Audio/Music/musiclibrary.db

import:
    copy: yes
    autotag: yes
    bell: yes
    log: /home/phire/beets_import.log
    from_scratch: yes
    write: yes

musicbrainz:
    searchlimit: 20

match:
    ignored_media:
    - Data CD
    - HD-DVD
    - VCD
    - SVCD
    - UMD
    - VHS
    distance_weights:
        work_id: 4

clutter:
- Thumbs.DB
- .DS_Store
- '*.jpg'
- '*.png'
- '*.accurip'
- '*.log'
- '*.cue'
threaded: yes

ui:
    color: yes

format_item: $artist - $album - $title - $length

plugins: musicintegrity discogs deezer convert permissions info missing chroma oldestdate acousticbrainz absubmit play playlist duplicates badfiles lastimport lastgenre replaygain fetchart embedart lyrics smartplaylist
convert:
    dest: /mnt/thumb
    pretend: no
    link: no
    hardlink: no
    threads: 8
    format: mp3
    id3v23: inherit
    formats:
        aac:
            command: ffmpeg -i $source -y -vn -acodec aac -aq 1 $dest
            extension: m4a
        alac:
            command: ffmpeg -i $source -y -vn -acodec alac $dest
            extension: m4a
        flac: ffmpeg -i $source -y -vn -acodec flac $dest
        mp3: ffmpeg -i $source -y -vn -aq 2 $dest
        opus: ffmpeg -i $source -y -vn -acodec libopus -ab 96k $dest
        ogg: ffmpeg -i $source -y -vn -acodec libvorbis -aq 3 $dest
        wma: ffmpeg -i $source -y -vn -acodec wmav2 -vn $dest
    max_bitrate:
    auto: no
    auto_keep: no
    tmpdir:
    quiet: no
    embed: yes

    paths: {}
    no_convert: ''
    never_convert_lossy_files: no
    copy_album_art: no
    album_art_maxwidth: 0
    delete_originals: no
    playlist:
permissions:
    file: 664
    dir: 775
fetchart:
    google_key: REDACTED
    sources: coverart itunes amazon albumart google
    auto: yes
    maxwidth: 500
    minwidth: 0
    quality: 0
    max_filesize: 0
    enforce_ratio: no
    cautious: no
    cover_names:
    - cover
    - front
    - art
    - album
    - folder
    store_source: no
    high_resolution: no
    deinterlace: no
    cover_format:
    google_engine: 001442825323518660753:hrh5ch1gjzm
    fanarttv_key: REDACTED
    lastfm_key: REDACTED
embedart:
    remove_art_file: yes
    maxwidth: 0
    auto: yes
    compare_threshold: 0
    ifempty: no
    quality: 0
oldestdate:
    auto: yes
    force: yes
    overwrite_year: yes
    prompt_missing_work_id: no
    ignore_track_id: no
    filter_on_import: yes
    overwrite_date: no
    filter_recordings: yes
    approach: releases
    release_types:
    use_file_date: no
replaygain:
    auto: yes
    backend: ffmpeg
    overwrite: yes
    targetlevel: 84
    threads: 8
    parallel_on_import: no
    per_disc: no
    peak: 'true'
    r128: [Opus]
    r128_targetlevel: 84
lastgenre:
    auto: yes
    canonical: no
    source: track
    whitelist: yes
    min_weight: 10
    count: 1
    fallback:
    force: yes
    separator: ', '
    prefer_specific: no
    title_case: yes
lastfm:
    user: phirestalker
    api_key: REDACTED
duplicates:
    tiebreak:
        items: [samplerate]
    album: no
    checksum: ''
    copy: ''
    count: no
    delete: no
    format: ''
    full: no
    keys: []
    merge: no
    move: ''
    path: no
    strict: no
    tag: ''
playlist:
    auto: no
    playlist_dir: /mnt/media/Audio/Music/playlists
    relative_to: /mnt/media/Audio/Music/playlists
    forward_slash: no
smartplaylist:
    playlist_dir: /mnt/media/Audio/Music/playlists
    relative_to: /mnt/media/Audio/Music/playlists
    playlists:
    -   name: all.m3u8
        query: ''
    -   name: car mix.m3u8
        query: ^genre:classical ^genre:death ^genre:rap ^genre:holiday ^genre:religious ^genre:christian ^genre:industrial ^genre:nu ^album:christmas ^album:holiday ^artist:choir ^artist:libera
    -   name: 50s.m3u8
        query: year:1950..1959 ^genre:punk ^genre:grunge ^genre:hard ^genre:metal ^genre:classical ^genre:alternative ^genre:rap ^genre:"hip hop" ^christmas ^rudolph
    -   name: 60s.m3u8
        query: year:1960..1969 ^genre:punk ^genre:grunge ^genre:hard ^genre:metal ^genre:classical ^genre:alternative ^genre:rap ^genre:"hip hop" ^christmas ^rudolph
    -   name: 70s.m3u8
        query: year:1970..1979 ^genre:punk ^genre:grunge ^genre:hard ^genre:metal ^genre:classical ^genre:alternative ^genre:rap ^genre:"hip hop" ^christmas ^rudolph
    -   name: 80s.m3u8
        query: year:1980..1989 ^genre:punk ^genre:grunge ^genre:hard ^genre:metal ^genre:classical ^genre:alternative ^genre:rap ^genre:"hip hop" ^christmas ^rudolph
    -   name: 90s.m3u8
        query: year:1990..1999 ^genre:punk ^genre:grunge ^genre:hard ^genre:metal ^genre:classical ^genre:alternative ^genre:rap ^genre:"hip hop" ^christmas ^rudolph
    -   name: metal.m3u8
        query: genre:metal
    -   name: pop.m3u8
        query: genre:pop
    -   name: recently_added.m3u8
        query: added:-1m..
    -   name: oldies.m3u8
        query: year:1930..1989 ^genre:punk ^genre:grunge ^genre:hard ^genre:metal ^genre:classical ^genre:alternative ^genre:rap ^genre:"hip hop" ^christmas ^rudolph
    -   name: comedy.m3u8
        query: genre:comedy, genre:humor, genre:parody
    -   name: male singers.m3u8
        query:
        - artist:"backstreet boys"
        - artist:declan
        - artist:"one direction"
        - artist:"aaron carter"
        - artist:"austin mahone"
        - artist:"jesse mccartney"
        - artist:"nick lachey"
        - artist:"billy gilman"
        - artist:nsync
        - artist:"ronan parke"
        - artist:"troye sivan"
        - artist:libera
        - artist:choir
        - artist:"new hope club"
        - artist:"justin bieber"
        - artist:jonas
        - artist:"5 seconds of summer"
        - artist:"greyson chance"
        - artist:hrvy
        - artist:"aj mitchell"
        - artist:"midnight red"
        - artist:"big time rush"
        - artist:"alec benjamin"
        - artist:"owen mac"
        - artist:bts
        - artist:"charlie puth"
        - artist:"lewis capaldi"
        - artist:"joshua bassett"
        - artist:"straight no chaser"
        - artist:"ross lynch"
        - artist:r5
        - artist:hanson
        - artist:rixton
        - artist:lauv
        - artist:"dream street"
        - artist:"bone man"
        - artist:"K-Ci & JoJo"
        - "artist:J\xF3nsi"
        - artist:"brian mcknight"
        - artist:"the vamps"
        - artist:"niall horan"
        - artist:"cody simpson"
        - artist:"why don't we"
    auto: yes
    forward_slash: no
    prefix: REDACTED
    urlencode: no
    pretend_paths: no

paths:
    default: $albumartist/$album%aunique{}/$track $title
musicintegrity:
    enabled: yes
    check: yes
    extra_args: [-n1]
    par2_exe: ''
    recovery: '15'
    memory: '1024'
acoustid:
    apikey: REDACTED
absubmit:
    extractor: /usr/local/bin/streaming_extractor_music
    force: no
    pretend: no
    base_url: ''
deezer:
    source_weight: 0.5
play:
    command:
    use_folders: no
    relative_to:
    raw: no
    warning_threshold: 100
    bom: no
discogs:
    apikey: REDACTED
    apisecret: REDACTED
    tokenfile: discogs_token.json
    source_weight: 0.5
    user_token: REDACTED
    separator: ', '
    index_tracks: no
    append_style_genre: no
acousticbrainz:
    auto: yes
    force: no
    tags: []
    base_url: ''
missing:
    count: no
    total: no
    album: no
lastimport:
    per_page: 500
    retry_limit: 3
chroma:
    auto: yes
```



Discussion:

Resolving patch:
diff --git a/beetsplug/lyrics.py b/beetsplug/lyrics.py
--- a/beetsplug/lyrics.py
+++ b/beetsplug/lyrics.py
@@ -572,6 +572,8 @@ def scrape_lyrics_from_html(html):
     instead.
     """
     def is_text_notcode(text):
+        if not text:
+            return False
         length = len(text)
         return (length > 20 and
                 text.count(' ') > length / 25 and


Test patch: