fix: fixed youtube music

This commit is contained in:
Hazel 2024-01-15 12:48:36 +01:00
parent 564621b332
commit 6a8374d595
7 changed files with 216 additions and 136 deletions

View File

@ -42,9 +42,8 @@ if __name__ == "__main__":
] ]
bandcamp_test = [ bandcamp_test = [
"s: #a Ghost Bath", "s: #a Only Smile",
"0", "d: 1",
"d: 4"
] ]

View File

@ -16,7 +16,6 @@ from ..download.page_attributes import Pages
from ..pages import Page from ..pages import Page
from ..objects import Song, Album, Artist, DatabaseObject from ..objects import Song, Album, Artist, DatabaseObject
""" """
This is the implementation of the Shell This is the implementation of the Shell
@ -107,6 +106,7 @@ def get_existing_genre() -> List[str]:
return existing_genres return existing_genres
def get_genre(): def get_genre():
existing_genres = get_existing_genre() existing_genres = get_existing_genre()
for i, genre_option in enumerate(existing_genres): for i, genre_option in enumerate(existing_genres):
@ -129,19 +129,18 @@ def get_genre():
verification = input(f"create new genre \"{new_genre}\"? (Y/N): ").lower() verification = input(f"create new genre \"{new_genre}\"? (Y/N): ").lower()
if verification in agree_inputs: if verification in agree_inputs:
return new_genre return new_genre
def help_message(): def help_message():
print() print()
print(main_settings["happy_messages"]) print(main_settings["happy_messages"])
print() print()
class Downloader: class Downloader:
def __init__( def __init__(
self, self,
exclude_pages: Set[Type[Page]] = None, exclude_pages: Set[Type[Page]] = None,
exclude_shady: bool = False, exclude_shady: bool = False,
max_displayed_options: int = 10, max_displayed_options: int = 10,
option_digits: int = 3, option_digits: int = 3,
@ -149,23 +148,22 @@ class Downloader:
process_metadata_anyway: bool = False, process_metadata_anyway: bool = False,
) -> None: ) -> None:
self.pages: Pages = Pages(exclude_pages=exclude_pages, exclude_shady=exclude_shady) self.pages: Pages = Pages(exclude_pages=exclude_pages, exclude_shady=exclude_shady)
self.page_dict: Dict[str, Type[Page]] = dict() self.page_dict: Dict[str, Type[Page]] = dict()
self.max_displayed_options = max_displayed_options self.max_displayed_options = max_displayed_options
self.option_digits: int = option_digits self.option_digits: int = option_digits
self.current_results: Results = None self.current_results: Results = None
self._result_history: List[Results] = [] self._result_history: List[Results] = []
self.genre = genre or get_genre() self.genre = genre or get_genre()
self.process_metadata_anyway = process_metadata_anyway self.process_metadata_anyway = process_metadata_anyway
print() print()
print(f"Downloading to: \"{self.genre}\"") print(f"Downloading to: \"{self.genre}\"")
print() print()
def print_current_options(self): def print_current_options(self):
self.page_dict = dict() self.page_dict = dict()
@ -176,12 +174,13 @@ class Downloader:
if isinstance(option, Option): if isinstance(option, Option):
print(f"{option.index:0{self.option_digits}} {option.music_object.option_string}") print(f"{option.index:0{self.option_digits}} {option.music_object.option_string}")
else: else:
prefix = ALPHABET[page_count%len(ALPHABET)] prefix = ALPHABET[page_count % len(ALPHABET)]
print(f"({prefix}) ------------------------{option.__name__:{PAGE_NAME_FILL}<{MAX_PAGE_LEN}}------------") print(
f"({prefix}) ------------------------{option.__name__:{PAGE_NAME_FILL}<{MAX_PAGE_LEN}}------------")
self.page_dict[prefix] = option self.page_dict[prefix] = option
self.page_dict[option.__name__] = option self.page_dict[option.__name__] = option
page_count += 1 page_count += 1
print() print()
@ -189,47 +188,47 @@ class Downloader:
def set_current_options(self, current_options: Results): def set_current_options(self, current_options: Results):
if main_settings["result_history"]: if main_settings["result_history"]:
self._result_history.append(current_options) self._result_history.append(current_options)
if main_settings["history_length"] != -1: if main_settings["history_length"] != -1:
if len(self._result_history) > main_settings["history_length"]: if len(self._result_history) > main_settings["history_length"]:
self._result_history.pop(0) self._result_history.pop(0)
self.current_results = current_options self.current_results = current_options
def previous_option(self) -> bool: def previous_option(self) -> bool:
if not main_settings["result_history"]: if not main_settings["result_history"]:
print("History is turned of.\nGo to main_settings, and change the value at 'result_history' to 'true'.") print("History is turned of.\nGo to main_settings, and change the value at 'result_history' to 'true'.")
return False return False
if len(self._result_history) <= 1: if len(self._result_history) <= 1:
print(f"No results in history.") print(f"No results in history.")
return False return False
self._result_history.pop() self._result_history.pop()
self.current_results = self._result_history[-1] self.current_results = self._result_history[-1]
return True return True
def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query: def _process_parsed(self, key_text: Dict[str, str], query: str) -> Query:
song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True) song = None if not "t" in key_text else Song(title=key_text["t"], dynamic=True)
album = None if not "r" in key_text else Album(title=key_text["r"], dynamic=True) album = None if not "r" in key_text else Album(title=key_text["r"], dynamic=True)
artist = None if not "a" in key_text else Artist(name=key_text["a"], dynamic=True) artist = None if not "a" in key_text else Artist(name=key_text["a"], dynamic=True)
if song is not None: if song is not None:
if album is not None: if album is not None:
song.album_collection.append(album) song.album_collection.append(album)
if artist is not None: if artist is not None:
song.main_artist_collection.append(artist) song.main_artist_collection.append(artist)
return Query(raw_query=query, music_object=song) return Query(raw_query=query, music_object=song)
if album is not None: if album is not None:
if artist is not None: if artist is not None:
album.artist_collection.append(artist) album.artist_collection.append(artist)
return Query(raw_query=query, music_object=album) return Query(raw_query=query, music_object=album)
if artist is not None: if artist is not None:
return Query(raw_query=query, music_object=artist) return Query(raw_query=query, music_object=artist)
return Query(raw_query=query) return Query(raw_query=query)
def search(self, query: str): def search(self, query: str):
if re.match(URL_PATTERN, query) is not None: if re.match(URL_PATTERN, query) is not None:
try: try:
@ -243,58 +242,57 @@ class Downloader:
self.set_current_options(PageResults(page, data_object.options)) self.set_current_options(PageResults(page, data_object.options))
self.print_current_options() self.print_current_options()
return return
special_characters = "#\\" special_characters = "#\\"
query = query + " " query = query + " "
key_text = {} key_text = {}
skip_next = False skip_next = False
escape_next = False escape_next = False
new_text = "" new_text = ""
latest_key: str = None latest_key: str = None
for i in range(len(query) - 1): for i in range(len(query) - 1):
current_char = query[i] current_char = query[i]
next_char = query[i+1] next_char = query[i + 1]
if skip_next: if skip_next:
skip_next = False skip_next = False
continue continue
if escape_next: if escape_next:
new_text += current_char new_text += current_char
escape_next = False escape_next = False
# escaping # escaping
if current_char == "\\": if current_char == "\\":
if next_char in special_characters: if next_char in special_characters:
escape_next = True escape_next = True
continue continue
if current_char == "#": if current_char == "#":
if latest_key is not None: if latest_key is not None:
key_text[latest_key] = new_text key_text[latest_key] = new_text
new_text = "" new_text = ""
latest_key = next_char latest_key = next_char
skip_next = True skip_next = True
continue continue
new_text += current_char new_text += current_char
if latest_key is not None: if latest_key is not None:
key_text[latest_key] = new_text key_text[latest_key] = new_text
parsed_query: Query = self._process_parsed(key_text, query) parsed_query: Query = self._process_parsed(key_text, query)
self.set_current_options(self.pages.search(parsed_query)) self.set_current_options(self.pages.search(parsed_query))
self.print_current_options() self.print_current_options()
def goto(self, index: int): def goto(self, index: int):
page: Type[Page] page: Type[Page]
music_object: DatabaseObject music_object: DatabaseObject
try: try:
page, music_object = self.current_results.get_music_object_by_index(index) page, music_object = self.current_results.get_music_object_by_index(index)
except KeyError: except KeyError:
@ -302,23 +300,22 @@ class Downloader:
print(f"The option {index} doesn't exist.") print(f"The option {index} doesn't exist.")
print() print()
return return
self.pages.fetch_details(music_object) self.pages.fetch_details(music_object)
print(music_object) print(music_object)
print(music_object.options) print(music_object.options)
self.set_current_options(PageResults(page, music_object.options)) self.set_current_options(PageResults(page, music_object.options))
self.print_current_options() self.print_current_options()
def download(self, download_str: str, download_all: bool = False) -> bool: def download(self, download_str: str, download_all: bool = False) -> bool:
to_download: List[DatabaseObject] = [] to_download: List[DatabaseObject] = []
if re.match(URL_PATTERN, download_str) is not None: if re.match(URL_PATTERN, download_str) is not None:
_, music_objects = self.pages.fetch_url(download_str) _, music_objects = self.pages.fetch_url(download_str)
to_download.append(music_objects) to_download.append(music_objects)
else: else:
index: str index: str
for index in download_str.split(", "): for index in download_str.split(", "):
@ -327,66 +324,68 @@ class Downloader:
print(f"Every download thingie has to be an index, not {index}.") print(f"Every download thingie has to be an index, not {index}.")
print() print()
return False return False
for index in download_str.split(", "): for index in download_str.split(", "):
to_download.append(self.current_results.get_music_object_by_index(int(index))[1]) to_download.append(self.current_results.get_music_object_by_index(int(index))[1])
print() print()
print("Downloading:") print("Downloading:")
for download_object in to_download: for download_object in to_download:
print(download_object.option_string) print(download_object.option_string)
print() print()
_result_map: Dict[DatabaseObject, DownloadResult] = dict() _result_map: Dict[DatabaseObject, DownloadResult] = dict()
for database_object in to_download: for database_object in to_download:
r = self.pages.download(music_object=database_object, genre=self.genre, download_all=download_all, process_metadata_anyway=self.process_metadata_anyway) r = self.pages.download(music_object=database_object, genre=self.genre, download_all=download_all,
process_metadata_anyway=self.process_metadata_anyway)
_result_map[database_object] = r _result_map[database_object] = r
for music_object, result in _result_map.items(): for music_object, result in _result_map.items():
print() print()
print(music_object.option_string) print(music_object.option_string)
print(result) print(result)
return True return True
def process_input(self, input_str: str) -> bool: def process_input(self, input_str: str) -> bool:
input_str = input_str.strip() input_str = input_str.strip()
processed_input: str = input_str.lower() processed_input: str = input_str.lower()
if processed_input in EXIT_COMMANDS: if processed_input in EXIT_COMMANDS:
return True return True
if processed_input == ".": if processed_input == ".":
self.print_current_options() self.print_current_options()
return False return False
if processed_input == "..": if processed_input == "..":
if self.previous_option(): if self.previous_option():
self.print_current_options() self.print_current_options()
return False return False
if processed_input.startswith("s: "): if processed_input.startswith("s: "):
self.search(input_str[3:]) self.search(input_str[3:])
return False return False
if processed_input.startswith("d: "): if processed_input.startswith("d: "):
return self.download(input_str[3:]) return self.download(input_str[3:])
if processed_input.isdigit(): if processed_input.isdigit():
self.goto(int(processed_input)) self.goto(int(processed_input))
return False return False
if processed_input != "help": if processed_input != "help":
print("Invalid input.") print("Invalid input.")
help_message() help_message()
return False return False
def mainloop(self): def mainloop(self):
while True: while True:
if self.process_input(input("> ")): if self.process_input(input("> ")):
return return
@cli_function @cli_function
def download( def download(
genre: str = None, genre: str = None,
@ -403,9 +402,9 @@ def download(
print("Restart the programm to use it.") print("Restart the programm to use it.")
else: else:
print("Something went wrong configuring.") print("Something went wrong configuring.")
shell = Downloader(genre=genre, process_metadata_anyway=process_metadata_anyway) shell = Downloader(genre=genre, process_metadata_anyway=process_metadata_anyway)
if command_list is not None: if command_list is not None:
for command in command_list: for command in command_list:
shell.process_input(command) shell.process_input(command)
@ -414,5 +413,5 @@ def download(
if direct_download_url is not None: if direct_download_url is not None:
if shell.download(direct_download_url, download_all=download_all): if shell.download(direct_download_url, download_all=download_all):
return return
shell.mainloop() shell.mainloop()

View File

@ -98,8 +98,10 @@ class Pages:
def download(self, music_object: DatabaseObject, genre: str, download_all: bool = False, process_metadata_anyway: bool = False) -> DownloadResult: def download(self, music_object: DatabaseObject, genre: str, download_all: bool = False, process_metadata_anyway: bool = False) -> DownloadResult:
if not isinstance(music_object, INDEPENDENT_DB_OBJECTS): if not isinstance(music_object, INDEPENDENT_DB_OBJECTS):
return DownloadResult(error_message=f"{type(music_object).__name__} can't be downloaded.") return DownloadResult(error_message=f"{type(music_object).__name__} can't be downloaded.")
_page_types = set() self.fetch_details(music_object)
_page_types = set(self._source_to_page)
for src in music_object.source_collection.source_pages: for src in music_object.source_collection.source_pages:
if src in self._source_to_page: if src in self._source_to_page:
_page_types.add(self._source_to_page[src]) _page_types.add(self._source_to_page[src])

View File

@ -315,7 +315,7 @@ class Collection(Generic[T]):
yield element yield element
def __merge__(self, __other: Collection, override: bool = False): def __merge__(self, __other: Collection, override: bool = False):
self.extend(__other.shallow_list, from_map=True) self.extend(__other._data, from_map=True)
def __getitem__(self, item: int): def __getitem__(self, item: int):
if item < len(self._data): if item < len(self._data):

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import random import random
from collections import defaultdict
from functools import lru_cache from functools import lru_cache
from typing import Optional, Dict, Tuple, List, Type, Generic, Any, TypeVar, Set from typing import Optional, Dict, Tuple, List, Type, Generic, Any, TypeVar, Set
@ -131,6 +132,18 @@ class OuterProxy:
return super().__setattr__(__name, __value) return super().__setattr__(__name, __value)
def _add_other_db_objects(self, object_type: Type[OuterProxy], object_list: List[OuterProxy]):
pass
def add_list_of_other_objects(self, object_list: List[OuterProxy]):
d: Dict[Type[OuterProxy], List[OuterProxy]] = defaultdict(list)
for db_object in object_list:
d[type(db_object)].append(db_object)
for key, value in d.items():
self._add_other_db_objects(key, value)
def __hash__(self): def __hash__(self):
""" """
:raise: IsDynamicException :raise: IsDynamicException

View File

@ -95,6 +95,22 @@ class Song(Base):
"feature_song_collection": self "feature_song_collection": self
} }
def _add_other_db_objects(self, object_type: Type[OuterProxy], object_list: List[OuterProxy]):
if object_type is Song:
return
if isinstance(object_list, Lyrics):
self.lyrics_collection.extend(object_list)
return
if isinstance(object_list, Artist):
self.main_artist_collection.extend(object_list)
return
if isinstance(object_list, Album):
self.album_collection.extend(object_list)
return
@property @property
def indexing_values(self) -> List[Tuple[str, object]]: def indexing_values(self) -> List[Tuple[str, object]]:
return [ return [
@ -229,6 +245,22 @@ class Album(Base):
"main_artist_collection": self.artist_collection "main_artist_collection": self.artist_collection
} }
def _add_other_db_objects(self, object_type: Type[OuterProxy], object_list: List[OuterProxy]):
if object_type is Song:
self.song_collection.extend(object_list)
return
if object_type is Artist:
self.artist_collection.extend(object_list)
return
if object_type is Album:
return
if object_type is Label:
self.label_collection.extend(object_list)
return
@property @property
def indexing_values(self) -> List[Tuple[str, object]]: def indexing_values(self) -> List[Tuple[str, object]]:
return [ return [
@ -436,6 +468,23 @@ class Artist(Base):
"current_artist_collection": self "current_artist_collection": self
} }
def _add_other_db_objects(self, object_type: Type[OuterProxy], object_list: List[OuterProxy]):
if object_type is Song:
# this doesn't really make sense
# self.feature_song_collection.extend(object_list)
return
if object_type is Artist:
return
if object_type is Album:
self.main_album_collection.extend(object_list)
return
if object_type is Label:
self.label_collection.extend(object_list)
return
@property @property
def options(self) -> List[P]: def options(self) -> List[P]:
options = [self, *self.main_album_collection.shallow_list, *self.feature_album] options = [self, *self.main_album_collection.shallow_list, *self.feature_album]
@ -618,6 +667,18 @@ class Label(Base):
*[('url', source.url) for source in self.source_collection] *[('url', source.url) for source in self.source_collection]
] ]
def _add_other_db_objects(self, object_type: Type[OuterProxy], object_list: List[OuterProxy]):
if object_type is Song:
return
if object_type is Artist:
self.current_artist_collection.extend(object_list)
return
if object_type is Album:
self.album_collection.extend(object_list)
return
@property @property
def options(self) -> List[P]: def options(self) -> List[P]:
options = [self] options = [self]

View File

@ -28,7 +28,6 @@ from ..utils.support_classes.query import Query
from ..utils.support_classes.download_result import DownloadResult from ..utils.support_classes.download_result import DownloadResult
from ..utils.string_processing import fit_to_file_system from ..utils.string_processing import fit_to_file_system
INDEPENDENT_DB_OBJECTS = Union[Label, Album, Artist, Song] INDEPENDENT_DB_OBJECTS = Union[Label, Album, Artist, Song]
INDEPENDENT_DB_TYPES = Union[Type[Song], Type[Album], Type[Artist], Type[Label]] INDEPENDENT_DB_TYPES = Union[Type[Song], Type[Album], Type[Artist], Type[Label]]
@ -42,22 +41,22 @@ class NamingDict(dict):
"album": "album.title", "album": "album.title",
"album_type": "album.album_type_string" "album_type": "album.album_type_string"
} }
def __init__(self, values: dict, object_mappings: Dict[str, DatabaseObject] = None): def __init__(self, values: dict, object_mappings: Dict[str, DatabaseObject] = None):
self.object_mappings: Dict[str, DatabaseObject] = object_mappings or dict() self.object_mappings: Dict[str, DatabaseObject] = object_mappings or dict()
super().__init__(values) super().__init__(values)
self["audio_format"] = main_settings["audio_format"] self["audio_format"] = main_settings["audio_format"]
def add_object(self, music_object: DatabaseObject): def add_object(self, music_object: DatabaseObject):
self.object_mappings[type(music_object).__name__.lower()] = music_object self.object_mappings[type(music_object).__name__.lower()] = music_object
def copy(self) -> dict: def copy(self) -> dict:
return type(self)(super().copy(), self.object_mappings.copy()) return type(self)(super().copy(), self.object_mappings.copy())
def __getitem__(self, key: str) -> str: def __getitem__(self, key: str) -> str:
return fit_to_file_system(super().__getitem__(key)) return fit_to_file_system(super().__getitem__(key))
def default_value_for_name(self, name: str) -> str: def default_value_for_name(self, name: str) -> str:
return f'Various {name.replace("_", " ").title()}' return f'Various {name.replace("_", " ").title()}'
@ -67,23 +66,23 @@ class NamingDict(dict):
return self.default_value_for_name(key) return self.default_value_for_name(key)
key = self.CUSTOM_KEYS[key] key = self.CUSTOM_KEYS[key]
frag_list = key.split(".") frag_list = key.split(".")
object_name = frag_list[0].strip().lower() object_name = frag_list[0].strip().lower()
attribute_name = frag_list[-1].strip().lower() attribute_name = frag_list[-1].strip().lower()
if object_name not in self.object_mappings: if object_name not in self.object_mappings:
return self.default_value_for_name(attribute_name) return self.default_value_for_name(attribute_name)
music_object = self.object_mappings[object_name] music_object = self.object_mappings[object_name]
try: try:
value = getattr(music_object, attribute_name) value = getattr(music_object, attribute_name)
if value is None: if value is None:
return self.default_value_for_name(attribute_name) return self.default_value_for_name(attribute_name)
return str(value) return str(value)
except AttributeError: except AttributeError:
return self.default_value_for_name(attribute_name) return self.default_value_for_name(attribute_name)
@ -133,6 +132,7 @@ def _clean_song(song: Song, collections: Dict[INDEPENDENT_DB_TYPES, Collection])
_clean_collection(song.feature_artist_collection, collections) _clean_collection(song.feature_artist_collection, collections)
_clean_collection(song.main_artist_collection, collections) _clean_collection(song.main_artist_collection, collections)
def clean_object(dirty_object: DatabaseObject) -> DatabaseObject: def clean_object(dirty_object: DatabaseObject) -> DatabaseObject:
if isinstance(dirty_object, INDEPENDENT_DB_OBJECTS): if isinstance(dirty_object, INDEPENDENT_DB_OBJECTS):
collections = { collections = {
@ -147,20 +147,22 @@ def clean_object(dirty_object: DatabaseObject) -> DatabaseObject:
_clean_music_object(dirty_object, collections) _clean_music_object(dirty_object, collections)
return dirty_object return dirty_object
def build_new_object(new_object: DatabaseObject) -> DatabaseObject: def build_new_object(new_object: DatabaseObject) -> DatabaseObject:
new_object = clean_object(new_object) new_object = clean_object(new_object)
new_object.compile(merge_into=False) new_object.compile(merge_into=False)
return new_object return new_object
def merge_together(old_object: DatabaseObject, new_object: DatabaseObject, do_compile: bool = True) -> DatabaseObject: def merge_together(old_object: DatabaseObject, new_object: DatabaseObject, do_compile: bool = True) -> DatabaseObject:
new_object = clean_object(new_object) new_object = clean_object(new_object)
old_object.merge(new_object) old_object.merge(new_object)
if do_compile and False: if do_compile and False:
old_object.compile(merge_into=False) old_object.compile(merge_into=False)
return old_object return old_object
@ -169,60 +171,59 @@ class Page:
This is an abstract class, laying out the This is an abstract class, laying out the
functionality for every other class fetching something functionality for every other class fetching something
""" """
SOURCE_TYPE: SourcePages SOURCE_TYPE: SourcePages
LOGGER = logging.getLogger("this shouldn't be used") LOGGER = logging.getLogger("this shouldn't be used")
# set this to true, if all song details can also be fetched by fetching album details # set this to true, if all song details can also be fetched by fetching album details
NO_ADDITIONAL_DATA_FROM_SONG = False NO_ADDITIONAL_DATA_FROM_SONG = False
def get_source_type(self, source: Source) -> Optional[Type[DatabaseObject]]: def get_source_type(self, source: Source) -> Optional[Type[DatabaseObject]]:
return None return None
def get_soup_from_response(self, r: requests.Response) -> BeautifulSoup: def get_soup_from_response(self, r: requests.Response) -> BeautifulSoup:
return BeautifulSoup(r.content, "html.parser") return BeautifulSoup(r.content, "html.parser")
# to search stuff # to search stuff
def search(self, query: Query) -> List[DatabaseObject]: def search(self, query: Query) -> List[DatabaseObject]:
music_object = query.music_object music_object = query.music_object
search_functions = { search_functions = {
Song: self.song_search, Song: self.song_search,
Album: self.album_search, Album: self.album_search,
Artist: self.artist_search, Artist: self.artist_search,
Label: self.label_search Label: self.label_search
} }
if type(music_object) in search_functions: if type(music_object) in search_functions:
r = search_functions[type(music_object)](music_object) r = search_functions[type(music_object)](music_object)
if r is not None and len(r) > 0: if r is not None and len(r) > 0:
return r return r
r = [] r = []
for default_query in query.default_search: for default_query in query.default_search:
for single_option in self.general_search(default_query): for single_option in self.general_search(default_query):
r.append(single_option) r.append(single_option)
return r return r
def general_search(self, search_query: str) -> List[DatabaseObject]: def general_search(self, search_query: str) -> List[DatabaseObject]:
return [] return []
def label_search(self, label: Label) -> List[Label]: def label_search(self, label: Label) -> List[Label]:
return [] return []
def artist_search(self, artist: Artist) -> List[Artist]: def artist_search(self, artist: Artist) -> List[Artist]:
return [] return []
def album_search(self, album: Album) -> List[Album]: def album_search(self, album: Album) -> List[Album]:
return [] return []
def song_search(self, song: Song) -> List[Song]: def song_search(self, song: Song) -> List[Song]:
return [] return []
def fetch_details(self, music_object: DatabaseObject, stop_at_level: int = 1, post_process: bool = True) -> DatabaseObject: def fetch_details(self, music_object: DatabaseObject, stop_at_level: int = 1,
post_process: bool = True) -> DatabaseObject:
""" """
when a music object with lacking data is passed in, it returns when a music object with lacking data is passed in, it returns
the SAME object **(no copy)** with more detailed data. the SAME object **(no copy)** with more detailed data.
@ -263,7 +264,9 @@ class Page:
return music_object return music_object
def fetch_object_from_source(self, source: Source, stop_at_level: int = 2, enforce_type: Type[DatabaseObject] = None, post_process: bool = True) -> Optional[DatabaseObject]: def fetch_object_from_source(self, source: Source, stop_at_level: int = 2,
enforce_type: Type[DatabaseObject] = None, post_process: bool = True) -> Optional[
DatabaseObject]:
obj_type = self.get_source_type(source) obj_type = self.get_source_type(source)
if obj_type is None: if obj_type is None:
@ -272,16 +275,16 @@ class Page:
if enforce_type != obj_type and enforce_type is not None: if enforce_type != obj_type and enforce_type is not None:
self.LOGGER.warning(f"Object type isn't type to enforce: {enforce_type}, {obj_type}") self.LOGGER.warning(f"Object type isn't type to enforce: {enforce_type}, {obj_type}")
return None return None
music_object: DatabaseObject = None music_object: DatabaseObject = None
fetch_map = { fetch_map = {
Song: self.fetch_song, Song: self.fetch_song,
Album: self.fetch_album, Album: self.fetch_album,
Artist: self.fetch_artist, Artist: self.fetch_artist,
Label: self.fetch_label Label: self.fetch_label
} }
if obj_type in fetch_map: if obj_type in fetch_map:
music_object = fetch_map[obj_type](source, stop_at_level) music_object = fetch_map[obj_type](source, stop_at_level)
else: else:
@ -294,10 +297,11 @@ class Page:
collection = music_object.__getattribute__(collection_str) collection = music_object.__getattribute__(collection_str)
for sub_element in collection: for sub_element in collection:
sub_element.merge(self.fetch_details(sub_element, stop_at_level=stop_at_level-1, post_process=False)) sub_element.merge(
self.fetch_details(sub_element, stop_at_level=stop_at_level - 1, post_process=False))
return music_object return music_object
def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song: def fetch_song(self, source: Source, stop_at_level: int = 1) -> Song:
return Song() return Song()
@ -310,41 +314,42 @@ class Page:
def fetch_label(self, source: Source, stop_at_level: int = 1) -> Label: def fetch_label(self, source: Source, stop_at_level: int = 1) -> Label:
return Label() return Label()
def download(self, music_object: DatabaseObject, genre: str, download_all: bool = False, process_metadata_anyway: bool = False) -> DownloadResult: def download(self, music_object: DatabaseObject, genre: str, download_all: bool = False,
process_metadata_anyway: bool = False) -> DownloadResult:
naming_dict: NamingDict = NamingDict({"genre": genre}) naming_dict: NamingDict = NamingDict({"genre": genre})
def fill_naming_objects(naming_music_object: DatabaseObject): def fill_naming_objects(naming_music_object: DatabaseObject):
nonlocal naming_dict nonlocal naming_dict
for collection_name in naming_music_object.UPWARDS_COLLECTION_STRING_ATTRIBUTES: for collection_name in naming_music_object.UPWARDS_COLLECTION_STRING_ATTRIBUTES:
collection: Collection = getattr(naming_music_object, collection_name) collection: Collection = getattr(naming_music_object, collection_name)
if collection.empty: if collection.empty:
continue continue
dom_ordered_music_object: DatabaseObject = collection[0] dom_ordered_music_object: DatabaseObject = collection[0]
naming_dict.add_object(dom_ordered_music_object) naming_dict.add_object(dom_ordered_music_object)
return fill_naming_objects(dom_ordered_music_object) return fill_naming_objects(dom_ordered_music_object)
fill_naming_objects(music_object) fill_naming_objects(music_object)
return self._download(music_object, naming_dict, download_all, process_metadata_anyway=process_metadata_anyway) return self._download(music_object, naming_dict, download_all, process_metadata_anyway=process_metadata_anyway)
def _download(self, music_object: DatabaseObject, naming_dict: NamingDict, download_all: bool = False,
def _download(self, music_object: DatabaseObject, naming_dict: NamingDict, download_all: bool = False, skip_details: bool = False, process_metadata_anyway: bool = False) -> DownloadResult: skip_details: bool = False, process_metadata_anyway: bool = False) -> DownloadResult:
skip_next_details = skip_details skip_next_details = skip_details
# Skips all releases, that are defined in shared.ALBUM_TYPE_BLACKLIST, if download_all is False # Skips all releases, that are defined in shared.ALBUM_TYPE_BLACKLIST, if download_all is False
if isinstance(music_object, Album): if isinstance(music_object, Album):
if self.NO_ADDITIONAL_DATA_FROM_SONG: if self.NO_ADDITIONAL_DATA_FROM_SONG:
skip_next_details = True skip_next_details = True
if not download_all and music_object.album_type.value in main_settings["album_type_blacklist"]: if not download_all and music_object.album_type.value in main_settings["album_type_blacklist"]:
return DownloadResult() return DownloadResult()
if not isinstance(music_object, Song) or not self.NO_ADDITIONAL_DATA_FROM_SONG: if not isinstance(music_object, Song) or not self.NO_ADDITIONAL_DATA_FROM_SONG:
self.fetch_details(music_object=music_object, stop_at_level=2) self.fetch_details(music_object=music_object, stop_at_level=2)
naming_dict.add_object(music_object) naming_dict.add_object(music_object)
if isinstance(music_object, Song): if isinstance(music_object, Song):
@ -357,7 +362,9 @@ class Page:
sub_ordered_music_object: DatabaseObject sub_ordered_music_object: DatabaseObject
for sub_ordered_music_object in collection: for sub_ordered_music_object in collection:
download_result.merge(self._download(sub_ordered_music_object, naming_dict.copy(), download_all, skip_details=skip_next_details, process_metadata_anyway=process_metadata_anyway)) download_result.merge(self._download(sub_ordered_music_object, naming_dict.copy(), download_all,
skip_details=skip_next_details,
process_metadata_anyway=process_metadata_anyway))
return download_result return download_result
@ -378,7 +385,6 @@ class Page:
) )
) )
if song.target_collection.empty: if song.target_collection.empty:
song.target_collection.append(new_target) song.target_collection.append(new_target)
@ -393,7 +399,7 @@ class Page:
str(song.id) str(song.id)
) )
) )
r = DownloadResult(1) r = DownloadResult(1)
found_on_disc = False found_on_disc = False
@ -403,10 +409,10 @@ class Page:
if process_metadata_anyway: if process_metadata_anyway:
target.copy_content(temp_target) target.copy_content(temp_target)
found_on_disc = True found_on_disc = True
r.found_on_disk += 1 r.found_on_disk += 1
r.add_target(target) r.add_target(target)
if found_on_disc and not process_metadata_anyway: if found_on_disc and not process_metadata_anyway:
self.LOGGER.info(f"{song.option_string} already exists, thus not downloading again.") self.LOGGER.info(f"{song.option_string} already exists, thus not downloading again.")
return r return r
@ -415,18 +421,18 @@ class Page:
if not found_on_disc: if not found_on_disc:
r = self.download_song_to_target(source=source, target=temp_target, desc=song.title) r = self.download_song_to_target(source=source, target=temp_target, desc=song.title)
if not r.is_fatal_error: if not r.is_fatal_error:
r.merge(self._post_process_targets(song, temp_target, [] if found_on_disc else self.get_skip_intervals(song, source))) r.merge(self._post_process_targets(song, temp_target,
[] if found_on_disc else self.get_skip_intervals(song, source)))
return r return r
def _post_process_targets(self, song: Song, temp_target: Target, interval_list: List) -> DownloadResult: def _post_process_targets(self, song: Song, temp_target: Target, interval_list: List) -> DownloadResult:
correct_codec(temp_target, interval_list=interval_list) correct_codec(temp_target, interval_list=interval_list)
self.post_process_hook(song, temp_target) self.post_process_hook(song, temp_target)
write_metadata_to_target(song.metadata, temp_target) write_metadata_to_target(song.metadata, temp_target)
r = DownloadResult() r = DownloadResult()
@ -436,17 +442,17 @@ class Page:
if temp_target is not target: if temp_target is not target:
temp_target.copy_content(target) temp_target.copy_content(target)
r.add_target(target) r.add_target(target)
temp_target.delete() temp_target.delete()
r.sponsor_segments += len(interval_list) r.sponsor_segments += len(interval_list)
return r return r
def get_skip_intervals(self, song: Song, source: Source) -> List[Tuple[float, float]]: def get_skip_intervals(self, song: Song, source: Source) -> List[Tuple[float, float]]:
return [] return []
def post_process_hook(self, song: Song, temp_target: Target, **kwargs): def post_process_hook(self, song: Song, temp_target: Target, **kwargs):
pass pass
def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult: def download_song_to_target(self, source: Source, target: Target, desc: str = None) -> DownloadResult:
return DownloadResult() return DownloadResult()