beetbox / beetbox/beets

Setting album_id during import results in corrupted beets db

Open
#5,546 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
15.7k
Forks
2.1k
Avg merge
4d 21h
Merged PRs (30d)
31

Description

Using the `beet import --set` option to set `album_id` results in corrupted beets db due to "orphaned" album record.

Cross references:
* I discovered this while trying to find a workaround to feature request #5012.
* Before I suspected that this was a bug, I started discussion #5525 on this topic.

### Problem

Here is a reproducible toy example:

### Create two dummy mp3 files and add minimal id3 tags
```
$ ffmpeg -f lavfi -i anullsrc=r=44100:cl=mono -t 5 -q:a 9 -acodec libmp3lame foo.mp3
$ cp foo.mp3 bar.mp3
$ id3tag -s foo -a foobar -A foobar -t 1 foo.mp3
$ id3tag -s bar -a foobar -A foobar -t 2 bar.mp3
```

### Start from a fresh, minimal beet 2.1.0 config:
```
$ rm ~/.config/beets/{state.pickle,library.db}
$ cat ~/.config/beets/config.yaml
directory: /home/odoublewen/mp3
import:
copy: yes
write: yes
log: beetslog.txt
paths:
default: $albumartist/$album/$track $title
$ beet --version
beets version 2.1.0
Python version 3.9.2
no plugins loaded
```

### Import the dummy mp3 files

* Import the files one at a time.
* The second file is imported using `--set` to assign the album_id of the second mp3 file.

As shown in the `beet ls` command, it _seemed_ to do the right thing:
```
$ beet import -AW foo.mp3
$ beet import -AW --set album_id=1 bar.mp3
$ beet ls -f 'album_id=$album_id $album $path'
album_id=1 foobar /home/odoublewen/mp3/foobar/foobar/01 foo.mp3
album_id=1 foobar /home/odoublewen/mp3/foobar/foobar/02 bar.mp3
```

But look at the weird results from `beet ls -a`:
```
$ beet ls -a -f '$album_id $album'
$album_id foobar
1 foobar
```

And beets raises a `ValueError` when I try `beet ls -a -p`:
```
$ beet ls -a -p
/home/odoublewen/mp3/foobar/foobar
Traceback (most recent call last):
File "/home/odoublewen/beets2/lib/python3.9/site-packages/beets/util/functemplate.py", line 557, in substitute
res = self.compiled(values, functions)
File "/home/odoublewen/beets2/lib/python3.9/site-packages/beets/util/functemplate.py", line 581, in wrapper_func
args[VARIABLE_PREFIX + varname] = values[varname]
File "/home/odoublewen/beets2/lib/python3.9/site-packages/beets/dbcore/db.py", line 108, in __getitem__
return self._get_formatted(self.model, key)
File "/home/odoublewen/beets2/lib/python3.9/site-packages/beets/dbcore/db.py", line 131, in _get_formatted
value = model._type(key).format(model.get(key))
File "/home/odoublewen/beets2/lib/python3.9/site-packages/beets/dbcore/db.py", line 456, in _get
return getters[key](self)
File "/home/odoublewen/beets2/lib/python3.9/site-packages/beets/library.py", line 1409, in item_dir
raise ValueError("empty album for album id %d" % self.id)
ValueError: empty album for album id 2
```

### Examine beets database

Notice that beets created an album with id=2, but set the album_id attribute to 1. Maybe this is problematic because the album with album_id=2 is "orphaned" -- it has no items in it. I had hoped, or assumed, that using `--set` to set the album_id to an existing album would cause beet to **not** create a new album_id.

```text
$ sqlite3 .config/beets/library.db
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
sqlite> .headers on

sqlite> select id, album from albums;
id|album
1|foobar
2|foobar

sqlite> select * from album_attributes;
id|entity_id|key|value
1|2|album_id|1
```

But I also notice that beets **did** assign the `bar.mp3` file to album_id=1 -- so that part seems good.

```text
sqlite> select id, album_id, path from items;
id|album_id|path
1|1|/home/odoublewen/mp3/foobar/foobar/01 foo.mp3
2|1|/home/odoublewen/mp3/foobar/foobar/02 bar.mp3
```

### Setup

* OS: debian linux
* Python version: 3.9.2
* beets version: 2.1.0
* Turning off plugins made problem go away (yes/no): no

My configuration (output of `beet config`) is:

```yaml
directory: /home/odoublewen/mp3
import:
copy: yes
write: yes
log: beetslog.txt
paths:
default: $albumartist/$album/$track $title
```

Contributor guide

Open the contributing guide

Research direction

Reproduce the import sequence with `beet import --set` and inspect the database results. Start with `beets/dbcore/db.py`, `beets/library.py`, and `beets/util/functemplate.py`, which appear in the traceback; done means importing into an existing album leaves no orphan album and `beet ls -a` and `beet ls -a -p` complete without errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, sqlite
Domain
cli, database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.