diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 3fa80c6f35..252807a051 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -100,6 +100,7 @@ def __init__( country: str | None = None, style: str | None = None, genre: str | None = None, + genres: str | None = None, albumstatus: str | None = None, media: str | None = None, albumdisambig: str | None = None, @@ -143,6 +144,7 @@ def __init__( self.country = country self.style = style self.genre = genre + self.genres = genres self.albumstatus = albumstatus self.media = media self.albumdisambig = albumdisambig @@ -212,6 +214,7 @@ def __init__( bpm: str | None = None, initial_key: str | None = None, genre: str | None = None, + genres: str | None = None, album: str | None = None, **kwargs, ): @@ -246,6 +249,7 @@ def __init__( self.bpm = bpm self.initial_key = initial_key self.genre = genre + self.genres = genres self.album = album self.update(kwargs) diff --git a/beets/library.py b/beets/library.py index d4ec63200d..321446c4e0 100644 --- a/beets/library.py +++ b/beets/library.py @@ -534,6 +534,7 @@ class Item(LibModel): "albumartist_credit": types.STRING, "albumartists_credit": types.MULTI_VALUE_DSV, "genre": types.STRING, + "genres": types.MULTI_VALUE_DSV, "style": types.STRING, "discogs_albumid": types.INTEGER, "discogs_artistid": types.INTEGER, @@ -1182,6 +1183,7 @@ class Album(LibModel): "albumartists_credit": types.MULTI_VALUE_DSV, "album": types.STRING, "genre": types.STRING, + "genres": types.MULTI_VALUE_DSV, "style": types.STRING, "discogs_albumid": types.INTEGER, "discogs_artistid": types.INTEGER, @@ -1238,6 +1240,7 @@ class Album(LibModel): "albumartists_credit", "album", "genre", + "genres", "style", "discogs_albumid", "discogs_artistid", diff --git a/docs/changelog.rst b/docs/changelog.rst index 46fa3b64e1..ce4e50e44e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -118,6 +118,8 @@ New features: * Beets now uses ``platformdirs`` to determine the default music directory. This location varies between systems -- for example, users can configure it on Unix systems via ``user-dirs.dirs(5)``. +* New multi-valued ``genres`` tag. This change brings up the ``genres`` tag to the same state as the ``*artists*`` multi-valued tags (see :bug:`4743` for details). + :bug:`5426` Bug fixes: diff --git a/test/test_library.py b/test/test_library.py index b5e6d4eebc..6de71d9ada 100644 --- a/test/test_library.py +++ b/test/test_library.py @@ -710,13 +710,13 @@ def test_if_def_false_complete(self): self._assert_dest(b"/base/not_played") def test_first(self): - self.i.genres = "Pop; Rock; Classical Crossover" - self._setf("%first{$genres}") + self.i.semicolon_delimited_field = "Pop; Rock; Classical Crossover" + self._setf("%first{$semicolon_delimited_field}") self._assert_dest(b"/base/Pop") def test_first_skip(self): - self.i.genres = "Pop; Rock; Classical Crossover" - self._setf("%first{$genres,1,2}") + self.i.semicolon_delimited_field = "Pop; Rock; Classical Crossover" + self._setf("%first{$semicolon_delimited_field,1,2}") self._assert_dest(b"/base/Classical Crossover") def test_first_different_sep(self): @@ -1308,6 +1308,28 @@ def test_write_date_field(self): item.write() assert MediaFile(syspath(item.path)).year == clean_year + def test_write_multi_genres(self): + item = self.add_item_fixture(genre="old genre") + item.write( + tags={"genres": ["g1", "g2"]}, + ) + + # Ensure it reads all genres + assert MediaFile(syspath(item.path)).genres == ["g1", "g2"] + + # Ensure reading single genre outputs the first of the genres + assert MediaFile(syspath(item.path)).genre == "g1" + + def test_write_multi_genres_both_single_and_multi(self): + item = self.add_item_fixture(genre="old genre 1") + item.write( + tags={"genre": "single genre", "genres": ["multi genre"]}, + ) + + # Ensure the multi takes precedence + assert MediaFile(syspath(item.path)).genre == "multi genre" + assert MediaFile(syspath(item.path)).genres == ["multi genre"] + class ItemReadTest(unittest.TestCase): def test_unreadable_raise_read_error(self):