Repository: beetbox/beets

Issue:
deezer: Tolerate missing `track_position` field
Currently I try to import some mixtapes = single long audio files which are mixed by a DJ and consisting of several titles.

### Problem

The Deezer plugin is the reason beets crashes everytime. It also happened yesterday with normal tracks of an album (sorry I deleted the logs already). If I deactivate the deezer plugin the mixtapes will be imported. What is the reason?

Running this command in verbose (`-vv`) mode:

```sh
beet -vv import -s --set track_type="mixtape" .
```

Led to this problem:

```
# beet -vv import -s --set track_type="mixtape" .                                                                               
user configuration: /Users/user/.config/beets/config.yaml
data directory: /Users/user/.config/beets
plugin paths:
Sending event: pluginload
inline: adding item field disc_and_track
inline: adding item field lossless_desc
library database: /Users/user/.config/beets/music.blb
library directory: /Volumes/mini/Music/Archiv_beets
Sending event: library_opened
Sending event: import_begin
ignoring b'.DS_Store' due to ignore rule b'.DS_Store'
Sending event: import_task_created
Sending event: import_task_start
badfiles: checking path: /Users/user/Music/tmp/Mixtapes/100% Beats by Dre.mp3
Sending event: import_task_created
badfiles: running command: mp3val "/Users/user/Music/tmp/Mixtapes/100% Beats by Dre.mp3"
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
Sending event: import_task_created
chroma: chroma: fingerprinted b'/Users/user/Music/tmp/Mixtapes/100% Beats by Dre.mp3'
chroma: no match found
Looking up: /Users/user/Music/tmp/Mixtapes/100% Beats by Dre.mp3
Item search terms: Dre - 100% Beats by Dre
Sending event: mb_track_extract
Sending event: trackinfo_received
Sending event: mb_track_extract
Sending event: trackinfo_received
Sending event: mb_track_extract
Sending event: trackinfo_received
Sending event: mb_track_extract
Sending event: trackinfo_received
Sending event: mb_track_extract
Sending event: trackinfo_received
spotify: Searching Spotify for '100% Beats by Dre artist:Dre'
spotify: Found 0 result(s) from Spotify for '100% Beats by Dre artist:Dre'
deezer: Searching Deezer for '100% Beats by Dre artist:"Dre"'
deezer: Found 25 result(s) from Deezer for '100% Beats by Dre artist:"Dre"'
Traceback (most recent call last):
  File "/usr/local/bin/beet", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.9/site-packages/beets/ui/__init__.py", line 1291, in main
    _raw_main(args)
  File "/usr/local/lib/python3.9/site-packages/beets/ui/__init__.py", line 1278, in _raw_main
    subcommand.func(lib, suboptions, subargs)
  File "/usr/local/lib/python3.9/site-packages/beets/ui/commands.py", line 973, in import_func
    import_files(lib, paths, query)
  File "/usr/local/lib/python3.9/site-packages/beets/ui/commands.py", line 943, in import_files
    session.run()
  File "/usr/local/lib/python3.9/site-packages/beets/importer.py", line 341, in run
    pl.run_parallel(QUEUE_SIZE)
  File "/usr/local/lib/python3.9/site-packages/beets/util/pipeline.py", line 442, in run_parallel
    six.reraise(exc_info[0], exc_info[1], exc_info[2])
  File "/usr/local/lib/python3.9/site-packages/six.py", line 719, in reraise
    raise value
  File "/usr/local/lib/python3.9/site-packages/beets/util/pipeline.py", line 309, in run
    out = self.coro.send(msg)
  File "/usr/local/lib/python3.9/site-packages/beets/util/pipeline.py", line 194, in coro
    func(*(args + (task,)))
  File "/usr/local/lib/python3.9/site-packages/beets/importer.py", line 1377, in lookup_candidates
    task.lookup_candidates()
  File "/usr/local/lib/python3.9/site-packages/beets/importer.py", line 912, in lookup_candidates
    prop = autotag.tag_item(self.item, search_ids=self.search_ids)
  File "/usr/local/lib/python3.9/site-packages/beets/autotag/match.py", line 520, in tag_item
    for track_info in hooks.item_candidates(item, search_artist, search_title):
  File "/usr/local/lib/python3.9/site-packages/beets/plugins.py", line 583, in decorated
    for v in generator(*args, **kwargs):
  File "/usr/local/lib/python3.9/site-packages/beets/autotag/hooks.py", line 652, in item_candidates
    for candidate in plugins.item_candidates(item, artist, title):
  File "/usr/local/lib/python3.9/site-packages/beets/plugins.py", line 401, in item_candidates
    for item_candidate in plugin.item_candidates(item, artist, title):
  File "/usr/local/lib/python3.9/site-packages/beets/plugins.py", line 763, in item_candidates
    return [self.track_for_id(track_data=track) for track in tracks]
  File "/usr/local/lib/python3.9/site-packages/beets/plugins.py", line 763, in <listcomp>
    return [self.track_for_id(track_data=track) for track in tracks]
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/beetsplug/deezer.py", line 159, in track_for_id
    track = self._get_track(track_data)
  File "/usr/local/Cellar/python@3.9/3.9.7_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/beetsplug/deezer.py", line 134, in _get_track
    index=track_data['track_position'],
KeyError: 'track_position'
```

