Setting album_id during import results in corrupted beets db
- 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
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