diff --git a/src/music_kraken/pages/abstract.py b/src/music_kraken/pages/abstract.py index 1cb8421..15055a4 100644 --- a/src/music_kraken/pages/abstract.py +++ b/src/music_kraken/pages/abstract.py @@ -3,6 +3,8 @@ from typing import Optional, Union, Type, Dict, List from bs4 import BeautifulSoup import requests import logging +from dataclasses import dataclass +from copy import copy from ..utils import shared from ..objects import ( @@ -19,9 +21,34 @@ from ..objects import ( Label ) from ..tagging import write_metadata_to_target +from ..utils.shared import DOWNLOAD_PATH, DOWNLOAD_FILE, DEFAULT_VALUES LOGGER = logging.getLogger("this shouldn't be used") +@dataclass +class DefaultTarget: + genre: str = DEFAULT_VALUES["genre"] + label: str = DEFAULT_VALUES["label"] + artist: str = DEFAULT_VALUES["artist"] + album: str = DEFAULT_VALUES["album"] + song: str = DEFAULT_VALUES["song"] + + def __setattr__(self, __name: str, __value: str) -> None: + if __name in DEFAULT_VALUES: + if self.__getattribute__(__name) == DEFAULT_VALUES[__name]: + super().__setattr__(__name, __value) + return + + super().__setattr__(__name, __value) + + @property + def target(self) -> Target: + return Target( + relative_to_music_dir=True, + path=DOWNLOAD_PATH.format(genre=self.genre, label=self.label, artist=self.artist, album=self.album, song=self.song), + file=DOWNLOAD_FILE.format(genre=self.genre, label=self.label, artist=self.artist, album=self.album, song=self.song) + ) + class Page: """ @@ -283,56 +310,80 @@ class Page: cls._clean_collection(song.main_artist_collection, collections) @classmethod - def download(cls, music_object: Union[Song, Album, Artist, Label], download_features: bool = True): - print("downloading") - print(music_object) + def download( + cls, + music_object: Union[Song, Album, Artist, Label], + download_features: bool = True, + default_target: DefaultTarget = None + ): + if default_target is None: + default_target = DefaultTarget() + if type(music_object) is Song: - return cls.download_song(music_object) + return cls.download_song(music_object, default_target) if type(music_object) is Album: - return cls.download_album(music_object) + return cls.download_album(music_object, default_target) if type(music_object) is Artist: - return cls.download_artist(music_object, download_features=download_features) + return cls.download_artist(music_object, default_target) if type(music_object) is Label: - return cls.download_label(music_object, download_features=download_features) + return cls.download_label(music_object, download_features=download_features, default_target=default_target) @classmethod - def download_label(cls, label: Label, download_features: bool = True, override_existing: bool = False): + def download_label(cls, label: Label, download_features: bool = True, override_existing: bool = False, default_target: DefaultTarget = None): + if default_target is None: + default_target = DefaultTarget() + else: + default_target = copy(default_target) + default_target.label = label.name + cls.fetch_details(label) for artist in label.current_artist_collection: - cls.download_artist(artist, download_features=download_features, override_existing=override_existing) + cls.download_artist(artist, download_features=download_features, override_existing=override_existing, default_target=default_target) for album in label.album_collection: - cls.download_album(album, override_existing=override_existing) + cls.download_album(album, override_existing=override_existing, default_target=default_target) @classmethod - def download_artist(cls, artist: Artist, download_features: bool = True, override_existing: bool = False): + def download_artist(cls, artist: Artist, download_features: bool = True, override_existing: bool = False, default_target: DefaultTarget = None): + if default_target is None: + default_target = DefaultTarget() + else: + default_target = copy(default_target) + default_target.artist = artist.name + cls.fetch_details(artist) for album in artist.main_album_collection: - cls.download_album(album, override_existing=override_existing) + cls.download_album(album, override_existing=override_existing, default_target=default_target) if download_features: for song in artist.feature_album.song_collection: - cls.download_song(song, override_existing=override_existing) + cls.download_song(song, override_existing=override_existing, default_target=default_target) @classmethod - def download_album(cls, album: Album, override_existing: bool = False): + def download_album(cls, album: Album, override_existing: bool = False, default_target: DefaultTarget = None): + if default_target is None: + default_target = DefaultTarget() + else: + default_target = copy(default_target) + default_target.album = album.title + cls.fetch_details(album) for song in album.song_collection: - cls.download_song(song, override_existing=override_existing) + cls.download_song(song, override_existing=override_existing, default_target=default_target) @classmethod - def download_song(cls, song: Song, override_existing: bool = False, create_target_on_demand: bool = True): + def download_song(cls, song: Song, override_existing: bool = False, create_target_on_demand: bool = True, default_target: DefaultTarget = None): + if default_target is None: + default_target = DefaultTarget() + else: + default_target = copy(default_target) + default_target.song = song.title + cls.fetch_details(song) if song.target_collection.empty: if create_target_on_demand and not song.main_artist_collection.empty and not song.album_collection.empty: - song.target_collection.append( - Target( - file=f"{song.title}.mp3", - relative_to_music_dir=True, - path=f"{song.main_artist_collection[0].name}/{song.album_collection[0].title}" - ) - ) + song.target_collection.append(default_target.target) else: return diff --git a/src/music_kraken/pages/musify.py b/src/music_kraken/pages/musify.py index 0f8d71a..5a81d11 100644 --- a/src/music_kraken/pages/musify.py +++ b/src/music_kraken/pages/musify.py @@ -10,8 +10,7 @@ from pathlib import Path import random from ..utils.shared import ( - ENCYCLOPAEDIA_METALLUM_LOGGER as LOGGER, - TEMP_FOLDER + ENCYCLOPAEDIA_METALLUM_LOGGER as LOGGER ) from .abstract import Page @@ -864,7 +863,7 @@ class Musify(Page): :param source: :return: """ - album = Album(title="Hi :)") + album = Album(title="Hi :)", source_list=[source]) url = cls.parse_url(source.url) @@ -881,6 +880,14 @@ class Musify(Page): card_soup: BeautifulSoup for card_soup in cards_soup.find_all("div", {"class": "playlist__item"}): album.song_collection.append(cls.parse_song_card(card_soup)) + + if stop_at_level > 1: + song: Song + for song in album.song_collection: + sources = song.source_collection.get_sources_from_page(cls.SOURCE_TYPE) + for source in sources: + song.merge(cls._fetch_song_from_source(source=source)) + album.update_tracksort() return album diff --git a/src/music_kraken/utils/shared.py b/src/music_kraken/utils/shared.py index f8ea941..ecd8edc 100644 --- a/src/music_kraken/utils/shared.py +++ b/src/music_kraken/utils/shared.py @@ -4,18 +4,13 @@ import tempfile import os import configparser from sys import platform as current_os +from pathlib import Path -TEMP_FOLDER = "music-downloader" LOG_FILE = "download_logs.log" TEMP_DATABASE_FILE = "metadata.db" -DATABASE_STRUCTURE_FILE = "database_structure.sql" -DATABASE_STRUCTURE_FALLBACK = "https://raw.githubusercontent.com/HeIIow2/music-downloader/master/assets/database_structure.sql" -TEMP_DIR = os.path.join(tempfile.gettempdir(), TEMP_FOLDER) -if not os.path.exists(TEMP_DIR): - os.mkdir(TEMP_DIR) - -TEMP_DATABASE_PATH = os.path.join(TEMP_DIR, TEMP_DATABASE_FILE) +TEMP_DIR = Path(tempfile.gettempdir(), "music-downloader") +TEMP_DIR.mkdir(exist_ok=True) # configure logger default logging.basicConfig( @@ -33,18 +28,17 @@ INIT_PATH_LOGGER = logging.getLogger("init_path") DATABASE_LOGGER = logging.getLogger("database") METADATA_DOWNLOAD_LOGGER = logging.getLogger("metadata") URL_DOWNLOAD_LOGGER = logging.getLogger("AudioSource") +TAGGING_LOGGER = logging.getLogger("tagging") YOUTUBE_LOGGER = logging.getLogger("Youtube") MUSIFY_LOGGER = logging.getLogger("Musify") PATH_LOGGER = logging.getLogger("create-paths") DOWNLOAD_LOGGER = logging.getLogger("download") LYRICS_LOGGER = logging.getLogger("lyrics") GENIUS_LOGGER = logging.getLogger("genius") -TAGGING_LOGGER = logging.getLogger("tagging") - ENCYCLOPAEDIA_METALLUM_LOGGER = logging.getLogger("ma") NOT_A_GENRE = ".", "..", "misc_scripts", "Music", "script", ".git", ".idea" -MUSIC_DIR = os.path.join(os.path.expanduser("~"), "Music") +MUSIC_DIR = Path(os.path.expanduser("~"), "Music") if current_os == "linux": # XDG_USER_DIRS_FILE reference: https://freedesktop.org/wiki/Software/xdg-user-dirs/ @@ -58,17 +52,34 @@ if current_os == "linux": config.read_string(data) xdg_config = config['XDG_USER_DIRS'] MUSIC_DIR = os.path.expandvars(xdg_config['xdg_music_dir'].strip('"')) + except (FileNotFoundError, KeyError) as E: - logger.warning(f''' -Missing file or No entry found for "xdg_music_dir" in: \'{XDG_USER_DIRS_FILE}\'. -Will fallback on default '$HOME/Music'. ----- - ''') + logger.warning( + f"Missing file or No entry found for \"xdg_music_dir\" in: \"{XDG_USER_DIRS_FILE}\".\n" \ + f"Will fallback on default \"$HOME/Music\"." + ) + TOR = False proxies = { 'http': 'socks5h://127.0.0.1:9150', 'https': 'socks5h://127.0.0.1:9150' } if TOR else {} -# only the sources here will get downloaded, in the order the list is ordered -AUDIO_SOURCES = ["Musify", "Youtube"] + +""" +available variables: +- genre +- label +- artist +- album +- song +""" +DOWNLOAD_PATH = "{genre}/{artist}/{album}" +DOWNLOAD_FILE = "{song}.mp3" +DEFAULT_VALUES = { + "genre": "Various Genre", + "label": "Various Labels", + "artist": "Various Artists", + "album": "Various Album", + "song": "Various Song", +}