diff --git a/beetsplug/bandcamp/__init__.py b/beetsplug/bandcamp/__init__.py index 17a3bc9..615c2aa 100644 --- a/beetsplug/bandcamp/__init__.py +++ b/beetsplug/bandcamp/__init__.py @@ -21,10 +21,10 @@ import logging import re from contextlib import contextmanager -from functools import lru_cache, partial +from functools import partial from itertools import chain from operator import itemgetter -from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Literal, Sequence +from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Literal from beets import IncludeLazyConfig, config, library, plugins @@ -64,10 +64,10 @@ class BandcampRequestsHandler: _log: logging.Logger config: IncludeLazyConfig - def _exc(self, msg_template: str, *args: Sequence[str]) -> None: + def _exc(self, msg_template: str, *args: object) -> None: self._log.log(logging.WARNING, msg_template, *args, exc_info=True) - def _info(self, msg_template: str, *args: Sequence[str]) -> None: + def _info(self, msg_template: str, *args: object) -> None: self._log.log(logging.DEBUG, msg_template, *args, exc_info=False) def _get(self, url: str) -> str: diff --git a/beetsplug/bandcamp/album_name.py b/beetsplug/bandcamp/album_name.py index 769f17d..52dae25 100644 --- a/beetsplug/bandcamp/album_name.py +++ b/beetsplug/bandcamp/album_name.py @@ -17,7 +17,7 @@ class AlbumName: _series = r"(?i:\b(part|volume|pt|vol)\b\.?)" SERIES = re.compile(rf"{_series}[ ]?[A-Z\d.-]+\b") SERIES_FMT = re.compile(rf"^(.+){_series} *0*") - REMIX_IN_TITLE = re.compile(r"[\( :]+(with re|inc|\+).*mix(\)|(.*$))", re.I) + REMIX_IN_TITLE = re.compile(r"[\( :]+(with re|inc|\+).*?mix[^)]*(\)|$)", re.I) CLEAN_EPLP = re.compile(r"(?:[([]|Double ){0,2}(\b[EL]P\b)\S?", re.I) EPLP_ALBUM = re.compile(r"\b(?!VA|0\d|-)([^\s:]+\b|[&, ])+ [EL]P\b( [\w#][^ ]+$)?") EPLP_ALBUM_LINE = re.compile(r"\b(?=[A-Z])(((?!Vinyl|VA|-)[^:\s]+ )+)[EL]P$", re.M) @@ -35,7 +35,7 @@ class AlbumName: CLEAN_CATALOGNUM = re.compile( r""" (^[A-Z]+\d+\ [|-]\ ) - | [^[\w)]*?\[[A-Z]+\d+\] + | [^[\w)\]]*\[[A-Z]+\d+\] """, re.VERBOSE, ) diff --git a/beetsplug/bandcamp/catalognum.py b/beetsplug/bandcamp/catalognum.py index 427eb2f..1545d94 100644 --- a/beetsplug/bandcamp/catalognum.py +++ b/beetsplug/bandcamp/catalognum.py @@ -69,7 +69,7 @@ class Catalognum: (?:[.-]\d+)? # .1 in RAWVA01.1RP, -1322 in SOP 063-1322 (?: (?!MIX) - (?<=\d\d)-?[A-Z]+ # CD in IBM001CD (needs at least two preceding digits) + (?<=\d\d)-?[A-Z]+ # CD in IBM001CD (at least two preceding digits) | RP # RP in RAWVA01.1RP )? )? diff --git a/beetsplug/bandcamp/helpers.py b/beetsplug/bandcamp/helpers.py index b72deea..906cd74 100644 --- a/beetsplug/bandcamp/helpers.py +++ b/beetsplug/bandcamp/helpers.py @@ -105,12 +105,12 @@ def medium_count(self) -> int: ), "track_alt": re.compile( r""" - (?:(?<=^)|(?<=-\ )) # beginning of the line or right after the title separator + (?:(?<=^)|(?<=-\ )) # beginning of the line or right after the title separator ( # capture the catalogue number, either - (?:[A-J]{1,3}[12]?\.?\d) # A1, B2, E4, A1.1 etc. - | (?:[AB]+(?=\W{2}\b)) # A, AA BB + [A-J]{1,3}[12]?\.?\d # A1, B2, E4, A1.1 etc. + | [AB]+(?=\W{2}\b) # A, AA BB ) - (?:[/.:)_\s-]+) # consume the non-word chars for removal + [/.:)_\s-]+ # consume the non-word chars for removal """, re.M | re.VERBOSE, ), @@ -154,18 +154,18 @@ def split_artist_title(m: Match[str]) -> str: # fmt: off CLEAN_PATTERNS: List[Tuple[Pattern[str], Union[str, Callable[[Match[str]], str]]]] = [ (re.compile(rf"(([\[(])|(^| ))\*?({'|'.join(rm_strings)})(?(2)[])]|([- ]|$))", re.I), ""), - (re.compile(r" -(\S)"), r" - \1"), # hi -bye -> hi - bye - (re.compile(r"(\S)- "), r"\1 - "), # hi- bye -> hi - bye - (re.compile(r" +"), " "), # hi bye -> hi bye - (re.compile(r"(- )?\( *"), "("), # hi - ( bye) -> hi (bye) - (re.compile(r" \)+|(\)+$)"), ")"), # hi (bye )) -> hi (bye) - (re.compile(r"- Reworked"), "(Reworked)"), # bye - Reworked -> bye (Reworked) - (re.compile(rf"(?<= - )([^()]+?) - ({REMIX.pattern})$", re.I), r"\1 (\2)"), # - bye - Some Mix -> - bye (Some Mix) - (re.compile(rf"(\({REMIX.pattern})$", re.I), r"\1)"), # bye - (Some Mix -> bye - (Some Mix) - (re.compile(rf"- *({REMIX.pattern})$", re.I), r"(\1)"), # bye - Some Mix -> bye (Some Mix) - (re.compile(r'(^|- )[“"]([^”"]+)[”"]( \(|$)'), r"\1\2\3"), # "bye" -> bye; hi - "bye" -> hi - bye - (re.compile(r"\((the )?(remixes)\)", re.I), r"\2"), # Album (Remixes) -> Album Remixes - (re.compile(r"examine-.+CD\d+_([^_-]+)[_-](.*)"), split_artist_title), # See https://examine-archive.bandcamp.com/album/va-examine-archive-international-sampler-xmn01 + (re.compile(r" -(\S)"), r" - \1"), # hi -bye -> hi - bye # noqa + (re.compile(r"(\S)- "), r"\1 - "), # hi- bye -> hi - bye # noqa + (re.compile(r" +"), " "), # hi bye -> hi bye # noqa + (re.compile(r"(- )?\( *"), "("), # hi - ( bye) -> hi (bye) # noqa + (re.compile(r" \)+|(\)+$)"), ")"), # hi (bye )) -> hi (bye) # noqa + (re.compile(r"- Reworked"), "(Reworked)"), # bye - Reworked -> bye (Reworked) # noqa + (re.compile(rf"(?<= - )([^()]+?) - ({REMIX.pattern})$", re.I), r"\1 (\2)"), # - bye - Some Mix -> - bye (Some Mix) # noqa + (re.compile(rf"(\({REMIX.pattern})$", re.I), r"\1)"), # bye - (Some Mix -> bye - (Some Mix) # noqa + (re.compile(rf"- *({REMIX.pattern})$", re.I), r"(\1)"), # bye - Some Mix -> bye (Some Mix) # noqa + (re.compile(r'(^|- )[“"]([^”"]+)[”"]( \(|$)'), r"\1\2\3"), # "bye" -> bye; hi - "bye" -> hi - bye # noqa + (re.compile(r"\((the )?(remixes)\)", re.I), r"\2"), # Album (Remixes) -> Album Remixes # noqa + (re.compile(r"examine-.+CD\d+_([^_-]+)[_-](.*)"), split_artist_title), # See https://examine-archive.bandcamp.com/album/va-examine-archive-international-sampler-xmn01 # noqa ] # fmt: on diff --git a/beetsplug/bandcamp/http.py b/beetsplug/bandcamp/http.py index c703f95..05a68b5 100644 --- a/beetsplug/bandcamp/http.py +++ b/beetsplug/bandcamp/http.py @@ -1,6 +1,5 @@ from functools import lru_cache from html import unescape -from urllib.parse import urlsplit from beets import __version__ import httpx @@ -11,6 +10,7 @@ _client = httpx.Client(headers={"User-Agent": USER_AGENT}) + @lru_cache(maxsize=None) def http_get_text(url: str) -> str: """Return text contents of the url.""" diff --git a/beetsplug/bandcamp/search.py b/beetsplug/bandcamp/search.py index 64b36c2..605ae41 100644 --- a/beetsplug/bandcamp/search.py +++ b/beetsplug/bandcamp/search.py @@ -2,7 +2,6 @@ import re from difflib import SequenceMatcher -from html import unescape from operator import itemgetter from typing import Any, Callable, Dict, List from urllib.parse import quote_plus diff --git a/beetsplug/bandcamp/track.py b/beetsplug/bandcamp/track.py index 3b3574a..5c2ccb1 100644 --- a/beetsplug/bandcamp/track.py +++ b/beetsplug/bandcamp/track.py @@ -227,8 +227,6 @@ def info(self) -> JSONDict: artists = self.artists if self.ft_artist: artists.append(self.ft_artist) - # if self.remix: - # artists.append(self.remix.remixer) return { "index": self.index, diff --git a/tests/test_genre.py b/tests/test_genre.py index bd38bbf..ce7c216 100644 --- a/tests/test_genre.py +++ b/tests/test_genre.py @@ -1,4 +1,5 @@ """Tests for genre functionality.""" + import pytest from beetsplug.bandcamp.metaguru import Metaguru diff --git a/tests/test_lib.py b/tests/test_lib.py index 2d167ac..b4205fe 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -16,8 +16,6 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Tuple import pytest -from beetsplug.bandcamp import BandcampPlugin -from beetsplug.bandcamp.metaguru import Metaguru from rich.console import Group from rich.traceback import install from rich_tables.utils import ( @@ -30,6 +28,9 @@ wrap, ) +from beetsplug.bandcamp import BandcampPlugin +from beetsplug.bandcamp.metaguru import Metaguru + if TYPE_CHECKING: from _pytest.config import Config from _pytest.fixtures import FixtureRequest @@ -303,7 +304,10 @@ def get_tracks(data: JSONDict) -> List[Tuple[str, ...]]: else: artist, title = new["artist"], new["title"] - return f"{make_difftext(guru.original_albumartist, artist)} - {make_difftext(guru.original_album, title)}" + return ( + f"{make_difftext(guru.original_albumartist, artist)} - " + f"{make_difftext(guru.original_album, title)}" + ) @pytest.fixture diff --git a/tests/test_plugin.py b/tests/test_plugin.py index ce8ebc6..9e8c14b 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -135,6 +135,7 @@ def test_singleton_candidates(plugin, expected_release): assert candidates == [expected_release] + def test_bandcamp_plugin_name(): assert BandcampPlugin().data_source == "bandcamp" @@ -174,9 +175,11 @@ def test_no_coverart_empty_response(monkeypatch, bandcamp_item, beets_config): "empty", json.dumps({"@id": "", "image": "someurl"}), # no tracks json.dumps({"@id": "", "track": [], "image": "someurl"}), # no label - json.dumps( - {"@id": "", "track": [], "publisher": {"name": "Label"}} - ), # missing image + json.dumps({ + "@id": "", + "track": [], + "publisher": {"name": "Label"}, + }), # missing image ), ) def test_no_coverart_bad_html(monkeypatch, html, bandcamp_item, beets_config): diff --git a/tests/test_real_queries.py b/tests/test_real_queries.py index 6ee0644..ec84bbb 100644 --- a/tests/test_real_queries.py +++ b/tests/test_real_queries.py @@ -1,4 +1,5 @@ """End to end tests aimed at catching issues with html updates on bandcamp side.""" + import pytest from beetsplug.bandcamp import BandcampPlugin diff --git a/tests/test_search.py b/tests/test_search.py index ae9ef6a..3b7433d 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -1,4 +1,5 @@ """Tests for searching functionality.""" + import pytest from beetsplug.bandcamp.search import get_matches, parse_and_sort_results