From c53e5aab7db9c30ec1e8d5943b4b99fcedf7aed5 Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Sat, 8 Nov 2025 14:03:09 -0500 Subject: [PATCH 1/7] Add Media field for album and test case --- beets/config_default.yaml | 1 - beets/library/models.py | 3 +++ test/test_media_field.py | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/test_media_field.py diff --git a/beets/config_default.yaml b/beets/config_default.yaml index c0bab8056..b0b495a22 100644 --- a/beets/config_default.yaml +++ b/beets/config_default.yaml @@ -7,7 +7,6 @@ statefile: state.pickle # --------------- Plugins --------------- plugins: [musicbrainz] - pluginpath: [] # --------------- Import --------------- diff --git a/beets/library/models.py b/beets/library/models.py index cbee2a411..f3cab6ae9 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -265,6 +265,7 @@ class Album(LibModel): "language": types.STRING, "country": types.STRING, "albumstatus": types.STRING, + "media": types.STRING, "albumdisambig": types.STRING, "releasegroupdisambig": types.STRING, "rg_album_gain": types.NULL_FLOAT, @@ -320,6 +321,7 @@ class Album(LibModel): "language", "country", "albumstatus", + "media", "albumdisambig", "releasegroupdisambig", "release_group_title", @@ -361,6 +363,7 @@ class Album(LibModel): getters = plugins.album_field_getters() getters["path"] = Album.item_dir getters["albumtotal"] = Album._albumtotal + return getters def items(self): diff --git a/test/test_media_field.py b/test/test_media_field.py new file mode 100644 index 000000000..3d405cc31 --- /dev/null +++ b/test/test_media_field.py @@ -0,0 +1,11 @@ +from beets.library import Item +from beets import library + +def test_album_media_field(tmp_path): + lib = library.Library(path=str(tmp_path / "library.db"), + directory=str(tmp_path / "music")) + + item = Item(title="Test Song", album="Test Album", media="Vinyl") + album = lib.add_album([item]) + + assert album.media == "Vinyl" \ No newline at end of file From 9f349b7498271764fd8ce7969bb66324fe8c8072 Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Sat, 8 Nov 2025 14:07:48 -0500 Subject: [PATCH 2/7] Add Media field for album and test case --- beets/library/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/beets/library/models.py b/beets/library/models.py index f3cab6ae9..84368f197 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -363,7 +363,6 @@ class Album(LibModel): getters = plugins.album_field_getters() getters["path"] = Album.item_dir getters["albumtotal"] = Album._albumtotal - return getters def items(self): From 96633716595000965a8759df8848c83245d7eec4 Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Sat, 8 Nov 2025 14:16:54 -0500 Subject: [PATCH 3/7] Adding Documentation --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5ebf3f53e..08985fd72 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,6 +21,7 @@ New features: - :doc:`plugins/mbpseudo`: Add a new `mbpseudo` plugin to proactively receive MusicBrainz pseudo-releases as recommendations during import. - Added support for Python 3.13. +-- Added album-level `$media` field derived from items’ media metadata. Bug fixes: From faa702fe755f3f6ee2ccaa59ef363426ff4560b4 Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Sat, 8 Nov 2025 14:25:02 -0500 Subject: [PATCH 4/7] Fixed Style --- docs/changelog.rst | 2 +- test/test_media_field.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 08985fd72..4345f0b39 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -21,7 +21,7 @@ New features: - :doc:`plugins/mbpseudo`: Add a new `mbpseudo` plugin to proactively receive MusicBrainz pseudo-releases as recommendations during import. - Added support for Python 3.13. --- Added album-level `$media` field derived from items’ media metadata. +- Added album-level `$media` field derived from items’ media metadata. Bug fixes: diff --git a/test/test_media_field.py b/test/test_media_field.py index 3d405cc31..91dd89d5a 100644 --- a/test/test_media_field.py +++ b/test/test_media_field.py @@ -1,11 +1,13 @@ -from beets.library import Item from beets import library +from beets.library import Item + def test_album_media_field(tmp_path): - lib = library.Library(path=str(tmp_path / "library.db"), - directory=str(tmp_path / "music")) + lib = library.Library( + path=str(tmp_path / "library.db"), directory=str(tmp_path / "music") + ) item = Item(title="Test Song", album="Test Album", media="Vinyl") - album = lib.add_album([item]) + album = lib.add_album([item]) - assert album.media == "Vinyl" \ No newline at end of file + assert album.media == "Vinyl" From 06850d872b11d4cfb08a5a5d77fc569e8544ef9b Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Sat, 22 Nov 2025 16:48:21 -0500 Subject: [PATCH 5/7] Media type as list --- beets/library/models.py | 12 +++++++++ test/test_media_field.py | 53 +++++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/beets/library/models.py b/beets/library/models.py index 84368f197..971a2b517 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -356,6 +356,17 @@ class Album(LibModel): """The path to album's cover picture as pathlib.Path.""" return Path(os.fsdecode(self.artpath)) if self.artpath else None + @property + def media(self): + """Return a list of distinct media types for the items in this album.""" + if not self.items(): + return [] + media_set = { + str(item.media) + for item in self.items() + if getattr(item, "media", None)} + return list(media_set) + @classmethod def _getters(cls): # In addition to plugin-provided computed fields, also expose @@ -363,6 +374,7 @@ class Album(LibModel): getters = plugins.album_field_getters() getters["path"] = Album.item_dir getters["albumtotal"] = Album._albumtotal + getters["media_types"] = lambda a: a.media_types return getters def items(self): diff --git a/test/test_media_field.py b/test/test_media_field.py index 91dd89d5a..aac6ae2c6 100644 --- a/test/test_media_field.py +++ b/test/test_media_field.py @@ -1,13 +1,48 @@ -from beets import library -from beets.library import Item +import unittest + +from beets.library import Item, Library -def test_album_media_field(tmp_path): - lib = library.Library( - path=str(tmp_path / "library.db"), directory=str(tmp_path / "music") - ) +class MediaFieldTest(unittest.TestCase): + def setUp(self): + self.lib = Library(':memory:') + self.lib.add_album = self.lib.add_album - item = Item(title="Test Song", album="Test Album", media="Vinyl") - album = lib.add_album([item]) + def add_album_with_items(self, items_data): + items = [] + for data in items_data: + item = Item(**data) + items.append(item) + album = self.lib.add_album(items) + return album - assert album.media == "Vinyl" + def test_album_media_field_multiple_types(self): + items_data = [ + {"title": "Track 1", "artist": "Artist A", "media": "CD"}, + {"title": "Track 2", "artist": "Artist A", "media": "Vinyl"}, + ] + album = self.add_album_with_items(items_data) + media = album.media + assert media == ["CD", "Vinyl"] + + def test_album_media_field_single_type(self): + items_data = [ + {"title": "Track 1", "artist": "Artist A", "media": "CD"}, + {"title": "Track 2", "artist": "Artist A", "media": "CD"}, + ] + album = self.add_album_with_items(items_data) + media = album.media + assert media == ["CD"] + + def test_album_with_no_media(self): + items_data = [ + {"title": "Track 1", "artist": "Artist A"}, + {"title": "Track 2", "artist": "Artist A"}, + ] + album = self.add_album_with_items(items_data) + media = album.media + assert media == [] + + +if __name__ == "__main__": + unittest.main() From 8fc30163c8191ce9edbee1f78c3ecfece68950c7 Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Sat, 22 Nov 2025 17:01:02 -0500 Subject: [PATCH 6/7] Style changes --- beets/library/models.py | 3 ++- test/test_media_field.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/beets/library/models.py b/beets/library/models.py index 971a2b517..69daeade3 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -364,7 +364,8 @@ class Album(LibModel): media_set = { str(item.media) for item in self.items() - if getattr(item, "media", None)} + if getattr(item, "media", None) + } return list(media_set) @classmethod diff --git a/test/test_media_field.py b/test/test_media_field.py index aac6ae2c6..e2410c6ad 100644 --- a/test/test_media_field.py +++ b/test/test_media_field.py @@ -5,7 +5,7 @@ from beets.library import Item, Library class MediaFieldTest(unittest.TestCase): def setUp(self): - self.lib = Library(':memory:') + self.lib = Library(":memory:") self.lib.add_album = self.lib.add_album def add_album_with_items(self, items_data): @@ -23,7 +23,7 @@ class MediaFieldTest(unittest.TestCase): ] album = self.add_album_with_items(items_data) media = album.media - assert media == ["CD", "Vinyl"] + assert sorted(media) == ["CD", "Vinyl"] def test_album_media_field_single_type(self): items_data = [ From a8818071e40b742cf960f6c5cb6195cf1574f3c2 Mon Sep 17 00:00:00 2001 From: Grace Coppola Date: Wed, 3 Dec 2025 11:04:22 -0500 Subject: [PATCH 7/7] Remove duplicate feature artist --- beets/library/models.py | 14 ------- beetsplug/discogs.py | 19 +++++++-- test/plugins/test_discogs.py | 24 +++++++++++ test/test_media_field.py | 78 ++++++++++++++++++------------------ 4 files changed, 78 insertions(+), 57 deletions(-) diff --git a/beets/library/models.py b/beets/library/models.py index 69daeade3..95da619e3 100644 --- a/beets/library/models.py +++ b/beets/library/models.py @@ -265,7 +265,6 @@ class Album(LibModel): "language": types.STRING, "country": types.STRING, "albumstatus": types.STRING, - "media": types.STRING, "albumdisambig": types.STRING, "releasegroupdisambig": types.STRING, "rg_album_gain": types.NULL_FLOAT, @@ -321,7 +320,6 @@ class Album(LibModel): "language", "country", "albumstatus", - "media", "albumdisambig", "releasegroupdisambig", "release_group_title", @@ -356,18 +354,6 @@ class Album(LibModel): """The path to album's cover picture as pathlib.Path.""" return Path(os.fsdecode(self.artpath)) if self.artpath else None - @property - def media(self): - """Return a list of distinct media types for the items in this album.""" - if not self.items(): - return [] - media_set = { - str(item.media) - for item in self.items() - if getattr(item, "media", None) - } - return list(media_set) - @classmethod def _getters(cls): # In addition to plugin-provided computed fields, also expose diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index 29600a676..feaba4660 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -780,10 +780,21 @@ class DiscogsPlugin(MetadataSourcePlugin): featured_list, self.config["anv"]["artist_credit"] ) if featured: - artist += f" {self.config['featured_string']} {featured}" - artist_credit += ( - f" {self.config['featured_string']} {featured_credit}" - ) + featured_string = self.config["featured_string"].as_str() + token = f"{featured_string} {featured}".lower() + token_credit = f"{featured_string} {featured_credit}".lower() + + # Only append if this featured artist isn't already present + if token not in artist.lower(): + artist += f" {featured_string} {featured}" + + if token_credit not in artist_credit.lower(): + artist_credit += f" {featured_string} {featured_credit}" + # Previous code + # artist += f" {self.config['featured_string']} {featured}" + # artist_credit += ( + # f" {self.config['featured_string']} {featured_credit}" + # ) return IntermediateTrackInfo( title=title, track_id=track_id, diff --git a/test/plugins/test_discogs.py b/test/plugins/test_discogs.py index eb65bc588..2cf5e924d 100644 --- a/test/plugins/test_discogs.py +++ b/test/plugins/test_discogs.py @@ -601,6 +601,30 @@ def test_anv_album_artist(): }, "NEW ARTIST, VOCALIST Feat. SOLOIST, PERFORMER, MUSICIAN", ), + ( + { + "type_": "track", + "title": "Infinite Regression", + "position": "6", + "duration": "5:00", + "artists": [ + { + "name": "Filteria Feat. Ukiro", + "tracks": "", + "id": 11146, + "join": "", + } + ], + "extraartists": [ + { + "name": "Ukiro", + "id": 3, + "role": "Featuring", + }, + ], + }, + "Filteria Feat. Ukiro", + ), ], ) @patch("beetsplug.discogs.DiscogsPlugin.setup", Mock()) diff --git a/test/test_media_field.py b/test/test_media_field.py index e2410c6ad..8c2f1d846 100644 --- a/test/test_media_field.py +++ b/test/test_media_field.py @@ -1,48 +1,48 @@ -import unittest +# import unittest -from beets.library import Item, Library +# from beets.library import Item, Library -class MediaFieldTest(unittest.TestCase): - def setUp(self): - self.lib = Library(":memory:") - self.lib.add_album = self.lib.add_album +# class MediaFieldTest(unittest.TestCase): +# def setUp(self): +# self.lib = Library(":memory:") +# self.lib.add_album = self.lib.add_album - def add_album_with_items(self, items_data): - items = [] - for data in items_data: - item = Item(**data) - items.append(item) - album = self.lib.add_album(items) - return album +# def add_album_with_items(self, items_data): +# items = [] +# for data in items_data: +# item = Item(**data) +# items.append(item) +# album = self.lib.add_album(items) +# return album - def test_album_media_field_multiple_types(self): - items_data = [ - {"title": "Track 1", "artist": "Artist A", "media": "CD"}, - {"title": "Track 2", "artist": "Artist A", "media": "Vinyl"}, - ] - album = self.add_album_with_items(items_data) - media = album.media - assert sorted(media) == ["CD", "Vinyl"] +# def test_album_media_field_multiple_types(self): +# items_data = [ +# {"title": "Track 1", "artist": "Artist A", "media": "CD"}, +# {"title": "Track 2", "artist": "Artist A", "media": "Vinyl"}, +# ] +# album = self.add_album_with_items(items_data) +# media = album.media +# assert sorted(media) == ["CD", "Vinyl"] - def test_album_media_field_single_type(self): - items_data = [ - {"title": "Track 1", "artist": "Artist A", "media": "CD"}, - {"title": "Track 2", "artist": "Artist A", "media": "CD"}, - ] - album = self.add_album_with_items(items_data) - media = album.media - assert media == ["CD"] +# def test_album_media_field_single_type(self): +# items_data = [ +# {"title": "Track 1", "artist": "Artist A", "media": "CD"}, +# {"title": "Track 2", "artist": "Artist A", "media": "CD"}, +# ] +# album = self.add_album_with_items(items_data) +# media = album.media +# assert media == ["CD"] - def test_album_with_no_media(self): - items_data = [ - {"title": "Track 1", "artist": "Artist A"}, - {"title": "Track 2", "artist": "Artist A"}, - ] - album = self.add_album_with_items(items_data) - media = album.media - assert media == [] +# def test_album_with_no_media(self): +# items_data = [ +# {"title": "Track 1", "artist": "Artist A"}, +# {"title": "Track 2", "artist": "Artist A"}, +# ] +# album = self.add_album_with_items(items_data) +# media = album.media +# assert media == [] -if __name__ == "__main__": - unittest.main() +# if __name__ == "__main__": +# unittest.main()