From 2bb072fde1be905ab67b6ab021614741988cecbc Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 26 Oct 2025 20:20:35 -0700 Subject: [PATCH] fixes --- beetsplug/titlecase.py | 54 +++++++++++++++++----------------- docs/plugins/titlecase.rst | 6 ++-- test/plugins/test_titlecase.py | 50 +++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/beetsplug/titlecase.py b/beetsplug/titlecase.py index d5b0e2221..7115ad919 100644 --- a/beetsplug/titlecase.py +++ b/beetsplug/titlecase.py @@ -17,7 +17,7 @@ Title case logic is derived from the python-titlecase library. Provides a template function and a tag modification function.""" import re -from typing import Pattern +from typing import Pattern, Optional from titlecase import titlecase @@ -32,8 +32,7 @@ __version__ = "1.0" # These fields are excluded to avoid modifying anything # that may be case sensistive, or important to database # function -EXCLUDED_INFO_FIELDS = set( - [ +EXCLUDED_INFO_FIELDS: set[str] = { "acoustid_fingerprint", "acoustid_id", "artists_ids", @@ -54,15 +53,14 @@ EXCLUDED_INFO_FIELDS = set( "bitrate_mode", "encoder_info", "encoder_settings", - ] -) + } class TitlecasePlugin(BeetsPlugin): preserve: dict[str, str] = {} preserve_phrases: dict[str, Pattern[str]] = {} force_lowercase: bool = True - fields_to_process: set[str] = set([]) + fields_to_process: set[str] = {} def __init__(self) -> None: super().__init__() @@ -111,9 +109,10 @@ class TitlecasePlugin(BeetsPlugin): Only uses fields included. Last, the EXCLUDED_INFO_FIELDS are removed to prevent unitentional modification. """ - initial_field_list = set(fields) - initial_field_list -= set(EXCLUDED_INFO_FIELDS) - self.fields_to_process = initial_field_list + if fields: + initial_field_list = set(fields) + initial_field_list -= set(EXCLUDED_INFO_FIELDS) + self.fields_to_process = initial_field_list def __preserve_words__(self, preserve: list[str]) -> None: for word in preserve: @@ -124,7 +123,7 @@ class TitlecasePlugin(BeetsPlugin): else: self.preserve[word.upper()] = word - def __preserved__(self, word, **kwargs) -> str | None: + def __preserved__(self, word, **kwargs) -> Optional[str]: """Callback function for words to preserve case of.""" if preserved_word := self.preserve.get(word.upper(), ""): return preserved_word @@ -150,20 +149,18 @@ class TitlecasePlugin(BeetsPlugin): """ for field in self.fields_to_process: init_field = getattr(item, field, "") - if isinstance(init_field, list): - cased_list: list[str] = [self.titlecase(i) for i in init_field] - self._log.info( - f""" - {field}: {", ".join(init_field)} -> - {", ".join(cased_list)}""" - ) - setattr(item, field, cased_list) - elif init_field and isinstance(init_field, str): - cased: str = self.titlecase(init_field) - self._log.info(f"{field}: {init_field} -> {cased}") - setattr(item, field, cased) - else: - self._log.info(f"{field}: no string present") + if init_field: + if isinstance(init_field, list) and isinstance(init_field[0], str): + cased_list: list[str] = [self.titlecase(i) for i in init_field] + self._log.info((f"{field}: {', '.join(init_field)} -> " + f"{', '.join(cased_list)}")) + setattr(item, field, cased_list) + elif isinstance(init_field, str): + cased: str = self.titlecase(init_field) + self._log.info(f"{field}: {init_field} -> {cased}") + setattr(item, field, cased) + else: + self._log.info(f"{field}: no string present") def titlecase(self, text: str) -> str: """Titlecase the given text.""" @@ -179,6 +176,9 @@ class TitlecasePlugin(BeetsPlugin): def imported(self, session: ImportSession, task: ImportTask) -> None: """Import hook for titlecasing on import.""" for item in task.imported_items(): - self._log.info(f"titlecasing {item.title}:") - self.titlecase_fields(item) - item.store() + try: + self._log.info(f"titlecasing {item.title}:") + self.titlecase_fields(item) + item.store() + except Exception as e: + self._log.info(f"titlecasing exception {e}") diff --git a/docs/plugins/titlecase.rst b/docs/plugins/titlecase.rst index 909ea8514..61dbdeae7 100644 --- a/docs/plugins/titlecase.rst +++ b/docs/plugins/titlecase.rst @@ -5,13 +5,13 @@ The ``titlecase`` plugin lets you format tags and paths in accordance with the titlecase guidelines in the `New York Times Manual of Style`_ and uses the `python titlecase library`_. -Motiviation for this plugin comes from a desire to resolve differences in style +Motivation for this plugin comes from a desire to resolve differences in style between databases sources. For example, `MusicBrainz style`_ follows standard title case rules, except in the case of terms that are deemed generic, like "mix" and "remix". On the other hand, `Discogs guidlines`_ recommend capitalizing the first letter of each word, even for small words like "of" and -"a". This plugin aims to achieve a middleground between disparate approaches to -casing, and bring more consistency to titlecasing in your library. +"a". This plugin aims to achieve a middle ground between disparate approaches to +casing, and bring more consistency to titles in your library. .. _discogs style: https://support.discogs.com/hc/en-us/articles/360005006334-Database-Guidelines-1-General-Rules#Capitalization_And_Grammar diff --git a/test/plugins/test_titlecase.py b/test/plugins/test_titlecase.py index cdadc5372..3b2fc9a1d 100644 --- a/test/plugins/test_titlecase.py +++ b/test/plugins/test_titlecase.py @@ -66,6 +66,55 @@ titlecase_test_cases = [ album="the black messiah", title="Till It's Done (Tutu)", ), + }, + { + "config": { + "preserve": [""], + "fields": ["artist", "albumartist", + "title", + "album", + "mb_albumd", "year"], + "force_lowercase": True, + "small_first_last": True, + }, + "item": Item( + artist="OPHIDIAN", + albumartist="ophiDIAN", + format="CD", + year=2003, + album="BLACKBOX", + title="KhAmElEoN", + ), + "expected": Item( + artist="Ophidian", + albumartist="Ophidian", + format="CD", + year=2003, + album="Blackbox", + title="Khameleon", + ), + }, + { + "config": { + "preserve": [""], + "fields": [ + "artists", + "artists_ids", + "discogs_artistid" + ], + "force_lowercase": False, + "small_first_last": True, + }, + "item": Item( + artists=["artist_one", "artist_two"], + artists_ids=["aBcDeF32", "aBcDeF12"], + discogs_artistid=21 + ), + "expected": Item( + artists=["Artist_One", "Artist_Two"], + artists_ids=["aBcDeF32", "aBcDeF12"], + discogs_artistid=21 + ), } ] @@ -146,3 +195,4 @@ class TitlecasePluginTest(PluginTestCase): output == f"{expected.artist} - {expected.album} - {expected.title}\n" ) + self.run_command(f"remove", expected.artist, "-f")