Here's a link to the music files that trigger the bug (if relevant):


### Setup


    OS: macOS 11.6
    Turning off plugins made problem go away (yes/no): did not try

```
$ beet --plugins= version                          
beets version 1.5.0
Python version 3.9.7
no plugins loaded
```

My configuration (output of `beet config`) is:

```yaml
importfeeds:
    relative_to: /Volumes/mini/Music/Archiv_beets
    absolute_path: yes
    formats: m3u
    m3u_name: imported.m3u
    dir:
lyrics:
    bing_lang_from: []
    auto: yes
    google_API_key: REDACTED
    sources: google musixmatch genius tekstowo
    bing_client_secret: REDACTED
    bing_lang_to:
    google_engine_ID: REDACTED
    genius_api_key: REDACTED
    fallback:
    force: no
    local: no
library: ~/.config/beets/music.blb
directory: /Volumes/mini/Music/Archiv_beets

import:
    copy: no
    move: yes
    write: yes
    resume: yes
    incremental: yes
    autotag: yes
    incremental_skip_later: yes
    log: ~/beetslog.txt
    none_rec_action: ask
    detail: yes
    duplicate_action: ask

plugins: zero scrub acousticbrainz spotify fetchart fromfilename discogs embedart inline convert mbsync chroma lastgenre lyrics the badfiles duplicates fuzzy info missing web edit keyfinder importadded metasync play replaygain importfeeds summarize bpmanalyser deezer
ignore: .AppleDouble ._* *~ .DS_Store
ignore_hidden: yes

clutter: [Thumbs.DB, .pls, '*.jpg']

replace:
    '[\\/]': _
    ^\.: ''
    '[\x00-\x1f]': _
    \s+$: ''
    ^\s+: ''
    /: _
asciify_paths: no
art_filename: cover
threaded: yes
original_date: no
per_disc_numbering: yes

paths:
    default: Album/%the{$albumartist} - $album%aunique{} ($year)$lossless_desc/$disc_and_track. $artist - $title
    track_type:mixtape: Mixtape/%the{$artist} - $title
    track_type:instrumental: Album/%the{$albumartist} - $album%aunique{} ($year)$lossless_desc/$disc_and_track. $artist - $title
    singleton: Non-Album/%the{$artist} - $title
    comp: Compilation/%the{$album%aunique{}}/$disc_and_track. %the{$artist} - $title
item_fields:
    disc_and_track: u'%01i-%02i' % (disc, track) if disctotal > 1 else u'%02i' % (track)
    lossless_desc: u' (Lossless)' if format == 'FLAC' or format == 'ALAC' else u''

musicbrainz:
    extra_tags: [year, country, media]

ui:
    color: yes
    colors:
        text_success: green
        text_warning: yellow
        text_error: red
        text_highlight: red
        text_highlight_minor: lightgray
        action_default: turquoise
        action: blue
duplicate_action: merge
embedart:
    ifempty: no
    maxwidth: 0
    auto: yes
    compare_threshold: 0
    remove_art_file: no
    quality: 0
keyfinder:
    auto: no
    bin: /Applications/KeyFinder.app/Contents/MacOS/KeyFinder
    overwrite: no
importadded:
    preserve_mtimes: yes
    preserve_write_mtimes: yes
lastgenre:
    auto: yes
    whitelist: yes
    min_weight: 10
    count: 1
    fallback:
    canonical: no
    source: album
    force: yes
    separator: ', '
    prefer_specific: no
    title_case: yes
replaygain:
    auto: yes
    backend: gstreamer
    overwrite: no
    threads: 8
    parallel_on_import: no
    per_disc: no
    peak: 'true'
    targetlevel: 89
    r128: [Opus]
    r128_targetlevel: 84
scrub:
    auto: yes
zero:
    auto: no
    fields: comments track tracktotal album albumartist albumartist_credit albumartist_sort comp composer encoder lyrics
    comments:
    - EAC
    - LAME
    - from.+collection
    - ripped by
    update_database: yes
    keep_fields: []
chroma:
    auto: yes
convert:
    auto: no
    copy_album_art: yes
    embed: yes
    never_convert_lossy_files: yes
    max_bitrate: 320
    format: alac
    dest:
    pretend: no
    link: no
    hardlink: no
    threads: 8
    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
    tmpdir:
    quiet: no

    paths: {}
    no_convert: ''
    album_art_maxwidth: 0
    delete_originals: no
metasync:
    source: itunes
    itunes:
        library: ~/Music/iTunes Library.xml
play:
    command: /Applications/VLC.app/Contents/MacOS/VLC
    use_folders: no
    relative_to:
    raw: no
    warning_threshold: 100
    bom: no
copyartifacts:
    extensions: .cue .log .jpg
    print_ignored: yes
acousticbrainz:
    auto: yes
    force: no
    tags: []

match:
    strong_rec_thresh: 0.04
    medium_rec_thresh: 0.25
    rec_gap_thresh: 0.25
    max_rec:
        missing_tracks: medium
        unmatched_tracks: medium
    distance_weights:
        source: 2.0
        artist: 3.0
        album: 3.0
        media: 1.0
        mediums: 1.0
        year: 1.0
        country: 0.5
        label: 0.5
        catalognum: 0.5
        albumdisambig: 0.5
        album_id: 5.0
        tracks: 2.0
        missing_tracks: 0.9
        unmatched_tracks: 0.6
        track_title: 3.0
        track_artist: 2.0
        track_index: 1.0
        track_length: 2.0
        track_id: 5.0
    preferred:
        countries: []
        media: []
        original_year: no
    track_length_grace: 10
    track_length_max: 30
discogs:
    source_weight: 0.5
    tokenfile: discogs_token.json
    apikey: REDACTED
    apisecret: REDACTED
    user_token: REDACTED
    separator: ', '
    index_tracks: no
deezer:
    source_weight: 0.5
fuzzy:
    threshold: 0.7
    prefix: '~'
spotify:
    source_weight: 0.5
    regex: []
    artist_field: albumartist
    mode: list
    track_field: title
    album_field: album
    show_failures: no
    tiebreak: popularity
    region_filter:
    client_id: 4e414367a1d14c75a5c5129a627fcab8
    client_secret: REDACTED
    tokenfile: spotify_token.json
beatport:
    source_weight: 0.5
fetchart:
    auto: yes
    minwidth: 0
    sources:
    - filesystem
    - coverart
    - itunes
    - amazon
    - albumart
    google_key: REDACTED
    enforce_ratio: no
    cautious: no
    maxwidth: 0
    store_source: yes
    cover_names:
    - cover
    - front
    - art
    - album
    - folder
    quality: 0
    max_filesize: 0
    google_engine: 001442825323518660753:hrh5ch1gjzm
    fanarttv_key: REDACTED
    lastfm_key: REDACTED
    high_resolution: no
web:
    host: 127.0.0.1
    port: 8337
    cors: ''
    cors_supports_credentials: no
    reverse_proxy: no
    include_paths: no
    readonly: yes
bpmanalyser:
    auto: no
    dry-run: no
    write: yes
    threads: 8
    force: no
    quiet: no
badfiles:
    check_on_import: yes
the:
    the: yes
    a: yes
    format: '{0}, {1}'
    strip: no
    patterns: []
missing:
    count: no
    total: no
    album: no
duplicates:
    album: no
    checksum: ''
    copy: ''
    count: no
    delete: no
    format: ''
    full: no
    keys: []
    merge: no
    move: ''
    path: no
    tiebreak: {}
    strict: no
    tag: ''
pathfields: {}
album_fields: {}
edit:
    albumfields: album albumartist
    itemfields: track title artist album
    ignore_fields: id path
```



Discussion:
Thanks! Looks like a bug.
I can confirm this bug.

Resolving patch:
diff --git a/beetsplug/deezer.py b/beetsplug/deezer.py
--- a/beetsplug/deezer.py
+++ b/beetsplug/deezer.py
@@ -128,9 +128,9 @@ def _get_track(self, track_data):
             artist=artist,
             artist_id=artist_id,
             length=track_data['duration'],
-            index=track_data['track_position'],
-            medium=track_data['disk_number'],
-            medium_index=track_data['track_position'],
+            index=track_data.get('track_position'),
+            medium=track_data.get('disk_number'),
+            medium_index=track_data.get('track_position'),
             data_source=self.data_source,
             data_url=track_data['link'],
         )


Test patch: