fix: fixed youtube music
This commit is contained in:
parent
564621b332
commit
6a8374d595
@ -42,9 +42,8 @@ if __name__ == "__main__":
|
|||||||
]
|
]
|
||||||
|
|
||||||
bandcamp_test = [
|
bandcamp_test = [
|
||||||
"s: #a Ghost Bath",
|
"s: #a Only Smile",
|
||||||
"0",
|
"d: 1",
|
||||||
"d: 4"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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])
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user