diff --git a/.vscode/settings.json b/.vscode/settings.json index 2108a96..a8c503e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "APIC", "Bandcamp", "bitrate", + "DEEZER", "dotenv", "encyclopaedia", "ENDC", @@ -27,6 +28,7 @@ "isrc", "levenshtein", "metallum", + "MUSICBRAINZ", "musify", "OKBLUE", "OKGREEN", diff --git a/music_kraken/download/__init__.py b/music_kraken/download/__init__.py index 05fb122..a52bd87 100644 --- a/music_kraken/download/__init__.py +++ b/music_kraken/download/__init__.py @@ -15,5 +15,6 @@ class DownloadOptions: download_all: bool = False album_type_blacklist: Set[AlbumType] = field(default_factory=lambda: set(AlbumType(a) for a in main_settings["album_type_blacklist"])) + download_again_if_found: bool = False process_audio_if_found: bool = False process_metadata_if_found: bool = True diff --git a/music_kraken/download/page_attributes.py b/music_kraken/download/page_attributes.py index 13d6ee8..8d9bb42 100644 --- a/music_kraken/download/page_attributes.py +++ b/music_kraken/download/page_attributes.py @@ -1,17 +1,29 @@ from typing import Tuple, Type, Dict, Set, Optional, List from collections import defaultdict from pathlib import Path +import re from . import FetchOptions, DownloadOptions from .results import SearchResults -from ..objects import DatabaseObject as DataObject, Source, Album, Song, Artist, Label - +from ..objects import ( + DatabaseObject as DataObject, + Collection, + Target, + Source, + Options, + Song, + Album, + Artist, + Label, +) from ..utils.string_processing import fit_to_file_system -from ..utils.config import youtube_settings -from ..utils.enums.source import SourcePages +from ..utils.config import youtube_settings, main_settings +from ..utils.path_manager import LOCATIONS +from ..utils.enums import SourceType from ..utils.support_classes.download_result import DownloadResult from ..utils.support_classes.query import Query 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 @@ -61,7 +73,7 @@ class Pages: # initialize all page instances self._page_instances: Dict[Type[Page], Page] = dict() - self._source_to_page: Dict[SourcePages, Type[Page]] = dict() + self._source_to_page: Dict[SourceType, Type[Page]] = dict() exclude_pages = exclude_pages if exclude_pages is not None else set() @@ -84,7 +96,7 @@ class Pages: self._page_instances[page_type] = page_type(fetch_options=self.fetch_options, download_options=self.download_options) self._source_to_page[page_type.SOURCE_TYPE] = page_type - def _get_page_from_enum(self, source_page: SourcePages) -> Page: + def _get_page_from_enum(self, source_page: SourceType) -> Page: if source_page not in self._source_to_page: return None return self._page_instances[self._source_to_page[source_page]] @@ -113,7 +125,7 @@ class Pages: return data_object def fetch_from_source(self, source: Source, **kwargs) -> Optional[DataObject]: - page: Page = self._get_page_from_enum(source.page_enum) + page: Page = self._get_page_from_enum(source.source_type) if page is None: return None @@ -129,7 +141,7 @@ class Pages: return data_object def fetch_from_url(self, url: str) -> Optional[DataObject]: - source = Source.match_url(url, SourcePages.MANUAL) + source = Source.match_url(url, SourceType.MANUAL) if source is None: return None @@ -198,13 +210,39 @@ class Pages: return download_result + def _extract_fields_from_template(self, path_template: str) -> Set[str]: + return set(re.findall(r"{([^}]+)}", path_template)) + + def _parse_path_template(self, path_template: str, naming: Dict[str, List[str]]) -> str: + field_names: Set[str] = self._extract_fields_from_template(path_template) + + for field in field_names: + if len(naming[field]) == 0: + raise MKMissingNameException(f"Missing field for {field}.") + + path_template = path_template.replace(f"{{{field}}}", naming[field][0]) + + return possible_parts + + def _get_pages_with_source(self, data_object: DataObject, sort_by_attribute: str = "DOWNLOAD_PRIORITY") -> List[Page]: + pages = [self._get_page_from_enum(s.source_type) for s in data_object.source_collection.get_sources()] + pages.sort(key=lambda p: getattr(p, sort_by_attribute), reverse=True) + return list(pages) + def _download_song(self, song: Song, naming: dict) -> DownloadOptions: + """ + TODO + Search the song in the file system. + """ + r = DownloadResult(total=1) + # pre process the data recursively song.compile() # manage the naming naming: Dict[str, List[str]] = defaultdict(list, naming) naming["song"].append(song.title_string) + naming["genre"].append(song.genre) naming["isrc"].append(song.isrc) naming["album"].extend(a.title_string for a in song.album_collection) naming["album_type"].extend(a.album_type.value for a in song.album_collection) @@ -216,21 +254,41 @@ class Pages: for key, value in naming.items(): # https://stackoverflow.com/a/17016257 naming[key] = list(dict.fromkeys(items)) - naming[key] = [fit_to_file_system(i) for i in naming[key] if i is not None] - # get every possible path - path_format = Path(main_settings["download_path"], main_settings["download_file"]) - for part in path_format.parts: - pass + # manage the targets + tmp: Target = Target.temp(file_extension=main_settings["audio_format"]) + found_on_disc = False - return DownloadOptions() + song.target_collection.append(Target( + relative_to_music_dir=True, + file_path=Path( + self._parse_path_template(main_settings["download_path"], naming=naming), + self._parse_path_template(main_settings["download_file"], naming=naming), + ) + )) + for target in song.target_collection: + if target.exists(): + output(f'- {target.file_path} {BColors.OKGREEN.value}[already exists]', color=BColors.GREY) + + found_on_disc = True + r.found_on_disk += 1 + target.copy_content(tmp) + else: + target.create_parent_directories() + output(f'- {target.file_path}', color=BColors.GREY) + + # actually download + for page in self._get_pages_with_source(song, sort_by_attribute="DOWNLOAD_PRIORITY"): + r = page.download_song_to_target(song, tmp, r) + + return r def fetch_url(self, url: str, stop_at_level: int = 2) -> Tuple[Type[Page], DataObject]: - source = Source.match_url(url, SourcePages.MANUAL) + source = Source.match_url(url, SourceType.MANUAL) if source is None: raise UrlNotFoundException(url=url) - _actual_page = self._source_to_page[source.page_enum] + _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 diff --git a/music_kraken/download/results.py b/music_kraken/download/results.py index a8fead7..00afea9 100644 --- a/music_kraken/download/results.py +++ b/music_kraken/download/results.py @@ -2,7 +2,7 @@ from typing import Tuple, Type, Dict, List, Generator, Union from dataclasses import dataclass from ..objects import DatabaseObject -from ..utils.enums.source import SourcePages +from ..utils.enums.source import SourceType from ..pages import Page, EncyclopaediaMetallum, Musify diff --git a/music_kraken/objects/__init__.py b/music_kraken/objects/__init__.py index 0504353..7c7515d 100644 --- a/music_kraken/objects/__init__.py +++ b/music_kraken/objects/__init__.py @@ -3,7 +3,7 @@ from .option import Options from .metadata import Metadata, Mapping as ID3Mapping, ID3Timestamp -from .source import Source, SourcePages, SourceTypes +from .source import Source, SourceType from .song import ( Song, diff --git a/music_kraken/objects/source.py b/music_kraken/objects/source.py index ff68d6a..0385bca 100644 --- a/music_kraken/objects/source.py +++ b/music_kraken/objects/source.py @@ -8,7 +8,7 @@ from dataclasses import dataclass, field from functools import cached_property from ..utils import generate_id -from ..utils.enums.source import SourcePages, SourceTypes +from ..utils.enums import SourceType from ..utils.config import youtube_settings from ..utils.string_processing import hash_url, shorten_display_url @@ -20,22 +20,22 @@ from .collection import Collection @dataclass class Source: - page_enum: SourcePages + source_type: SourceType url: str - referrer_page: SourcePages = None + referrer_page: SourceType = None audio_url: Optional[str] = None additional_data: dict = field(default_factory=dict) def __post_init__(self): - self.referrer_page = self.referrer_page or self.page_enum + self.referrer_page = self.referrer_page or self.source_type @property def parsed_url(self) -> ParseResult: return urlparse(self.url) @classmethod - def match_url(cls, url: str, referrer_page: SourcePages) -> Optional[Source]: + def match_url(cls, url: str, referrer_page: SourceType) -> Optional[Source]: """ this shouldn't be used, unless you are not certain what the source is for the reason is that it is more inefficient @@ -44,38 +44,38 @@ class Source: url = parsed_url.geturl() if "musify" in parsed_url.netloc: - return cls(SourcePages.MUSIFY, url, referrer_page=referrer_page) + return cls(SourceType.MUSIFY, url, referrer_page=referrer_page) if parsed_url.netloc in [_url.netloc for _url in youtube_settings['youtube_url']]: - return cls(SourcePages.YOUTUBE, url, referrer_page=referrer_page) + return cls(SourceType.YOUTUBE, url, referrer_page=referrer_page) if url.startswith("https://www.deezer"): - return cls(SourcePages.DEEZER, url, referrer_page=referrer_page) + return cls(SourceType.DEEZER, url, referrer_page=referrer_page) if url.startswith("https://open.spotify.com"): - return cls(SourcePages.SPOTIFY, url, referrer_page=referrer_page) + return cls(SourceType.SPOTIFY, url, referrer_page=referrer_page) if "bandcamp" in url: - return cls(SourcePages.BANDCAMP, url, referrer_page=referrer_page) + return cls(SourceType.BANDCAMP, url, referrer_page=referrer_page) if "wikipedia" in parsed_url.netloc: - return cls(SourcePages.WIKIPEDIA, url, referrer_page=referrer_page) + return cls(SourceType.WIKIPEDIA, url, referrer_page=referrer_page) if url.startswith("https://www.metal-archives.com/"): - return cls(SourcePages.ENCYCLOPAEDIA_METALLUM, url, referrer_page=referrer_page) + return cls(SourceType.ENCYCLOPAEDIA_METALLUM, url, referrer_page=referrer_page) # the less important once if url.startswith("https://www.facebook"): - return cls(SourcePages.FACEBOOK, url, referrer_page=referrer_page) + return cls(SourceType.FACEBOOK, url, referrer_page=referrer_page) if url.startswith("https://www.instagram"): - return cls(SourcePages.INSTAGRAM, url, referrer_page=referrer_page) + return cls(SourceType.INSTAGRAM, url, referrer_page=referrer_page) if url.startswith("https://twitter"): - return cls(SourcePages.TWITTER, url, referrer_page=referrer_page) + return cls(SourceType.TWITTER, url, referrer_page=referrer_page) if url.startswith("https://myspace.com"): - return cls(SourcePages.MYSPACE, url, referrer_page=referrer_page) + return cls(SourceType.MYSPACE, url, referrer_page=referrer_page) @property def hash_url(self) -> str: @@ -89,21 +89,21 @@ class Source: return r def __repr__(self) -> str: - return f"Src({self.page_enum.value}: {shorten_display_url(self.url)})" + return f"Src({self.source_type.value}: {shorten_display_url(self.url)})" def __merge__(self, other: Source, **kwargs): if self.audio_url is None: self.audio_url = other.audio_url self.additional_data.update(other.additional_data) - page_str = property(fget=lambda self: self.page_enum.value) + page_str = property(fget=lambda self: self.source_type.value) class SourceCollection: __change_version__ = generate_id() _indexed_sources: Dict[str, Source] - _page_to_source_list: Dict[SourcePages, List[Source]] + _page_to_source_list: Dict[SourceType, List[Source]] def __init__(self, data: Optional[Iterable[Source]] = None, **kwargs): self._page_to_source_list = defaultdict(list) @@ -111,7 +111,7 @@ class SourceCollection: self.extend(data or []) - def has_source_page(self, *source_pages: SourcePages) -> bool: + def has_source_page(self, *source_pages: SourceType) -> bool: return any(source_page in self._page_to_source_list for source_page in source_pages) def get_sources(self, *source_pages: List[Source]) -> Generator[Source]: @@ -135,7 +135,7 @@ class SourceCollection: existing_source.__merge__(source) source = existing_source else: - self._page_to_source_list[source.page_enum].append(source) + self._page_to_source_list[source.source_type].append(source) changed = False for key in source.indexing_values: @@ -157,7 +157,7 @@ class SourceCollection: self.extend(other) @property - def source_pages(self) -> Iterable[SourcePages]: + def source_pages(self) -> Iterable[SourceType]: return sorted(self._page_to_source_list.keys(), key=lambda page: page.value) @property diff --git a/music_kraken/pages/abstract.py b/music_kraken/pages/abstract.py index 22e0a7d..2e0ae49 100644 --- a/music_kraken/pages/abstract.py +++ b/music_kraken/pages/abstract.py @@ -22,7 +22,7 @@ from ..objects import ( Collection, Label, ) -from ..utils.enums.source import SourcePages +from ..utils.enums import SourceType from ..utils.enums.album import AlbumType from ..audio import write_metadata_to_target, correct_codec from ..utils.config import main_settings @@ -47,72 +47,15 @@ class DownloadOptions: process_audio_if_found: bool = False process_metadata_if_found: bool = True -class NamingDict(dict): - CUSTOM_KEYS: Dict[str, str] = { - "label": "label.name", - "artist": "artist.name", - "song": "song.title", - "isrc": "song.isrc", - "album": "album.title", - "album_type": "album.album_type_string" - } - - def __init__(self, values: dict, object_mappings: Dict[str, DatabaseObject] = None): - self.object_mappings: Dict[str, DatabaseObject] = object_mappings or dict() - - super().__init__(values) - self["audio_format"] = main_settings["audio_format"] - - def add_object(self, music_object: DatabaseObject): - self.object_mappings[type(music_object).__name__.lower()] = music_object - - def copy(self) -> dict: - return type(self)(super().copy(), self.object_mappings.copy()) - - def __getitem__(self, key: str) -> str: - return fit_to_file_system(super().__getitem__(key)) - - def default_value_for_name(self, name: str) -> str: - return f'Various {name.replace("_", " ").title()}' - - def __missing__(self, key: str) -> str: - if "." not in key: - if key not in self.CUSTOM_KEYS: - return self.default_value_for_name(key) - - key = self.CUSTOM_KEYS[key] - - frag_list = key.split(".") - - object_name = frag_list[0].strip().lower() - attribute_name = frag_list[-1].strip().lower() - - if object_name not in self.object_mappings: - return self.default_value_for_name(attribute_name) - - music_object = self.object_mappings[object_name] - try: - value = getattr(music_object, attribute_name) - if value is None: - return self.default_value_for_name(attribute_name) - - return str(value) - - except AttributeError: - return self.default_value_for_name(attribute_name) - - class Page: - """ - This is an abstract class, laying out the - functionality for every other class fetching something - """ - DOWNLOAD_PRIORITY: int = 0 - SOURCE_TYPE: SourcePages - LOGGER = logging.getLogger("this shouldn't be used") + SOURCE_TYPE: SourceType + LOGGER: LOGGER - # set this to true, if all song details can also be fetched by fetching album details - NO_ADDITIONAL_DATA_FROM_SONG = False + def __new__(cls, *args, **kwargs): + cls.SOURCE_TYPE.register_page(cls) + cls.LOGGER = logging.getLogger(cls.__name__) + + return super().__new__(cls) def __init__(self, download_options: DownloadOptions = None, fetch_options: FetchOptions = None): self.download_options: DownloadOptions = download_options or DownloadOptions() diff --git a/music_kraken/pages/bandcamp.py b/music_kraken/pages/bandcamp.py index 4a8fd6c..44bc6a1 100644 --- a/music_kraken/pages/bandcamp.py +++ b/music_kraken/pages/bandcamp.py @@ -10,7 +10,7 @@ from .abstract import Page from ..objects import ( Artist, Source, - SourcePages, + SourceType, Song, Album, Label, @@ -23,6 +23,7 @@ from ..objects import ( ) from ..connection import Connection from ..utils import dump_to_file +from ..utils.enums import SourceType, ALL_SOURCE_TYPES from ..utils.support_classes.download_result import DownloadResult from ..utils.string_processing import clean_song_title from ..utils.config import main_settings, logging_settings @@ -49,8 +50,7 @@ class BandcampTypes(Enum): class Bandcamp(Page): - DOWNLOAD_PRIORITY = 10 - SOURCE_TYPE = SourcePages.BANDCAMP + SOURCE_TYPE = ALL_SOURCE_TYPES.BANDCAMP LOGGER = logging_settings["bandcamp_logger"] def __init__(self, *args, **kwargs): diff --git a/music_kraken/pages/encyclopaedia_metallum.py b/music_kraken/pages/encyclopaedia_metallum.py index dba4527..6ebd1d7 100644 --- a/music_kraken/pages/encyclopaedia_metallum.py +++ b/music_kraken/pages/encyclopaedia_metallum.py @@ -7,7 +7,7 @@ from urllib.parse import urlparse, urlencode from ..connection import Connection from ..utils.config import logging_settings from .abstract import Page -from ..utils.enums.source import SourcePages +from ..utils.enums.source import SourceType from ..utils.enums.album import AlbumType from ..utils.support_classes.query import Query from ..objects import ( @@ -59,7 +59,7 @@ def _song_from_json(artist_html=None, album_html=None, release_type=None, title= _album_from_json(album_html=album_html, release_type=release_type, artist_html=artist_html) ], source_list=[ - Source(SourcePages.ENCYCLOPAEDIA_METALLUM, song_id) + Source(SourceType.ENCYCLOPAEDIA_METALLUM, song_id) ] ) @@ -85,7 +85,7 @@ def _artist_from_json(artist_html=None, genre=None, country=None) -> Artist: return Artist( name=artist_name, source_list=[ - Source(SourcePages.ENCYCLOPAEDIA_METALLUM, artist_url) + Source(SourceType.ENCYCLOPAEDIA_METALLUM, artist_url) ] ) @@ -105,7 +105,7 @@ def _album_from_json(album_html=None, release_type=None, artist_html=None) -> Al title=album_name, album_type=album_type, source_list=[ - Source(SourcePages.ENCYCLOPAEDIA_METALLUM, album_url) + Source(SourceType.ENCYCLOPAEDIA_METALLUM, album_url) ], artist_list=[ _artist_from_json(artist_html=artist_html) @@ -207,7 +207,7 @@ def create_grid( class EncyclopaediaMetallum(Page): - SOURCE_TYPE = SourcePages.ENCYCLOPAEDIA_METALLUM + SOURCE_TYPE = SourceType.ENCYCLOPAEDIA_METALLUM LOGGER = logging_settings["metal_archives_logger"] def __init__(self, **kwargs): @@ -832,7 +832,7 @@ class EncyclopaediaMetallum(Page): ) def get_source_type(self, source: Source): - if self.SOURCE_TYPE != source.page_enum: + if self.SOURCE_TYPE != source.source_type: return None url = source.url diff --git a/music_kraken/pages/musify.py b/music_kraken/pages/musify.py index 454425d..cf612af 100644 --- a/music_kraken/pages/musify.py +++ b/music_kraken/pages/musify.py @@ -9,7 +9,7 @@ from bs4 import BeautifulSoup from ..connection import Connection from .abstract import Page -from ..utils.enums.source import SourcePages +from ..utils.enums import SourceType, ALL_SOURCE_TYPES from ..utils.enums.album import AlbumType, AlbumStatus from ..objects import ( Artist, @@ -111,9 +111,7 @@ def parse_url(url: str) -> MusifyUrl: class Musify(Page): - DOWNLOAD_PRIORITY = 9 - SOURCE_TYPE = SourcePages.MUSIFY - LOGGER = logging_settings["musify_logger"] + SOURCE_TYPE = ALL_SOURCE_TYPES.MUSIFY HOST = "https://musify.club" @@ -505,7 +503,7 @@ class Musify(Page): iframe_list: List[BeautifulSoup] = video_container.findAll("iframe") for iframe in iframe_list: source_list.append(Source( - SourcePages.YOUTUBE, + SourceType.YOUTUBE, iframe["src"], referrer_page=self.SOURCE_TYPE )) diff --git a/music_kraken/pages/preset.py b/music_kraken/pages/preset.py deleted file mode 100644 index 0755089..0000000 --- a/music_kraken/pages/preset.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import List, Optional, Type -from urllib.parse import urlparse -import logging - - -from ..objects import Source, DatabaseObject -from .abstract import Page -from ..objects import ( - Artist, - Source, - SourcePages, - Song, - Album, - Label, - Target -) -from ..connection import Connection -from ..utils.support_classes.query import Query -from ..utils.support_classes.download_result import DownloadResult - -class Preset(Page): - # CHANGE - SOURCE_TYPE = SourcePages.PRESET - LOGGER = logging.getLogger("preset") - - def __init__(self, *args, **kwargs): - self.connection: Connection = Connection( - host="https://www.preset.cum/", - logger=self.LOGGER - ) - - super().__init__(*args, **kwargs) - - def get_source_type(self, source: Source) -> Optional[Type[DatabaseObject]]: - return super().get_source_type(source) - - def general_search(self, search_query: str) -> List[DatabaseObject]: - return [] - - def label_search(self, label: Label) -> List[Label]: - return [] - - def artist_search(self, artist: Artist) -> List[Artist]: - return [] - - def album_search(self, album: Album) -> List[Album]: - return [] - - def song_search(self, song: Song) -> List[Song]: - return [] - - def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song: - return Song() - - def fetch_album(self, source: Source, stop_at_level: int = 1) -> Album: - return Album() - - def fetch_artist(self, source: Source, stop_at_level: int = 1) -> Artist: - return Artist() - - def fetch_label(self, source: Source, stop_at_level: int = 1) -> Label: - return Label() - - def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult: - return DownloadResult() diff --git a/music_kraken/pages/youtube.py b/music_kraken/pages/youtube.py index afc5501..5dda132 100644 --- a/music_kraken/pages/youtube.py +++ b/music_kraken/pages/youtube.py @@ -9,7 +9,6 @@ from .abstract import Page from ..objects import ( Artist, Source, - SourcePages, Song, Album, Label, @@ -19,6 +18,7 @@ from ..objects import ( ) from ..connection import Connection from ..utils.string_processing import clean_song_title +from ..utils.enums import SourceType, ALL_SOURCE_TYPES from ..utils.support_classes.download_result import DownloadResult from ..utils.config import youtube_settings, main_settings, logging_settings @@ -39,8 +39,7 @@ def get_piped_url(path: str = "", params: str = "", query: str = "", fragment: s class YouTube(SuperYouTube): # CHANGE - SOURCE_TYPE = SourcePages.YOUTUBE - LOGGER = logging_settings["youtube_logger"] + SOURCE_TYPE = ALL_SOURCE_TYPES.YOUTUBE NO_ADDITIONAL_DATA_FROM_SONG = False diff --git a/music_kraken/pages/youtube_music/_list_render.py b/music_kraken/pages/youtube_music/_list_render.py index bb6f40b..2158385 100644 --- a/music_kraken/pages/youtube_music/_list_render.py +++ b/music_kraken/pages/youtube_music/_list_render.py @@ -7,7 +7,6 @@ from ..abstract import Page from ...objects import ( Artist, Source, - SourcePages, Song, Album, Label, diff --git a/music_kraken/pages/youtube_music/_music_object_render.py b/music_kraken/pages/youtube_music/_music_object_render.py index 831d50d..43aee3e 100644 --- a/music_kraken/pages/youtube_music/_music_object_render.py +++ b/music_kraken/pages/youtube_music/_music_object_render.py @@ -3,12 +3,13 @@ from enum import Enum from ...utils.config import youtube_settings, logging_settings from ...utils.string_processing import clean_song_title +from ...utils.enums import SourceType, ALL_SOURCE_TYPES + from ...objects import Source, DatabaseObject from ..abstract import Page from ...objects import ( Artist, Source, - SourcePages, Song, Album, Label, @@ -18,7 +19,7 @@ from ...objects import ( LOGGER = logging_settings["youtube_music_logger"] -SOURCE_PAGE = SourcePages.YOUTUBE_MUSIC +SOURCE_PAGE = ALL_SOURCE_TYPES.YOUTUBE class PageType(Enum): diff --git a/music_kraken/pages/youtube_music/super_youtube.py b/music_kraken/pages/youtube_music/super_youtube.py index 3ba1cee..df900a1 100644 --- a/music_kraken/pages/youtube_music/super_youtube.py +++ b/music_kraken/pages/youtube_music/super_youtube.py @@ -10,7 +10,6 @@ from ..abstract import Page from ...objects import ( Artist, Source, - SourcePages, Song, Album, Label, @@ -21,6 +20,7 @@ from ...objects import ( from ...connection import Connection from ...utils.support_classes.download_result import DownloadResult from ...utils.config import youtube_settings, logging_settings, main_settings +from ...utils.enums import SourceType, ALL_SOURCE_TYPES def get_invidious_url(path: str = "", params: str = "", query: str = "", fragment: str = "") -> str: @@ -50,7 +50,7 @@ class YouTubeUrl: """ def __init__(self, url: str) -> None: - self.SOURCE_TYPE = SourcePages.YOUTUBE + self.SOURCE_TYPE = ALL_SOURCE_TYPES.YOUTUBE """ Raises Index exception for wrong url, and value error for not found enum type @@ -58,9 +58,6 @@ class YouTubeUrl: self.id = "" parsed = urlparse(url=url) - if parsed.netloc == "music.youtube.com": - self.SOURCE_TYPE = SourcePages.YOUTUBE_MUSIC - self.url_type: YouTubeUrlType type_frag_list = parsed.path.split("/") @@ -124,8 +121,7 @@ class YouTubeUrl: class SuperYouTube(Page): # CHANGE - SOURCE_TYPE = SourcePages.YOUTUBE - LOGGER = logging_settings["youtube_logger"] + SOURCE_TYPE = ALL_SOURCE_TYPES.YOUTUBE NO_ADDITIONAL_DATA_FROM_SONG = False diff --git a/music_kraken/pages/youtube_music/youtube_music.py b/music_kraken/pages/youtube_music/youtube_music.py index 9339c9f..d62df42 100644 --- a/music_kraken/pages/youtube_music/youtube_music.py +++ b/music_kraken/pages/youtube_music/youtube_music.py @@ -27,7 +27,7 @@ from ..abstract import Page from ...objects import ( Artist, Source, - SourcePages, + SourceType, Song, Album, Label, @@ -176,7 +176,7 @@ ALBUM_TYPE_MAP = { class YoutubeMusic(SuperYouTube): # CHANGE - SOURCE_TYPE = SourcePages.YOUTUBE_MUSIC + SOURCE_TYPE = SourceType.YOUTUBE_MUSIC LOGGER = logging_settings["youtube_music_logger"] def __init__(self, *args, ydl_opts: dict = None, **kwargs): @@ -619,7 +619,7 @@ class YoutubeMusic(SuperYouTube): Artist( name=name, source_list=[Source( - SourcePages.YOUTUBE_MUSIC, + SourceType.YOUTUBE_MUSIC, f"https://music.youtube.com/channel/{ydl_res.get('channel_id', ydl_res.get('uploader_id', ''))}" )] ) for name in artist_names] @@ -640,7 +640,7 @@ class YoutubeMusic(SuperYouTube): artwork=Artwork(*ydl_res.get("thumbnails", [])), main_artist_list=artist_list, source_list=[Source( - SourcePages.YOUTUBE_MUSIC, + SourceType.YOUTUBE_MUSIC, f"https://music.youtube.com/watch?v={ydl_res.get('id')}" ), source], ) diff --git a/music_kraken/utils/enums/__init__.py b/music_kraken/utils/enums/__init__.py index b90f9aa..e460a03 100644 --- a/music_kraken/utils/enums/__init__.py +++ b/music_kraken/utils/enums/__init__.py @@ -1 +1,53 @@ -from .source import SourcePages \ No newline at end of file +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional, TYPE_CHECKING, Type +if TYPE_CHECKING: + from ...pages.abstract import Page + +@dataclass +class SourceType: + name: str + homepage: Optional[str] = None + download_priority: int = 0 + + page_type: Type[Page] = None + page: Page = None + + + def register_page(self, page_type: Type[Page]): + self.page_type = page + self.page = page_type() + + @property + def has_page(self) -> bool: + return self.page is not None + + # for backwards compatibility + @property + def value(self) -> str: + return self.name + + +class ALL_SOURCE_TYPES: + YOUTUBE = SourceType(name="youtube", homepage="https://music.youtube.com/") + BANDCAMP = SourceType(name="bandcamp", homepage="https://bandcamp.com/", download_priority=10) + MUSIFY = SourceType(name="musify", homepage="https://musify.club/", download_priority=7) + + GENIUS = SourceType(name="genius", homepage="https://genius.com/") + MUSICBRAINZ = SourceType(name="musicbrainz", homepage="https://musicbrainz.org/") + ENCYCLOPAEDIA_METALLUM = SourceType(name="encyclopaedia metallum") + DEEZER = SourceType(name="deezer", homepage="https://www.deezer.com/") + SPOTIFY = SourceType(name="spotify", homepage="https://open.spotify.com/") + + # This has nothing to do with audio, but bands can be here + WIKIPEDIA = SourceType(name="wikipedia", homepage="https://en.wikipedia.org/wiki/Main_Page") + INSTAGRAM = SourceType(name="instagram", homepage="https://www.instagram.com/") + FACEBOOK = SourceType(name="facebook", homepage="https://www.facebook.com/") + TWITTER = SourceType(name="twitter", homepage="https://twitter.com/") + # Yes somehow this ancient site is linked EVERYWHERE + MYSPACE = SourceType(name="myspace", homepage="https://myspace.com/") + + MANUAL = SourceType(name="manual") + + PRESET = SourceType(name="preset") diff --git a/music_kraken/utils/enums/source.py b/music_kraken/utils/enums/source.py deleted file mode 100644 index be3171f..0000000 --- a/music_kraken/utils/enums/source.py +++ /dev/null @@ -1,40 +0,0 @@ -from enum import Enum - - -class SourceTypes(Enum): - SONG = "song" - ALBUM = "album" - ARTIST = "artist" - LYRICS = "lyrics" - - -class SourcePages(Enum): - YOUTUBE = "youtube", "https://www.youtube.com/" - MUSIFY = "musify", "https://musify.club/" - YOUTUBE_MUSIC = "youtube music", "https://music.youtube.com/" - GENIUS = "genius", "https://genius.com/" - MUSICBRAINZ = "musicbrainz", "https://musicbrainz.org/" - ENCYCLOPAEDIA_METALLUM = "encyclopaedia metallum" - BANDCAMP = "bandcamp", "https://bandcamp.com/" - DEEZER = "deezer", "https://www.deezer.com/" - SPOTIFY = "spotify", "https://open.spotify.com/" - - # This has nothing to do with audio, but bands can be here - WIKIPEDIA = "wikipedia", "https://en.wikipedia.org/wiki/Main_Page" - INSTAGRAM = "instagram", "https://www.instagram.com/" - FACEBOOK = "facebook", "https://www.facebook.com/" - TWITTER = "twitter", "https://twitter.com/" - MYSPACE = "myspace", "https://myspace.com/" # Yes somehow this ancient site is linked EVERYWHERE - - MANUAL = "manual", "" - - PRESET = "preset", "" - - def __new__(cls, value, homepage = None): - member = object.__new__(cls) - - member._value_ = value - member.homepage = homepage - - return member - \ No newline at end of file diff --git a/music_kraken/utils/exception/__init__.py b/music_kraken/utils/exception/__init__.py index 746fe78..8f139fb 100644 --- a/music_kraken/utils/exception/__init__.py +++ b/music_kraken/utils/exception/__init__.py @@ -4,8 +4,20 @@ class MKBaseException(Exception): super().__init__(message, **kwargs) +# Downloading +class MKDownloadException(MKBaseException): + pass + + +class MKMissingNameException(MKDownloadException): + pass + + +# Frontend class MKFrontendException(MKBaseException): pass class MKInvalidInputException(MKFrontendException): pass + +