feat: bandcamp artist artwork
This commit is contained in:
		| @@ -4,6 +4,8 @@ from pathlib import Path | |||||||
| import re | import re | ||||||
| import logging | import logging | ||||||
|  |  | ||||||
|  | from PIL import Image | ||||||
|  |  | ||||||
| from . import FetchOptions, DownloadOptions | from . import FetchOptions, DownloadOptions | ||||||
| from .results import SearchResults | from .results import SearchResults | ||||||
| from ..objects import ( | from ..objects import ( | ||||||
| @@ -17,6 +19,7 @@ from ..objects import ( | |||||||
|     Artist, |     Artist, | ||||||
|     Label, |     Label, | ||||||
| ) | ) | ||||||
|  | from ..objects.artwork import ArtworkVariant | ||||||
| from ..audio import write_metadata_to_target, correct_codec | from ..audio import write_metadata_to_target, correct_codec | ||||||
| from ..utils import output, BColors | from ..utils import output, BColors | ||||||
| from ..utils.string_processing import fit_to_file_system | 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 import MKMissingNameException | ||||||
| from ..utils.exception.download import UrlNotFoundException | from ..utils.exception.download import UrlNotFoundException | ||||||
| from ..utils.shared import DEBUG_PAGES | from ..utils.shared import DEBUG_PAGES | ||||||
|  | from ..connection import Connection | ||||||
|  |  | ||||||
| from ..pages import Page, EncyclopaediaMetallum, Musify, YouTube, YoutubeMusic, Bandcamp, Genius, INDEPENDENT_DB_OBJECTS | from ..pages import Page, EncyclopaediaMetallum, Musify, YouTube, YoutubeMusic, Bandcamp, Genius, INDEPENDENT_DB_OBJECTS | ||||||
|  |  | ||||||
|  | artwork_connection: Connection = Connection() | ||||||
|  |  | ||||||
| ALL_PAGES: Set[Type[Page]] = { | ALL_PAGES: Set[Type[Page]] = { | ||||||
|     # EncyclopaediaMetallum, |     # EncyclopaediaMetallum, | ||||||
| @@ -162,6 +167,59 @@ class Pages: | |||||||
|          |          | ||||||
|         return False |         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: |     def download(self, data_object: DataObject, genre: str, **kwargs) -> DownloadResult: | ||||||
|         # fetch the given object |         # fetch the given object | ||||||
|         self.fetch_details(data_object) |         self.fetch_details(data_object) | ||||||
| @@ -186,6 +244,16 @@ class Pages: | |||||||
|              |              | ||||||
|             kwargs["fetched_upwards"] = True |             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 all children | ||||||
|         download_result: DownloadResult = DownloadResult() |         download_result: DownloadResult = DownloadResult() | ||||||
|         for c in data_object.get_child_collections(): |         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. |             I am able to do that, because duplicate values are removed later on. | ||||||
|             """ |             """ | ||||||
|  |  | ||||||
|             self._download_song(data_object, naming={ |             self._download_song(data_object, naming=naming) | ||||||
|                 "genre": [genre], |  | ||||||
|                 "audio_format": [main_settings["audio_format"]], |  | ||||||
|             }) |  | ||||||
|  |  | ||||||
|         return download_result |         return download_result | ||||||
|  |  | ||||||
| @@ -326,3 +391,5 @@ class Pages: | |||||||
|         _actual_page = self._source_to_page[source.source_type] |         _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) |         return _actual_page, self._page_instances[_actual_page].fetch_object_from_source(source=source, stop_at_level=stop_at_level) | ||||||
|  |  | ||||||
|  |      | ||||||
| @@ -10,7 +10,6 @@ from .metadata import Mapping as id3Mapping | |||||||
| from .metadata import Metadata | from .metadata import Metadata | ||||||
| from .parents import OuterProxy as Base | from .parents import OuterProxy as Base | ||||||
|  |  | ||||||
|  |  | ||||||
| class ArtworkVariant(TypedDict): | class ArtworkVariant(TypedDict): | ||||||
|     url: str |     url: str | ||||||
|     width: int |     width: int | ||||||
| @@ -76,3 +75,8 @@ class Artwork: | |||||||
|         if not isinstance(other, Artwork): |         if not isinstance(other, Artwork): | ||||||
|             return False |             return False | ||||||
|         return any(a == b for a, b in zip(self._variant_mapping.keys(), other._variant_mapping.keys())) |         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() | ||||||
|  |  | ||||||
|  |      | ||||||
| @@ -239,6 +239,11 @@ class Bandcamp(Page): | |||||||
|             for subsoup in html_music_grid.find_all("li"): |             for subsoup in html_music_grid.find_all("li"): | ||||||
|                 artist.album_collection.append(self._parse_album(soup=subsoup, initial_source=source)) |                 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"]})): |         for i, data_blob_soup in enumerate(soup.find_all("div", {"id": ["pagedata", "collectors-data"]})): | ||||||
|             data_blob = data_blob_soup["data-blob"] |             data_blob = data_blob_soup["data-blob"] | ||||||
|  |  | ||||||
| @@ -316,7 +321,6 @@ class Bandcamp(Page): | |||||||
|                     artwork.append(url=_artwork_url, width=350, height=350) |                     artwork.append(url=_artwork_url, width=350, height=350) | ||||||
|                     break |                     break | ||||||
|  |  | ||||||
|  |  | ||||||
|         for i, track_json in enumerate(data.get("track", {}).get("itemListElement", [])): |         for i, track_json in enumerate(data.get("track", {}).get("itemListElement", [])): | ||||||
|             if DEBUG: |             if DEBUG: | ||||||
|                 dump_to_file(f"album_track_{i}.json", json.dumps(track_json), is_json=True, exit_after_dump=False) |                 dump_to_file(f"album_track_{i}.json", json.dumps(track_json), is_json=True, exit_after_dump=False) | ||||||
|   | |||||||
| @@ -1,11 +1,8 @@ | |||||||
| from typing import Tuple | from typing import Tuple | ||||||
|  |  | ||||||
| from .config import Config | from .config import Config | ||||||
| from .config_files import ( | from .config_files import main_config, logging_config, youtube_config | ||||||
|     main_config, |  | ||||||
|     logging_config, |  | ||||||
|     youtube_config, |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| _sections: Tuple[Config, ...] = ( | _sections: Tuple[Config, ...] = ( | ||||||
|     main_config.config, |     main_config.config, | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ config = Config(( | |||||||
|     AudioFormatAttribute(name="audio_format", default_value="mp3", description="""Music Kraken will stream the audio into this format. |     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, | You can use Audio formats which support ID3.2 and ID3.1, | ||||||
| but you will have cleaner Metadata using ID3.2."""), | 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. |     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."""), | 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(), |     EmptyLine(), | ||||||
|  |  | ||||||
|     Attribute(name="preferred_artwork_resolution", default_value=1000), |     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(), |     EmptyLine(), | ||||||
|  |  | ||||||
| @@ -46,7 +46,7 @@ This means for example, the Studio Albums and EP's are always in front of Single | |||||||
| - album_type | - album_type | ||||||
| The folder music kraken should put the songs into."""), | 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="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=[ |     SelectAttribute(name="album_type_blacklist", default_value=[ | ||||||
|         "Compilation Album", |         "Compilation Album", | ||||||
|         "Live Album", |         "Live Album", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user