2022-11-23 07:24:05 +00:00
|
|
|
import logging
|
2023-03-30 10:43:43 +00:00
|
|
|
import re
|
2023-04-05 08:01:51 +00:00
|
|
|
from pathlib import Path
|
|
|
|
from typing import List
|
2022-11-23 07:24:05 +00:00
|
|
|
|
2023-04-15 15:17:33 +00:00
|
|
|
import gc
|
|
|
|
import musicbrainzngs
|
|
|
|
|
2023-05-23 08:55:44 +00:00
|
|
|
from . import objects, pages, download
|
2023-04-18 07:02:03 +00:00
|
|
|
from .utils import exception, shared, path_manager
|
2023-04-15 17:24:23 +00:00
|
|
|
from .utils.config import config, read, write, PATHS_SECTION
|
2023-04-05 08:01:51 +00:00
|
|
|
from .utils.shared import MUSIC_DIR, MODIFY_GC, NOT_A_GENRE_REGEX, get_random_message
|
2023-04-15 15:17:33 +00:00
|
|
|
from .utils.string_processing import fit_to_file_system
|
2023-04-04 20:07:56 +00:00
|
|
|
|
2023-04-18 07:02:03 +00:00
|
|
|
|
2023-04-04 20:07:56 +00:00
|
|
|
if MODIFY_GC:
|
|
|
|
"""
|
|
|
|
At the start I modify the garbage collector to run a bit fewer times.
|
|
|
|
This should increase speed:
|
|
|
|
https://mkennedy.codes/posts/python-gc-settings-change-this-and-make-your-app-go-20pc-faster/
|
|
|
|
"""
|
|
|
|
# Clean up what might be garbage so far.
|
|
|
|
gc.collect(2)
|
|
|
|
|
|
|
|
allocs, gen1, gen2 = gc.get_threshold()
|
|
|
|
allocs = 50_000 # Start the GC sequence every 50K not 700 allocations.
|
|
|
|
gen1 = gen1 * 2
|
|
|
|
gen2 = gen2 * 2
|
|
|
|
gc.set_threshold(allocs, gen1, gen2)
|
2022-11-24 21:10:22 +00:00
|
|
|
|
2022-11-22 13:53:29 +00:00
|
|
|
logging.getLogger("musicbrainzngs").setLevel(logging.WARNING)
|
|
|
|
musicbrainzngs.set_useragent("metadata receiver", "0.1", "https://github.com/HeIIow2/music-downloader")
|
2022-11-15 12:04:44 +00:00
|
|
|
|
2023-04-04 19:27:27 +00:00
|
|
|
URL_REGEX = 'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+'
|
2023-03-31 08:34:29 +00:00
|
|
|
DOWNLOAD_COMMANDS = {
|
|
|
|
"ok",
|
|
|
|
"download",
|
2023-04-04 19:29:10 +00:00
|
|
|
"\\d",
|
2023-03-31 08:34:29 +00:00
|
|
|
"hs"
|
|
|
|
}
|
2023-03-30 10:43:43 +00:00
|
|
|
|
2023-04-04 18:19:29 +00:00
|
|
|
EXIT_COMMANDS = {
|
|
|
|
"exit",
|
|
|
|
"quit"
|
|
|
|
}
|
|
|
|
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-06 14:34:51 +00:00
|
|
|
def print_cute_message():
|
|
|
|
message = get_random_message()
|
|
|
|
try:
|
|
|
|
print(message)
|
2023-04-06 15:45:15 +00:00
|
|
|
except UnicodeEncodeError:
|
2023-04-06 15:54:16 +00:00
|
|
|
message = str(c for c in message if 0 < ord(c) < 127)
|
|
|
|
print(message)
|
2022-11-24 17:25:49 +00:00
|
|
|
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-12 11:13:02 +00:00
|
|
|
def exit_message():
|
|
|
|
print()
|
|
|
|
print_cute_message()
|
2023-04-14 09:22:47 +00:00
|
|
|
print("See you soon! :3")
|
2023-04-12 11:13:02 +00:00
|
|
|
|
|
|
|
|
2023-04-15 17:24:23 +00:00
|
|
|
def paths():
|
|
|
|
print(f"Temp dir:\t{shared.TEMP_DIR}\n"
|
|
|
|
f"Music dir:\t{shared.MUSIC_DIR}\n"
|
|
|
|
f"Log file:\t{shared.LOG_PATH}")
|
|
|
|
print()
|
|
|
|
print_cute_message()
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
2023-04-15 09:54:17 +00:00
|
|
|
def settings(
|
|
|
|
name: str = None,
|
|
|
|
value: str = None,
|
|
|
|
):
|
|
|
|
def modify_setting(_name: str, _value: str, invalid_ok: bool = True) -> bool:
|
|
|
|
try:
|
|
|
|
config.set_name_to_value(_name, _value)
|
2023-04-15 10:42:12 +00:00
|
|
|
except exception.config.SettingException as e:
|
2023-04-15 09:54:17 +00:00
|
|
|
if invalid_ok:
|
2023-04-15 10:42:12 +00:00
|
|
|
print(e)
|
2023-04-15 09:54:17 +00:00
|
|
|
return False
|
|
|
|
else:
|
|
|
|
raise e
|
|
|
|
|
|
|
|
write()
|
|
|
|
return True
|
|
|
|
|
|
|
|
def print_settings():
|
|
|
|
for i, attribute in enumerate(config):
|
|
|
|
print(f"{i:0>2}: {attribute.name}={attribute.value}")
|
|
|
|
|
2023-04-15 10:42:12 +00:00
|
|
|
def modify_setting_by_index(index: int) -> bool:
|
2023-04-15 09:54:17 +00:00
|
|
|
attribute = list(config)[index]
|
|
|
|
|
|
|
|
print()
|
|
|
|
print(attribute)
|
|
|
|
|
2023-04-15 10:42:12 +00:00
|
|
|
input__ = input(f"{attribute.name}=")
|
2023-04-15 09:54:17 +00:00
|
|
|
if not modify_setting(attribute.name, input__.strip()):
|
2023-04-15 10:42:12 +00:00
|
|
|
return modify_setting_by_index(index)
|
|
|
|
|
2023-04-15 09:54:17 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
if name is not None and value is not None:
|
2023-04-15 17:39:25 +00:00
|
|
|
modify_setting(name, value, invalid_ok=True)
|
|
|
|
|
|
|
|
print()
|
|
|
|
print_cute_message()
|
|
|
|
print()
|
|
|
|
return
|
2023-04-15 09:54:17 +00:00
|
|
|
|
|
|
|
while True:
|
|
|
|
print_settings()
|
|
|
|
|
|
|
|
input_ = input("Id of setting to modify: ")
|
|
|
|
print()
|
|
|
|
if input_.isdigit() and int(input_) < len(config):
|
|
|
|
if modify_setting_by_index(int(input_)):
|
2023-04-15 17:39:25 +00:00
|
|
|
print()
|
|
|
|
print_cute_message()
|
|
|
|
print()
|
2023-04-15 09:54:17 +00:00
|
|
|
return
|
|
|
|
else:
|
|
|
|
print("Please input a valid ID.")
|
|
|
|
print()
|
|
|
|
|
|
|
|
|
2023-04-12 11:27:42 +00:00
|
|
|
def cli(
|
|
|
|
genre: str = None,
|
|
|
|
download_all: bool = False,
|
|
|
|
direct_download_url: str = None,
|
|
|
|
command_list: List[str] = None
|
|
|
|
):
|
2023-04-05 08:01:51 +00:00
|
|
|
def get_existing_genre() -> List[str]:
|
|
|
|
"""
|
|
|
|
gets the name of all subdirectories of shared.MUSIC_DIR,
|
|
|
|
but filters out all directories, where the name matches with any patern
|
|
|
|
from shared.NOT_A_GENRE_REGEX.
|
|
|
|
"""
|
|
|
|
existing_genres: List[str] = []
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
# get all subdirectories of MUSIC_DIR, not the files in the dir.
|
|
|
|
existing_subdirectories: List[Path] = [f for f in MUSIC_DIR.iterdir() if f.is_dir()]
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
for subdirectory in existing_subdirectories:
|
|
|
|
name: str = subdirectory.name
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
if not any(re.match(regex_pattern, name) for regex_pattern in NOT_A_GENRE_REGEX):
|
|
|
|
existing_genres.append(name)
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:25:55 +00:00
|
|
|
existing_genres.sort()
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
return existing_genres
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
def get_genre():
|
2023-04-04 20:30:14 +00:00
|
|
|
existing_genres = get_existing_genre()
|
|
|
|
for i, genre_option in enumerate(existing_genres):
|
2023-04-06 15:45:15 +00:00
|
|
|
print(f"{i + 1:0>2}: {genre_option}")
|
2023-04-05 08:01:51 +00:00
|
|
|
|
|
|
|
while True:
|
2023-04-05 08:05:45 +00:00
|
|
|
genre = input("Id or new genre: ")
|
2023-04-05 08:01:51 +00:00
|
|
|
|
|
|
|
if genre.isdigit():
|
|
|
|
genre_id = int(genre) - 1
|
|
|
|
if genre_id >= len(existing_genres):
|
2023-04-06 15:45:15 +00:00
|
|
|
print(f"No genre under the id {genre_id + 1}.")
|
2023-04-05 08:01:51 +00:00
|
|
|
continue
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
return existing_genres[genre_id]
|
|
|
|
|
|
|
|
new_genre = fit_to_file_system(genre)
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-05 08:01:51 +00:00
|
|
|
agree_inputs = {"y", "yes", "ok"}
|
|
|
|
verification = input(f"create new genre \"{new_genre}\"? (Y/N): ").lower()
|
|
|
|
if verification in agree_inputs:
|
|
|
|
return new_genre
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-05-23 08:55:44 +00:00
|
|
|
def next_search(_search: download.Search, query: str) -> bool:
|
2023-04-04 18:19:29 +00:00
|
|
|
"""
|
2023-04-04 19:26:37 +00:00
|
|
|
:param _search:
|
2023-04-04 18:19:29 +00:00
|
|
|
:param query:
|
|
|
|
:return exit in the next step:
|
|
|
|
"""
|
2023-04-05 08:12:02 +00:00
|
|
|
nonlocal genre
|
|
|
|
nonlocal download_all
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-03-30 08:49:17 +00:00
|
|
|
query: str = query.strip()
|
|
|
|
parsed: str = query.lower()
|
2023-04-04 18:19:29 +00:00
|
|
|
|
|
|
|
if parsed in EXIT_COMMANDS:
|
|
|
|
return True
|
2023-04-04 20:30:14 +00:00
|
|
|
|
2023-03-30 08:49:17 +00:00
|
|
|
if parsed == ".":
|
2023-04-04 18:19:29 +00:00
|
|
|
return False
|
2023-03-30 08:49:17 +00:00
|
|
|
if parsed == "..":
|
2023-04-04 19:26:37 +00:00
|
|
|
_search.goto_previous()
|
2023-04-04 18:19:29 +00:00
|
|
|
return False
|
2023-04-04 20:30:14 +00:00
|
|
|
|
2023-03-30 08:49:17 +00:00
|
|
|
if parsed.isdigit():
|
2023-04-04 19:26:37 +00:00
|
|
|
_search.choose_index(int(parsed))
|
2023-04-04 18:19:29 +00:00
|
|
|
return False
|
2023-04-04 20:30:14 +00:00
|
|
|
|
2023-03-31 08:34:29 +00:00
|
|
|
if parsed in DOWNLOAD_COMMANDS:
|
2023-04-05 08:12:02 +00:00
|
|
|
r = _search.download_chosen(genre=genre, download_all=download_all)
|
2023-04-04 19:18:56 +00:00
|
|
|
|
2023-04-04 18:19:29 +00:00
|
|
|
print()
|
|
|
|
print(r)
|
2023-04-04 19:18:56 +00:00
|
|
|
print()
|
|
|
|
|
|
|
|
return not r.is_mild_failure
|
2023-04-04 18:00:21 +00:00
|
|
|
|
2023-04-04 19:27:27 +00:00
|
|
|
url = re.match(URL_REGEX, query)
|
2023-03-30 10:43:43 +00:00
|
|
|
if url is not None:
|
2023-04-04 19:26:37 +00:00
|
|
|
if not _search.search_url(url.string):
|
2023-04-04 18:19:29 +00:00
|
|
|
print("The given url couldn't be found.")
|
|
|
|
return False
|
2023-04-04 20:30:14 +00:00
|
|
|
|
2023-04-04 19:26:37 +00:00
|
|
|
page = _search.get_page_from_query(parsed)
|
2023-03-30 08:49:17 +00:00
|
|
|
if page is not None:
|
2023-04-04 19:26:37 +00:00
|
|
|
_search.choose_page(page)
|
2023-04-04 18:19:29 +00:00
|
|
|
return False
|
2023-04-04 20:30:14 +00:00
|
|
|
|
2023-03-30 08:49:17 +00:00
|
|
|
# if everything else is not valid search
|
2023-04-04 19:26:37 +00:00
|
|
|
_search.search(query)
|
2023-04-04 18:19:29 +00:00
|
|
|
return False
|
|
|
|
|
2023-04-05 08:05:45 +00:00
|
|
|
if genre is None:
|
|
|
|
genre = get_genre()
|
|
|
|
print()
|
2023-04-06 15:45:15 +00:00
|
|
|
|
2023-04-06 14:34:51 +00:00
|
|
|
print_cute_message()
|
2023-04-05 08:08:27 +00:00
|
|
|
print()
|
|
|
|
print(f"Downloading to: \"{genre}\"")
|
|
|
|
print()
|
2023-04-04 20:30:14 +00:00
|
|
|
|
2023-05-23 08:55:44 +00:00
|
|
|
search = download.Search()
|
2023-03-30 08:49:17 +00:00
|
|
|
|
2023-04-12 11:13:02 +00:00
|
|
|
# directly download url
|
|
|
|
if direct_download_url is not None:
|
|
|
|
if search.search_url(direct_download_url):
|
|
|
|
r = search.download_chosen(genre=genre, download_all=download_all)
|
|
|
|
print()
|
|
|
|
print(r)
|
|
|
|
print()
|
|
|
|
else:
|
|
|
|
print(f"Sorry, could not download the url: {direct_download_url}")
|
|
|
|
|
|
|
|
exit_message()
|
|
|
|
return
|
|
|
|
|
2023-04-12 11:27:42 +00:00
|
|
|
# run one command after another from the command list
|
|
|
|
if command_list is not None:
|
|
|
|
for command in command_list:
|
|
|
|
print(f">> {command}")
|
|
|
|
if next_search(search, command):
|
|
|
|
break
|
|
|
|
print(search)
|
|
|
|
|
|
|
|
exit_message()
|
|
|
|
return
|
|
|
|
|
|
|
|
# the actual cli
|
2023-03-29 15:24:02 +00:00
|
|
|
while True:
|
2023-04-05 08:25:55 +00:00
|
|
|
if next_search(search, input(">> ")):
|
2023-04-04 18:00:21 +00:00
|
|
|
break
|
2023-03-29 15:24:02 +00:00
|
|
|
print(search)
|
2023-04-04 18:19:29 +00:00
|
|
|
|
2023-04-12 11:13:02 +00:00
|
|
|
exit_message()
|