mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
Merge 0317ae1b61 into 2bd77b9895
This commit is contained in:
commit
b6f1b22c9c
3 changed files with 92 additions and 131 deletions
|
|
@ -732,6 +732,28 @@ class DiscogsPlugin(MetadataSourcePlugin):
|
|||
return text
|
||||
return DISAMBIGUATION_RE.sub("", text)
|
||||
|
||||
def _normalize_featured_name(self, name: str) -> str:
|
||||
"""Normalize a featured artist name for comparison."""
|
||||
# Reuse disambiguation stripping so "Artist (5)" and "Artist" match.
|
||||
return self.strip_disambiguation(name).strip().lower()
|
||||
|
||||
def _existing_featured_artists(self, artist: str) -> set[str]:
|
||||
"""Extract already-present featured artist names from an artist string.
|
||||
|
||||
For example:
|
||||
"Filteria Feat. Ukiro, Someone Else"
|
||||
-> {"ukiro", "someone else"}
|
||||
"""
|
||||
feat_str = self.config["featured_string"].as_str()
|
||||
if feat_str not in artist:
|
||||
return set()
|
||||
|
||||
# Split once: "Filteria Feat. Ukiro, Someone" ->
|
||||
# ["Filteria ", " Ukiro, Someone"]
|
||||
_, after_feat = artist.split(feat_str, 1)
|
||||
raw_names = [n.strip() for n in after_feat.split(",")]
|
||||
return {self._normalize_featured_name(n) for n in raw_names if n}
|
||||
|
||||
def get_track_info(
|
||||
self,
|
||||
track: Track,
|
||||
|
|
@ -780,10 +802,29 @@ 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}"
|
||||
)
|
||||
feat_str = self.config["featured_string"].as_str()
|
||||
|
||||
# What featured artists are *already* present in the string?
|
||||
existing = self._existing_featured_artists(artist)
|
||||
|
||||
# What are we trying to add now?
|
||||
new = {
|
||||
self._normalize_featured_name(n)
|
||||
for n in featured.split(",")
|
||||
if n.strip()
|
||||
}
|
||||
|
||||
# Only append if we'd actually introduce *new* featured names.
|
||||
# This avoids "Filteria Feat. Ukiro Feat. Ukiro" and also
|
||||
# fixes the ABCD/D example (ABCD feat. D + D again).
|
||||
if not new.issubset(existing):
|
||||
artist += f" {feat_str} {featured}"
|
||||
artist_credit += f" {feat_str} {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,
|
||||
|
|
|
|||
|
|
@ -601,6 +601,53 @@ 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",
|
||||
),
|
||||
(
|
||||
{
|
||||
"type_": "track",
|
||||
"title": "track",
|
||||
"position": "1",
|
||||
"duration": "5:00",
|
||||
"artists": [
|
||||
{
|
||||
"name": "ABCD",
|
||||
"tracks": "",
|
||||
"id": 11146,
|
||||
}
|
||||
],
|
||||
"extraartists": [
|
||||
{
|
||||
"name": "D",
|
||||
"id": 3,
|
||||
"role": "Featuring",
|
||||
}
|
||||
],
|
||||
},
|
||||
"ABCD Feat. D",
|
||||
),
|
||||
],
|
||||
)
|
||||
@patch("beetsplug.discogs.DiscogsPlugin.setup", Mock())
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2016, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Tests the facility that lets plugins add custom field to MediaFile."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import mediafile
|
||||
import pytest
|
||||
|
||||
from beets.library import Item
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets.test import _common
|
||||
from beets.test.helper import BeetsTestCase
|
||||
from beets.util import bytestring_path, syspath
|
||||
|
||||
field_extension = mediafile.MediaField(
|
||||
mediafile.MP3DescStorageStyle("customtag"),
|
||||
mediafile.MP4StorageStyle("----:com.apple.iTunes:customtag"),
|
||||
mediafile.StorageStyle("customtag"),
|
||||
mediafile.ASFStorageStyle("customtag"),
|
||||
)
|
||||
|
||||
list_field_extension = mediafile.ListMediaField(
|
||||
mediafile.MP3ListDescStorageStyle("customlisttag"),
|
||||
mediafile.MP4ListStorageStyle("----:com.apple.iTunes:customlisttag"),
|
||||
mediafile.ListStorageStyle("customlisttag"),
|
||||
mediafile.ASFStorageStyle("customlisttag"),
|
||||
)
|
||||
|
||||
|
||||
class ExtendedFieldTestMixin(BeetsTestCase):
|
||||
def _mediafile_fixture(self, name, extension="mp3"):
|
||||
name = bytestring_path(f"{name}.{extension}")
|
||||
src = os.path.join(_common.RSRC, name)
|
||||
target = os.path.join(self.temp_dir, name)
|
||||
shutil.copy(syspath(src), syspath(target))
|
||||
return mediafile.MediaFile(target)
|
||||
|
||||
def test_extended_field_write(self):
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field("customtag", field_extension)
|
||||
|
||||
try:
|
||||
mf = self._mediafile_fixture("empty")
|
||||
mf.customtag = "F#"
|
||||
mf.save()
|
||||
|
||||
mf = mediafile.MediaFile(mf.path)
|
||||
assert mf.customtag == "F#"
|
||||
|
||||
finally:
|
||||
delattr(mediafile.MediaFile, "customtag")
|
||||
Item._media_fields.remove("customtag")
|
||||
|
||||
def test_extended_list_field_write(self):
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field("customlisttag", list_field_extension)
|
||||
|
||||
try:
|
||||
mf = self._mediafile_fixture("empty")
|
||||
mf.customlisttag = ["a", "b"]
|
||||
mf.save()
|
||||
|
||||
mf = mediafile.MediaFile(mf.path)
|
||||
assert mf.customlisttag == ["a", "b"]
|
||||
|
||||
finally:
|
||||
delattr(mediafile.MediaFile, "customlisttag")
|
||||
Item._media_fields.remove("customlisttag")
|
||||
|
||||
def test_write_extended_tag_from_item(self):
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field("customtag", field_extension)
|
||||
|
||||
try:
|
||||
mf = self._mediafile_fixture("empty")
|
||||
assert mf.customtag is None
|
||||
|
||||
item = Item(path=mf.path, customtag="Gb")
|
||||
item.write()
|
||||
mf = mediafile.MediaFile(mf.path)
|
||||
assert mf.customtag == "Gb"
|
||||
|
||||
finally:
|
||||
delattr(mediafile.MediaFile, "customtag")
|
||||
Item._media_fields.remove("customtag")
|
||||
|
||||
def test_read_flexible_attribute_from_file(self):
|
||||
plugin = BeetsPlugin()
|
||||
plugin.add_media_field("customtag", field_extension)
|
||||
|
||||
try:
|
||||
mf = self._mediafile_fixture("empty")
|
||||
mf.update({"customtag": "F#"})
|
||||
mf.save()
|
||||
|
||||
item = Item.from_path(mf.path)
|
||||
assert item["customtag"] == "F#"
|
||||
|
||||
finally:
|
||||
delattr(mediafile.MediaFile, "customtag")
|
||||
Item._media_fields.remove("customtag")
|
||||
|
||||
def test_invalid_descriptor(self):
|
||||
with pytest.raises(
|
||||
ValueError, match="must be an instance of MediaField"
|
||||
):
|
||||
mediafile.MediaFile.add_field("somekey", True)
|
||||
|
||||
def test_overwrite_property(self):
|
||||
with pytest.raises(
|
||||
ValueError, match='property "artist" already exists'
|
||||
):
|
||||
mediafile.MediaFile.add_field("artist", mediafile.MediaField())
|
||||
Loading…
Reference in a new issue