From 7d23ecac066dd96f83080f15f526920ed7be0a16 Mon Sep 17 00:00:00 2001 From: Kur01234 Date: Wed, 5 Jun 2024 08:34:37 +0200 Subject: [PATCH] feat: bandcamp artist artwork --- music_kraken/download/page_attributes.py | 81 +++++++++++++++++-- music_kraken/objects/artwork.py | 6 +- music_kraken/pages/bandcamp.py | 6 +- music_kraken/utils/config/__init__.py | 7 +- .../utils/config/config_files/main_config.py | 6 +- 5 files changed, 89 insertions(+), 17 deletions(-) diff --git a/music_kraken/download/page_attributes.py b/music_kraken/download/page_attributes.py index 1db24be..eba3274 100644 --- a/music_kraken/download/page_attributes.py +++ b/music_kraken/download/page_attributes.py @@ -4,6 +4,8 @@ from pathlib import Path import re import logging +from PIL import Image + from . import FetchOptions, DownloadOptions from .results import SearchResults from ..objects import ( @@ -17,6 +19,7 @@ from ..objects import ( Artist, Label, ) +from ..objects.artwork import ArtworkVariant from ..audio import write_metadata_to_target, correct_codec from ..utils import output, BColors from ..utils.string_processing import fit_to_file_system @@ -29,9 +32,11 @@ from ..utils.support_classes.download_result import DownloadResult from ..utils.exception import MKMissingNameException from ..utils.exception.download import UrlNotFoundException from ..utils.shared import DEBUG_PAGES +from ..connection import Connection from ..pages import Page, EncyclopaediaMetallum, Musify, YouTube, YoutubeMusic, Bandcamp, Genius, INDEPENDENT_DB_OBJECTS +artwork_connection: Connection = Connection() ALL_PAGES: Set[Type[Page]] = { # EncyclopaediaMetallum, @@ -162,11 +167,64 @@ class Pages: return False + def download_artwork_variant_to_target(self, artwork_variant: ArtworkVariant, target: Target): + + r = artwork_connection.get( + url=artwork_variant["url"], + name=artwork_variant["url"], + ) + + temp_target: Target = Target.temp() + with temp_target.open("wb") as f: + f.write(r.content) + + converted_target: Target = Target.temp(file_extension=main_settings["image_format"]) + with Image.open(temp_target.file_path) as img: + # crop the image if it isn't square in the middle with minimum data loss + width, height = img.size + if width != height: + if width > height: + img = img.crop((width // 2 - height // 2, 0, width // 2 + height // 2, height)) + else: + img = img.crop((0, height // 2 - width // 2, width, height // 2 + width // 2)) + + # resize the image to the preferred resolution + img.thumbnail((main_settings["preferred_artwork_resolution"], main_settings["preferred_artwork_resolution"])) + + # https://stackoverflow.com/a/59476938/16804841 + if img.mode != 'RGB': + img = img.convert('RGB') + + img.save(converted_target.file_path, main_settings["image_format"]) + + + + def _fetch_artist_artwork(self, artist: Artist, naming: dict): + naming: Dict[str, List[str]] = defaultdict(list, naming) + naming["artist"].append(artist.name) + naming["label"].extend([l.title_value for l in artist.label_collection]) + # removing duplicates from the naming, and process the strings + for key, value in naming.items(): + # https://stackoverflow.com/a/17016257 + naming[key] = list(dict.fromkeys(value)) + + artwork: Artwork = artist.artwork + for image_number, variant in enumerate(artwork): + naming["image_number"] = [str(image_number)] + + url: str = variant["url"] + + target = Target( + relative_to_music_dir=True, + file_path=Path(self._parse_path_template(main_settings["artist_artwork_path"], naming=naming)) + ) + self.download_artwork_variant_to_target(variant, target) + def download(self, data_object: DataObject, genre: str, **kwargs) -> DownloadResult: # fetch the given object self.fetch_details(data_object) output(f"\nDownloading {data_object.option_string}...", color=BColors.BOLD) - + # fetching all parent objects (e.g. if you only download a song) if not kwargs.get("fetched_upwards", False): to_fetch: List[DataObject] = [data_object] @@ -185,7 +243,17 @@ class Pages: to_fetch = new_to_fetch kwargs["fetched_upwards"] = True - + + naming = kwargs.get("naming", { + "genre": [genre], + "audio_format": [main_settings["audio_format"]], + "image_format": [main_settings["image_format"]] + }) + + # download artist artwork + if isinstance(data_object, Artist): + self._fetch_artist_artwork(artist=data_object, naming=naming) + # download all children download_result: DownloadResult = DownloadResult() for c in data_object.get_child_collections(): @@ -203,10 +271,7 @@ class Pages: I am able to do that, because duplicate values are removed later on. """ - self._download_song(data_object, naming={ - "genre": [genre], - "audio_format": [main_settings["audio_format"]], - }) + self._download_song(data_object, naming=naming) return download_result @@ -325,4 +390,6 @@ class Pages: _actual_page = self._source_to_page[source.source_type] - return _actual_page, self._page_instances[_actual_page].fetch_object_from_source(source=source, stop_at_level=stop_at_level) \ No newline at end of file + return _actual_page, self._page_instances[_actual_page].fetch_object_from_source(source=source, stop_at_level=stop_at_level) + + \ No newline at end of file diff --git a/music_kraken/objects/artwork.py b/music_kraken/objects/artwork.py index 1ff9edc..6b5c096 100644 --- a/music_kraken/objects/artwork.py +++ b/music_kraken/objects/artwork.py @@ -10,7 +10,6 @@ from .metadata import Mapping as id3Mapping from .metadata import Metadata from .parents import OuterProxy as Base - class ArtworkVariant(TypedDict): url: str width: int @@ -76,3 +75,8 @@ class Artwork: if not isinstance(other, Artwork): return False return any(a == b for a, b in zip(self._variant_mapping.keys(), other._variant_mapping.keys())) + + def __iter__(self) -> Generator[ArtworkVariant, None, None]: + yield from self._variant_mapping.values() + + \ No newline at end of file diff --git a/music_kraken/pages/bandcamp.py b/music_kraken/pages/bandcamp.py index 1caf803..0eae4ac 100644 --- a/music_kraken/pages/bandcamp.py +++ b/music_kraken/pages/bandcamp.py @@ -239,6 +239,11 @@ class Bandcamp(Page): for subsoup in html_music_grid.find_all("li"): artist.album_collection.append(self._parse_album(soup=subsoup, initial_source=source)) + # artist artwork + artist_artwork: BeautifulSoup = soup.find("img", {"class":"band-photo"}) + if artist_artwork is not None: + artist.artwork.append(artist_artwork.get("data-src", artist_artwork.get("src"))) + for i, data_blob_soup in enumerate(soup.find_all("div", {"id": ["pagedata", "collectors-data"]})): data_blob = data_blob_soup["data-blob"] @@ -316,7 +321,6 @@ class Bandcamp(Page): artwork.append(url=_artwork_url, width=350, height=350) break - for i, track_json in enumerate(data.get("track", {}).get("itemListElement", [])): if DEBUG: dump_to_file(f"album_track_{i}.json", json.dumps(track_json), is_json=True, exit_after_dump=False) diff --git a/music_kraken/utils/config/__init__.py b/music_kraken/utils/config/__init__.py index e1def0a..2543d8a 100644 --- a/music_kraken/utils/config/__init__.py +++ b/music_kraken/utils/config/__init__.py @@ -1,11 +1,8 @@ from typing import Tuple from .config import Config -from .config_files import ( - main_config, - logging_config, - youtube_config, -) +from .config_files import main_config, logging_config, youtube_config + _sections: Tuple[Config, ...] = ( main_config.config, diff --git a/music_kraken/utils/config/config_files/main_config.py b/music_kraken/utils/config/config_files/main_config.py index 8ed3ef1..a7b2ae9 100644 --- a/music_kraken/utils/config/config_files/main_config.py +++ b/music_kraken/utils/config/config_files/main_config.py @@ -18,7 +18,7 @@ config = Config(( AudioFormatAttribute(name="audio_format", default_value="mp3", description="""Music Kraken will stream the audio into this format. You can use Audio formats which support ID3.2 and ID3.1, but you will have cleaner Metadata using ID3.2."""), - Attribute(name="image_format", default_value="jpg", description="This Changes the format in which images are getting downloaded") + Attribute(name="image_format", default_value="jpeg", description="This Changes the format in which images are getting downloaded"), Attribute(name="result_history", default_value=True, description="""If enabled, you can go back to the previous results. The consequence is a higher meory consumption, because every result is saved."""), @@ -29,7 +29,7 @@ The further you choose to be able to go back, the higher the memory usage. EmptyLine(), Attribute(name="preferred_artwork_resolution", default_value=1000), - Attribute(name="download_artist_artworks", default_value=True, description="Changes if the artists Profile picture is being downloaded."), + Attribute(name="download_artist_artworks", default_value=True, description="Enables the fetching of artist galleries."), EmptyLine(), @@ -46,7 +46,7 @@ This means for example, the Studio Albums and EP's are always in front of Single - album_type The folder music kraken should put the songs into."""), Attribute(name="download_file", default_value="{song}.{audio_format}", description="The filename of the audio file."), - Attribute(name="artist_artwork_path" default_value="{genre}/{artist}/{artist}.{image_format}", description="The Path to download artist images to."), + Attribute(name="artist_artwork_path", default_value="{genre}/{artist}/{artist}_{image_number}.{image_format}", description="The Path to download artist images to."), SelectAttribute(name="album_type_blacklist", default_value=[ "Compilation Album", "Live Album",