- Use the INI format (since YAML and regex is a nightmare...)
- Ship a default aliases file with beets
- Dont use non-capturing groups use ?: because colons are a problem.
Also some replacement patterns were bad. Fixed
- Implement loading the aliases.ini file similar to how the whitelist
file is handled.
- Apply aliases in two places, right after last.fm fetch and after
collecting existing genres.
My usecase needs the `convert` to not write tags so I can write my own
APEv2 tags. This PR adds a write_metadata option to disable the
`convert` plugin writing metadata to the converted files.
Fixes#5366 .
Adds removal of disambiguation from label names, in addition this PR
moves the Discogs disambiguation function out of the
MetadataSourcePlugin, and puts it in the Discogs plugin, keeping the
parent class more generic.
A config option has been added to allow disabling Discogs disambiguation
removal. Tests and docs are written for the feature, and shows no side
effects in other plugins that rely on the MetadataSourcePlugin.
I have different expectations around this [FtInTitle test
case](fcc9341360/test/plugins/test_ftintitle.py (L147-L151)),
which represents a song on an album by a **guest artist featuring a
third artist**:
```
{
"artist": "Alice ft. Carol",
"album_artist": "Bob",
"feat_part": None,
},
```
If this were Alice's album, the plugin would process the track, moving
`Carol` to the `feat_part` and changing the `artist` to `Alice`. But
because it's Bob's album, nothing happens. I don't want `Alice ft.
Carol` as an artist on Bob's album any more than I want it on Alice's
though 😭
This may be a bit of a corner case but it does happen reasonably often
in the wild:
```
Album: Flying Lotus - ASH (OST) (2025)
Track 26: Kuedo feat. Miguel Atwood-Ferguson - WHAT'S WRONG PEACH?
```
More commonly, this case applies to __compilation albums which use
`Various Artists` as an albumartist__.
Processing doesn't occur on these tracks in the current implementation
because of how
[`find_feat_part()`](fcc9341360/beetsplug/ftintitle.py (L57-L82))
kicks off:
```
# Look for the album artist in the artist field. If it's not present, give up.
albumartist_split = artist.split(albumartist, 1)
if len(albumartist_split) <= 1:
return None
```
As best I can tell, the code is setup this way to enable parsing of more
complex cases , like a song by `Hall & Oates` on an Oates album, which
is pretty clever. But giving up in cases where albumartist isn't in the
artist field seems premature when a reasonable parsing method exists
that doesn't require it.
So this PR proposes modifications to code and tests that enables
FtInTitle to process items whose artist doesn't contain the albumartist.
It seems like what someone loading a `FtInTitle` plugin would want by
default, but if that's a contentious take then I'm happy to put it
behind a config flag.
Thanks for your consideration!
Introduce a `--pretend` option to the lastgenre plugin, allowing users
to preview genre changes without making any modifications to their
library. This feature enhances user control by showing potential changes
before they are applied.
A bit niche but I tried setting my bareasc prefix to an empty string,
and was getting an obtuse error. This should help make clearer what is
happening when queries fail.
The exception is not properly raised up the stack in the first place
because it happens across 2 FFI boundaries: the DB query
(Python -> SQLite), and the custom DB function (SQLite -> Python).
Thus Python cannot forwarded it back to itself through SQLite, and it's
treated as an "unraisable" exception.
We could override `sys.unraisablehook` to not print anything for the
original exception, and store it in a global for the outer Python
interpreter to fetch and raise properly, but that's pretty hacky,
limited to a single DB instance and query at once, and risks swallowing
other "unraisable" exceptions.
Instead we just tell the user to look above for what Python prints.
Sample output:
```
Exception ignored in: <function unidecode_expect_ascii at 0x7f7fa20bb060>
Traceback (most recent call last):
File "site-packages/unidecode/__init__.py", line 60, in unidecode_expect_ascii
bytestring = string.encode('ASCII')
^^^^^^^^^^^^^
AttributeError: 'bytes' object has no attribute 'encode'
Traceback (most recent call last):
File "site-packages/beets/dbcore/db.py", line 988, in query
cursor = self.db._connection().execute(statement, subvals)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.OperationalError: user-defined function raised exception
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "site-packages/beets/__main__.py", line 9, in <module>
sys.exit(main())
^^^^^^
File "site-packages/beets/ui/__init__.py", line 1865, in main
_raw_main(args)
File "site-packages/beets/ui/__init__.py", line 1852, in _raw_main
subcommand.func(lib, suboptions, subargs)
File "site-packages/beets/ui/commands.py", line 1599, in list_func
list_items(lib, decargs(args), opts.album)
File "site-packages/beets/ui/commands.py", line 1594, in list_items
for item in lib.items(query):
^^^^^^^^^^^^^^^^
File "site-packages/beets/library.py", line 1695, in items
return self._fetch(Item, query, sort or self.get_default_item_sort())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/beets/library.py", line 1673, in _fetch
return super()._fetch(model_cls, query, sort)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/beets/dbcore/db.py", line 1301, in _fetch
rows = tx.query(sql, subvals)
^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/beets/dbcore/db.py", line 991, in query
raise DBCustomFunctionError()
beets.dbcore.db.DBCustomFunctionError: beets defined custom SQLite function failed; see the other errors above for details
```
A bit niche but I tried setting my bareasc prefix to an empty string,
and was getting an obtuse error. This should help make clearer what is
happening when queries fail.
The exception is not properly raised up the stack in the first place
because it happens across 2 FFI boundaries: the DB query
(Python -> SQLite), and the custom DB function (SQLite -> Python).
Thus Python cannot forwarded it back to itself through SQLite, and it's
treated as an "unraisable" exception.
We could override `sys.unraisablehook` to not print anything for the
original exception, and store it in a global for the outer Python
interpreter to fetch and raise properly, but that's pretty hacky,
limited to a single DB instance and query at once, and risks swallowing
other "unraisable" exceptions.
Instead we just tell the user to look above for what Python prints.
Sample output:
```
Exception ignored in: <function unidecode_expect_ascii at
0x7f7fa20bb060>
Traceback (most recent call last):
File "site-packages/unidecode/__init__.py", line 60, in
unidecode_expect_ascii
bytestring = string.encode('ASCII')
^^^^^^^^^^^^^
AttributeError: 'bytes' object has no attribute 'encode'
Traceback (most recent call last):
File "site-packages/beets/dbcore/db.py", line 988, in query
cursor = self.db._connection().execute(statement, subvals)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
sqlite3.OperationalError: user-defined function raised exception
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "site-packages/beets/__main__.py", line 9, in <module>
sys.exit(main())
^^^^^^
File "site-packages/beets/ui/__init__.py", line 1865, in main
_raw_main(args)
File "site-packages/beets/ui/__init__.py", line 1852, in _raw_main
subcommand.func(lib, suboptions, subargs)
File "site-packages/beets/ui/commands.py", line 1599, in list_func
list_items(lib, decargs(args), opts.album)
File "site-packages/beets/ui/commands.py", line 1594, in list_items
for item in lib.items(query):
^^^^^^^^^^^^^^^^
File "site-packages/beets/library.py", line 1695, in items
return self._fetch(Item, query, sort or
self.get_default_item_sort())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/beets/library.py", line 1673, in _fetch
return super()._fetch(model_cls, query, sort)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/beets/dbcore/db.py", line 1301, in _fetch
rows = tx.query(sql, subvals)
^^^^^^^^^^^^^^^^^^^^^^
File "site-packages/beets/dbcore/db.py", line 991, in query
raise DBCustomFunctionError()
beets.dbcore.db.DBCustomFunctionError: beets defined SQLite function
failed; see the other errors above for details
```
Only include non-empty artist and album filters in Spotify track
search and metadata candidate queries to prevent incorrect matches when
the album field is missing or empty.
closes#5189