From 37a5f9cb156e33690cac37f77cebd103bdbecbcf Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 20:47:51 +0200 Subject: [PATCH 01/10] Add custom feat words --- beets/plugins.py | 6 ++- beetsplug/ftintitle.py | 57 +++++++++++++++++++-------- test/plugins/test_ftintitle.py | 71 +++++++++++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 20 deletions(-) diff --git a/beets/plugins.py b/beets/plugins.py index 7fa0e660a..397c33822 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -632,13 +632,15 @@ def send(event: EventType, **arguments: Any) -> list[Any]: ] -def feat_tokens(for_artist: bool = True) -> str: +def feat_tokens( + for_artist: bool = True, custom_feat_words: list[str] = [] +) -> str: """Return a regular expression that matches phrases like "featuring" that separate a main artist or a song title from secondary artists. The `for_artist` option determines whether the regex should be suitable for matching artist fields (the default) or title fields. """ - feat_words = ["ft", "featuring", "feat", "feat.", "ft."] + feat_words = ["ft", "featuring", "feat", "feat.", "ft."] + custom_feat_words if for_artist: feat_words += ["with", "vs", "and", "con", "&"] return ( diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py index e17d7bc1c..b1331e893 100644 --- a/beetsplug/ftintitle.py +++ b/beetsplug/ftintitle.py @@ -27,7 +27,7 @@ if TYPE_CHECKING: def split_on_feat( - artist: str, for_artist: bool = True + artist: str, for_artist: bool = True, custom_feat_words: list[str] = [] ) -> tuple[str, str | None]: """Given an artist string, split the "main" artist from any artist on the right-hand side of a string like "feat". Return the main @@ -35,7 +35,9 @@ def split_on_feat( may be a string or None if none is present. """ # split on the first "feat". - regex = re.compile(plugins.feat_tokens(for_artist), re.IGNORECASE) + regex = re.compile( + plugins.feat_tokens(for_artist, custom_feat_words), re.IGNORECASE + ) parts = tuple(s.strip() for s in regex.split(artist, 1)) if len(parts) == 1: return parts[0], None @@ -44,18 +46,22 @@ def split_on_feat( return parts -def contains_feat(title: str) -> bool: +def contains_feat(title: str, custom_feat_words: list[str] = []) -> bool: """Determine whether the title contains a "featured" marker.""" return bool( re.search( - plugins.feat_tokens(for_artist=False), + plugins.feat_tokens( + for_artist=False, custom_feat_words=custom_feat_words + ), title, flags=re.IGNORECASE, ) ) -def find_feat_part(artist: str, albumartist: str | None) -> str | None: +def find_feat_part( + artist: str, albumartist: str | None, custom_feat_words: list[str] = [] +) -> str | None: """Attempt to find featured artists in the item's artist fields and return the results. Returns None if no featured artist found. """ @@ -69,20 +75,24 @@ def find_feat_part(artist: str, albumartist: str | None) -> str | None: # featured artist. if albumartist_split[1] != "": # Extract the featured artist from the right-hand side. - _, feat_part = split_on_feat(albumartist_split[1]) + _, feat_part = split_on_feat( + albumartist_split[1], custom_feat_words=custom_feat_words + ) return feat_part # Otherwise, if there's nothing on the right-hand side, # look for a featuring artist on the left-hand side. else: - lhs, _ = split_on_feat(albumartist_split[0]) + lhs, _ = split_on_feat( + albumartist_split[0], custom_feat_words=custom_feat_words + ) if lhs: return lhs # Fall back to conservative handling of the track artist without relying # on albumartist, which covers compilations using a 'Various Artists' # albumartist and album tracks by a guest artist featuring a third artist. - _, feat_part = split_on_feat(artist, False) + _, feat_part = split_on_feat(artist, False, custom_feat_words) return feat_part @@ -96,6 +106,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): "drop": False, "format": "feat. {}", "keep_in_artist": False, + "custom_feat_words": [], } ) @@ -120,10 +131,13 @@ class FtInTitlePlugin(plugins.BeetsPlugin): self.config.set_args(opts) drop_feat = self.config["drop"].get(bool) keep_in_artist_field = self.config["keep_in_artist"].get(bool) + custom_feat_words = self.config["custom_feat_words"].get(list) write = ui.should_write() for item in lib.items(args): - if self.ft_in_title(item, drop_feat, keep_in_artist_field): + if self.ft_in_title( + item, drop_feat, keep_in_artist_field, custom_feat_words + ): item.store() if write: item.try_write() @@ -135,9 +149,12 @@ class FtInTitlePlugin(plugins.BeetsPlugin): """Import hook for moving featuring artist automatically.""" drop_feat = self.config["drop"].get(bool) keep_in_artist_field = self.config["keep_in_artist"].get(bool) + custom_feat_words = self.config["custom_feat_words"].get(list) for item in task.imported_items(): - if self.ft_in_title(item, drop_feat, keep_in_artist_field): + if self.ft_in_title( + item, drop_feat, keep_in_artist_field, custom_feat_words + ): item.store() def update_metadata( @@ -146,6 +163,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): feat_part: str, drop_feat: bool, keep_in_artist_field: bool, + custom_feat_words: list[str], ) -> None: """Choose how to add new artists to the title and set the new metadata. Also, print out messages about any changes that are made. @@ -158,17 +176,21 @@ class FtInTitlePlugin(plugins.BeetsPlugin): "artist: {.artist} (Not changing due to keep_in_artist)", item ) else: - track_artist, _ = split_on_feat(item.artist) + track_artist, _ = split_on_feat( + item.artist, custom_feat_words=custom_feat_words + ) self._log.info("artist: {0.artist} -> {1}", item, track_artist) item.artist = track_artist if item.artist_sort: # Just strip the featured artist from the sort name. - item.artist_sort, _ = split_on_feat(item.artist_sort) + item.artist_sort, _ = split_on_feat( + item.artist_sort, custom_feat_words=custom_feat_words + ) # Only update the title if it does not already contain a featured # artist and if we do not drop featuring information. - if not drop_feat and not contains_feat(item.title): + if not drop_feat and not contains_feat(item.title, custom_feat_words): feat_format = self.config["format"].as_str() new_format = feat_format.format(feat_part) new_title = f"{item.title} {new_format}" @@ -180,6 +202,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): item: Item, drop_feat: bool, keep_in_artist_field: bool, + custom_feat_words: list[str], ) -> bool: """Look for featured artists in the item's artist fields and move them to the title. @@ -196,19 +219,21 @@ class FtInTitlePlugin(plugins.BeetsPlugin): if albumartist and artist == albumartist: return False - _, featured = split_on_feat(artist) + _, featured = split_on_feat(artist, custom_feat_words=custom_feat_words) if not featured: return False self._log.info("{.filepath}", item) # Attempt to find the featured artist. - feat_part = find_feat_part(artist, albumartist) + feat_part = find_feat_part(artist, albumartist, custom_feat_words) if not feat_part: self._log.info("no featuring artists found") return False # If we have a featuring artist, move it to the title. - self.update_metadata(item, feat_part, drop_feat, keep_in_artist_field) + self.update_metadata( + item, feat_part, drop_feat, keep_in_artist_field, custom_feat_words + ) return True diff --git a/test/plugins/test_ftintitle.py b/test/plugins/test_ftintitle.py index 005318b11..466a95e4f 100644 --- a/test/plugins/test_ftintitle.py +++ b/test/plugins/test_ftintitle.py @@ -38,13 +38,15 @@ def env() -> Generator[FtInTitlePluginFunctional, None, None]: def set_config( - env: FtInTitlePluginFunctional, cfg: Optional[Dict[str, Union[str, bool]]] + env: FtInTitlePluginFunctional, + cfg: Optional[Dict[str, Union[str, bool, list[str]]]], ) -> None: cfg = {} if cfg is None else cfg defaults = { "drop": False, "auto": True, "keep_in_artist": False, + "custom_feat_words": [], } env.config["ftintitle"].set(defaults) env.config["ftintitle"].set(cfg) @@ -170,11 +172,44 @@ def add_item( ("Alice ft Bob", "Song 1"), id="keep-in-artist-drop-from-title", ), + # ---- custom_feat_words variants ---- + pytest.param( + {"format": "featuring {}", "custom_feat_words": ["med"]}, + ("ftintitle",), + ("Alice med Bob", "Song 1", "Alice"), + ("Alice", "Song 1 featuring Bob"), + id="custom-feat-words", + ), + pytest.param( + { + "format": "featuring {}", + "keep_in_artist": True, + "custom_feat_words": ["med"], + }, + ("ftintitle",), + ("Alice med Bob", "Song 1", "Alice"), + ("Alice med Bob", "Song 1 featuring Bob"), + id="custom-feat-words-keep-in-artists", + ), + pytest.param( + { + "format": "featuring {}", + "keep_in_artist": True, + "custom_feat_words": ["med"], + }, + ( + "ftintitle", + "-d", + ), + ("Alice med Bob", "Song 1", "Alice"), + ("Alice med Bob", "Song 1"), + id="custom-feat-words-keep-in-artists-drop-from-title", + ), ], ) def test_ftintitle_functional( env: FtInTitlePluginFunctional, - cfg: Optional[Dict[str, Union[str, bool]]], + cfg: Optional[Dict[str, Union[str, bool, list[str]]]], cmd_args: Tuple[str, ...], given: Tuple[str, str, Optional[str]], expected: Tuple[str, str], @@ -256,3 +291,35 @@ def test_split_on_feat( ) def test_contains_feat(given: str, expected: bool) -> None: assert ftintitle.contains_feat(given) is expected + + +@pytest.mark.parametrize( + "given,custom_feat_words,expected", + [ + ("Alice ft. Bob", [], True), + ("Alice feat. Bob", [], True), + ("Alice feat Bob", [], True), + ("Alice featuring Bob", [], True), + ("Alice (ft. Bob)", [], True), + ("Alice (feat. Bob)", [], True), + ("Alice [ft. Bob]", [], True), + ("Alice [feat. Bob]", [], True), + ("Alice defeat Bob", [], False), + ("Aliceft.Bob", [], False), + ("Alice (defeat Bob)", [], False), + ("Live and Let Go", [], False), + ("Come With Me", [], False), + ("Alice x Bob", ["x"], True), + ("Alice x Bob", ["X"], True), + ("Alice och Xavier", ["x"], False), + ("Alice ft. Xavier", ["x"], True), + ("Alice med Carol", ["med"], True), + ("Alice med Carol", [], False), + ], +) +def test_custom_feat_words( + given: str, custom_feat_words: Optional[list[str]], expected: bool +) -> None: + if custom_feat_words is None: + custom_feat_words = [] + assert ftintitle.contains_feat(given, custom_feat_words) is expected From 992938f0ae70631f0124642a1d07c63b7ba72973 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 20:58:38 +0200 Subject: [PATCH 02/10] Add documentation --- docs/plugins/ftintitle.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plugins/ftintitle.rst b/docs/plugins/ftintitle.rst index 90b89ae89..733c50510 100644 --- a/docs/plugins/ftintitle.rst +++ b/docs/plugins/ftintitle.rst @@ -28,6 +28,8 @@ file. The available options are: - **keep_in_artist**: Keep the featuring X part in the artist field. This can be useful if you still want to be able to search for features in the artist field. Default: ``no``. +- **custom_feat_word**: Add custom words to the feat list; any words you add will + also be treated as "feat" tokens. Default: ``[]``. Running Manually ---------------- From e90738a6e27d53e13964d4c7de191d56dd1e80f5 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 21:09:17 +0200 Subject: [PATCH 03/10] Added changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4f32093fe..f5c96d598 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,7 @@ Unreleased ---------- New features: +- Added argument for custom feat. words in ftintitle. Bug fixes: From 51c971f089a0f16debccf10122869c5a278a0ed2 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 21:38:13 +0200 Subject: [PATCH 04/10] Fix sourcery-ai comments --- beets/plugins.py | 6 ++++-- beetsplug/ftintitle.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/beets/plugins.py b/beets/plugins.py index 397c33822..65c181388 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -633,14 +633,16 @@ def send(event: EventType, **arguments: Any) -> list[Any]: def feat_tokens( - for_artist: bool = True, custom_feat_words: list[str] = [] + for_artist: bool = True, custom_feat_words: list[str] | None = None ) -> str: """Return a regular expression that matches phrases like "featuring" that separate a main artist or a song title from secondary artists. The `for_artist` option determines whether the regex should be suitable for matching artist fields (the default) or title fields. """ - feat_words = ["ft", "featuring", "feat", "feat.", "ft."] + custom_feat_words + feat_words = ["ft", "featuring", "feat", "feat.", "ft."] + if isinstance(custom_feat_words, list): + feat_words += custom_feat_words if for_artist: feat_words += ["with", "vs", "and", "con", "&"] return ( diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py index b1331e893..6837732b2 100644 --- a/beetsplug/ftintitle.py +++ b/beetsplug/ftintitle.py @@ -27,7 +27,9 @@ if TYPE_CHECKING: def split_on_feat( - artist: str, for_artist: bool = True, custom_feat_words: list[str] = [] + artist: str, + for_artist: bool = True, + custom_feat_words: list[str] | None = None, ) -> tuple[str, str | None]: """Given an artist string, split the "main" artist from any artist on the right-hand side of a string like "feat". Return the main @@ -46,7 +48,9 @@ def split_on_feat( return parts -def contains_feat(title: str, custom_feat_words: list[str] = []) -> bool: +def contains_feat( + title: str, custom_feat_words: list[str] | None = None +) -> bool: """Determine whether the title contains a "featured" marker.""" return bool( re.search( @@ -60,7 +64,9 @@ def contains_feat(title: str, custom_feat_words: list[str] = []) -> bool: def find_feat_part( - artist: str, albumartist: str | None, custom_feat_words: list[str] = [] + artist: str, + albumartist: str | None, + custom_feat_words: list[str] | None = None, ) -> str | None: """Attempt to find featured artists in the item's artist fields and return the results. Returns None if no featured artist found. From af09e58fb07ca6363b849f610f93915ebeea2801 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 21:40:22 +0200 Subject: [PATCH 05/10] Add new line after New features: --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index f5c96d598..d696f98ee 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,6 +8,7 @@ Unreleased ---------- New features: + - Added argument for custom feat. words in ftintitle. Bug fixes: From b95a17d8d35562cca9d3353d23202e0cbdba1314 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 22:40:27 +0200 Subject: [PATCH 06/10] remove feat from custom_feat_words --- beets/plugins.py | 6 ++--- beetsplug/ftintitle.py | 46 ++++++++++++++++------------------ test/plugins/test_ftintitle.py | 22 ++++++++-------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/beets/plugins.py b/beets/plugins.py index 65c181388..b96a3703c 100644 --- a/beets/plugins.py +++ b/beets/plugins.py @@ -633,7 +633,7 @@ def send(event: EventType, **arguments: Any) -> list[Any]: def feat_tokens( - for_artist: bool = True, custom_feat_words: list[str] | None = None + for_artist: bool = True, custom_words: list[str] | None = None ) -> str: """Return a regular expression that matches phrases like "featuring" that separate a main artist or a song title from secondary artists. @@ -641,8 +641,8 @@ def feat_tokens( suitable for matching artist fields (the default) or title fields. """ feat_words = ["ft", "featuring", "feat", "feat.", "ft."] - if isinstance(custom_feat_words, list): - feat_words += custom_feat_words + if isinstance(custom_words, list): + feat_words += custom_words if for_artist: feat_words += ["with", "vs", "and", "con", "&"] return ( diff --git a/beetsplug/ftintitle.py b/beetsplug/ftintitle.py index 6837732b2..ef9b763cf 100644 --- a/beetsplug/ftintitle.py +++ b/beetsplug/ftintitle.py @@ -29,7 +29,7 @@ if TYPE_CHECKING: def split_on_feat( artist: str, for_artist: bool = True, - custom_feat_words: list[str] | None = None, + custom_words: list[str] | None = None, ) -> tuple[str, str | None]: """Given an artist string, split the "main" artist from any artist on the right-hand side of a string like "feat". Return the main @@ -38,7 +38,7 @@ def split_on_feat( """ # split on the first "feat". regex = re.compile( - plugins.feat_tokens(for_artist, custom_feat_words), re.IGNORECASE + plugins.feat_tokens(for_artist, custom_words), re.IGNORECASE ) parts = tuple(s.strip() for s in regex.split(artist, 1)) if len(parts) == 1: @@ -48,15 +48,11 @@ def split_on_feat( return parts -def contains_feat( - title: str, custom_feat_words: list[str] | None = None -) -> bool: +def contains_feat(title: str, custom_words: list[str] | None = None) -> bool: """Determine whether the title contains a "featured" marker.""" return bool( re.search( - plugins.feat_tokens( - for_artist=False, custom_feat_words=custom_feat_words - ), + plugins.feat_tokens(for_artist=False, custom_words=custom_words), title, flags=re.IGNORECASE, ) @@ -66,7 +62,7 @@ def contains_feat( def find_feat_part( artist: str, albumartist: str | None, - custom_feat_words: list[str] | None = None, + custom_words: list[str] | None = None, ) -> str | None: """Attempt to find featured artists in the item's artist fields and return the results. Returns None if no featured artist found. @@ -82,7 +78,7 @@ def find_feat_part( if albumartist_split[1] != "": # Extract the featured artist from the right-hand side. _, feat_part = split_on_feat( - albumartist_split[1], custom_feat_words=custom_feat_words + albumartist_split[1], custom_words=custom_words ) return feat_part @@ -90,7 +86,7 @@ def find_feat_part( # look for a featuring artist on the left-hand side. else: lhs, _ = split_on_feat( - albumartist_split[0], custom_feat_words=custom_feat_words + albumartist_split[0], custom_words=custom_words ) if lhs: return lhs @@ -98,7 +94,7 @@ def find_feat_part( # Fall back to conservative handling of the track artist without relying # on albumartist, which covers compilations using a 'Various Artists' # albumartist and album tracks by a guest artist featuring a third artist. - _, feat_part = split_on_feat(artist, False, custom_feat_words) + _, feat_part = split_on_feat(artist, False, custom_words) return feat_part @@ -112,7 +108,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): "drop": False, "format": "feat. {}", "keep_in_artist": False, - "custom_feat_words": [], + "custom_words": [], } ) @@ -137,12 +133,12 @@ class FtInTitlePlugin(plugins.BeetsPlugin): self.config.set_args(opts) drop_feat = self.config["drop"].get(bool) keep_in_artist_field = self.config["keep_in_artist"].get(bool) - custom_feat_words = self.config["custom_feat_words"].get(list) + custom_words = self.config["custom_words"].get(list) write = ui.should_write() for item in lib.items(args): if self.ft_in_title( - item, drop_feat, keep_in_artist_field, custom_feat_words + item, drop_feat, keep_in_artist_field, custom_words ): item.store() if write: @@ -155,11 +151,11 @@ class FtInTitlePlugin(plugins.BeetsPlugin): """Import hook for moving featuring artist automatically.""" drop_feat = self.config["drop"].get(bool) keep_in_artist_field = self.config["keep_in_artist"].get(bool) - custom_feat_words = self.config["custom_feat_words"].get(list) + custom_words = self.config["custom_words"].get(list) for item in task.imported_items(): if self.ft_in_title( - item, drop_feat, keep_in_artist_field, custom_feat_words + item, drop_feat, keep_in_artist_field, custom_words ): item.store() @@ -169,7 +165,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): feat_part: str, drop_feat: bool, keep_in_artist_field: bool, - custom_feat_words: list[str], + custom_words: list[str], ) -> None: """Choose how to add new artists to the title and set the new metadata. Also, print out messages about any changes that are made. @@ -183,7 +179,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): ) else: track_artist, _ = split_on_feat( - item.artist, custom_feat_words=custom_feat_words + item.artist, custom_words=custom_words ) self._log.info("artist: {0.artist} -> {1}", item, track_artist) item.artist = track_artist @@ -191,12 +187,12 @@ class FtInTitlePlugin(plugins.BeetsPlugin): if item.artist_sort: # Just strip the featured artist from the sort name. item.artist_sort, _ = split_on_feat( - item.artist_sort, custom_feat_words=custom_feat_words + item.artist_sort, custom_words=custom_words ) # Only update the title if it does not already contain a featured # artist and if we do not drop featuring information. - if not drop_feat and not contains_feat(item.title, custom_feat_words): + if not drop_feat and not contains_feat(item.title, custom_words): feat_format = self.config["format"].as_str() new_format = feat_format.format(feat_part) new_title = f"{item.title} {new_format}" @@ -208,7 +204,7 @@ class FtInTitlePlugin(plugins.BeetsPlugin): item: Item, drop_feat: bool, keep_in_artist_field: bool, - custom_feat_words: list[str], + custom_words: list[str], ) -> bool: """Look for featured artists in the item's artist fields and move them to the title. @@ -225,14 +221,14 @@ class FtInTitlePlugin(plugins.BeetsPlugin): if albumartist and artist == albumartist: return False - _, featured = split_on_feat(artist, custom_feat_words=custom_feat_words) + _, featured = split_on_feat(artist, custom_words=custom_words) if not featured: return False self._log.info("{.filepath}", item) # Attempt to find the featured artist. - feat_part = find_feat_part(artist, albumartist, custom_feat_words) + feat_part = find_feat_part(artist, albumartist, custom_words) if not feat_part: self._log.info("no featuring artists found") @@ -240,6 +236,6 @@ class FtInTitlePlugin(plugins.BeetsPlugin): # If we have a featuring artist, move it to the title. self.update_metadata( - item, feat_part, drop_feat, keep_in_artist_field, custom_feat_words + item, feat_part, drop_feat, keep_in_artist_field, custom_words ) return True diff --git a/test/plugins/test_ftintitle.py b/test/plugins/test_ftintitle.py index 466a95e4f..30b414948 100644 --- a/test/plugins/test_ftintitle.py +++ b/test/plugins/test_ftintitle.py @@ -46,7 +46,7 @@ def set_config( "drop": False, "auto": True, "keep_in_artist": False, - "custom_feat_words": [], + "custom_words": [], } env.config["ftintitle"].set(defaults) env.config["ftintitle"].set(cfg) @@ -172,9 +172,9 @@ def add_item( ("Alice ft Bob", "Song 1"), id="keep-in-artist-drop-from-title", ), - # ---- custom_feat_words variants ---- + # ---- custom_words variants ---- pytest.param( - {"format": "featuring {}", "custom_feat_words": ["med"]}, + {"format": "featuring {}", "custom_words": ["med"]}, ("ftintitle",), ("Alice med Bob", "Song 1", "Alice"), ("Alice", "Song 1 featuring Bob"), @@ -184,7 +184,7 @@ def add_item( { "format": "featuring {}", "keep_in_artist": True, - "custom_feat_words": ["med"], + "custom_words": ["med"], }, ("ftintitle",), ("Alice med Bob", "Song 1", "Alice"), @@ -195,7 +195,7 @@ def add_item( { "format": "featuring {}", "keep_in_artist": True, - "custom_feat_words": ["med"], + "custom_words": ["med"], }, ( "ftintitle", @@ -294,7 +294,7 @@ def test_contains_feat(given: str, expected: bool) -> None: @pytest.mark.parametrize( - "given,custom_feat_words,expected", + "given,custom_words,expected", [ ("Alice ft. Bob", [], True), ("Alice feat. Bob", [], True), @@ -317,9 +317,9 @@ def test_contains_feat(given: str, expected: bool) -> None: ("Alice med Carol", [], False), ], ) -def test_custom_feat_words( - given: str, custom_feat_words: Optional[list[str]], expected: bool +def test_custom_words( + given: str, custom_words: Optional[list[str]], expected: bool ) -> None: - if custom_feat_words is None: - custom_feat_words = [] - assert ftintitle.contains_feat(given, custom_feat_words) is expected + if custom_words is None: + custom_words = [] + assert ftintitle.contains_feat(given, custom_words) is expected From 717809c52c198e426335ef9be2f4ea082e5c1662 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 22:40:44 +0200 Subject: [PATCH 07/10] Better custom_words documentation --- docs/plugins/ftintitle.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/ftintitle.rst b/docs/plugins/ftintitle.rst index 733c50510..6528b61cd 100644 --- a/docs/plugins/ftintitle.rst +++ b/docs/plugins/ftintitle.rst @@ -28,8 +28,8 @@ file. The available options are: - **keep_in_artist**: Keep the featuring X part in the artist field. This can be useful if you still want to be able to search for features in the artist field. Default: ``no``. -- **custom_feat_word**: Add custom words to the feat list; any words you add will - also be treated as "feat" tokens. Default: ``[]``. +- **custom_words**: List of additional words that will be treated as a marker for + artist features. Default: ``[]``. Running Manually ---------------- From 0f0e38b0bfea5f31fd3f3e4b2463d39a0555d096 Mon Sep 17 00:00:00 2001 From: Ember Light Date: Sun, 12 Oct 2025 22:40:55 +0200 Subject: [PATCH 08/10] Add link in changelog --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index d696f98ee..e6a81ab14 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,7 +9,7 @@ Unreleased New features: -- Added argument for custom feat. words in ftintitle. +- :doc:`plugins/fitintitle`: Added argument for custom feat. words in ftintitle. Bug fixes: From 320ebf6a205041dd5062082c631c01026342cc17 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 14 Oct 2025 14:07:45 +0200 Subject: [PATCH 09/10] Fix misspelling --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e6a81ab14..d4d90ebe5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,7 +9,7 @@ Unreleased New features: -- :doc:`plugins/fitintitle`: Added argument for custom feat. words in ftintitle. +- :doc:`plugins/ftintitle`: Added argument for custom feat. words in ftintitle. Bug fixes: From 83858cd7ca0ed7f0ed3f464a0262ad48d76377da Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 14 Oct 2025 14:08:30 +0200 Subject: [PATCH 10/10] Fixed too long text line --- docs/plugins/ftintitle.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/ftintitle.rst b/docs/plugins/ftintitle.rst index 6528b61cd..1a95d03a8 100644 --- a/docs/plugins/ftintitle.rst +++ b/docs/plugins/ftintitle.rst @@ -28,8 +28,8 @@ file. The available options are: - **keep_in_artist**: Keep the featuring X part in the artist field. This can be useful if you still want to be able to search for features in the artist field. Default: ``no``. -- **custom_words**: List of additional words that will be treated as a marker for - artist features. Default: ``[]``. +- **custom_words**: List of additional words that will be treated as a marker + for artist features. Default: ``[]``. Running Manually ----------------