Compare commits
4 Commits
097211b3cd
...
feature/cl
| Author | SHA1 | Date | |
|---|---|---|---|
| e53e50b5d2 | |||
| 240bd105f0 | |||
| ed2eeabd6a | |||
| b236291378 |
3
music_kraken/__meta__.py
Normal file
3
music_kraken/__meta__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
PROGRAMM: str = "music-kraken"
|
||||
DESCRIPTION: str = """This program will first get the metadata of various songs from metadata providers like musicbrainz, and then search for download links on pages like bandcamp.
|
||||
Then it will download the song and edit the metadata accordingly."""
|
||||
@@ -1,5 +0,0 @@
|
||||
from .informations import print_paths
|
||||
from .main_downloader import download
|
||||
from .options.settings import settings
|
||||
from .options.frontend import set_frontend
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Generator, List, Set, Type, Union
|
||||
|
||||
from ..download import Downloader, Page, components
|
||||
from ..utils.config import main_settings
|
||||
from .utils import ask_for_bool, cli_function
|
||||
|
||||
|
||||
class GenreIO(components.HumanIO):
|
||||
@staticmethod
|
||||
def ask_to_create(option: components.Option) -> bool:
|
||||
output()
|
||||
return ask_for_bool(f"create the genre {BColors.OKBLUE.value}{option.value}{BColors.ENDC.value}")
|
||||
|
||||
@staticmethod
|
||||
def not_found(key: str) -> None:
|
||||
output(f"\ngenre {BColors.BOLD.value}{key}{BColors.ENDC.value} not found\n", color=BColors.FAIL)
|
||||
|
||||
|
||||
def _genre_generator() -> Generator[str, None, None]:
|
||||
def is_valid_genre(genre: Path) -> bool:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if not genre.is_dir():
|
||||
return False
|
||||
|
||||
if any(re.match(regex_pattern, genre.name) for regex_pattern in main_settings["not_a_genre_regex"]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
for genre in filter(is_valid_genre, main_settings["music_directory"].iterdir()):
|
||||
yield genre.name
|
||||
|
||||
def get_genre() -> str:
|
||||
select_genre = components.Select(
|
||||
human_io=GenreIO,
|
||||
can_create_options=True,
|
||||
data=_genre_generator(),
|
||||
)
|
||||
genre: Optional[components.Option[str]] = None
|
||||
|
||||
while genre is None:
|
||||
print(select_genre.pprint())
|
||||
print()
|
||||
|
||||
genre = select_genre.choose(input("> "))
|
||||
|
||||
return genre.value
|
||||
@@ -1,47 +0,0 @@
|
||||
from ..utils import BColors
|
||||
from ..utils.shared import get_random_message
|
||||
|
||||
|
||||
def cli_function(function):
|
||||
def wrapper(*args, **kwargs):
|
||||
silent = kwargs.get("no_cli", False)
|
||||
if "no_cli" in kwargs:
|
||||
del kwargs["no_cli"]
|
||||
|
||||
if silent:
|
||||
return function(*args, **kwargs)
|
||||
return
|
||||
|
||||
code = 0
|
||||
|
||||
print_cute_message()
|
||||
print()
|
||||
try:
|
||||
code = function(*args, **kwargs)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nRaise an issue if I fucked up:\nhttps://github.com/HeIIow2/music-downloader/issues")
|
||||
|
||||
finally:
|
||||
print()
|
||||
print_cute_message()
|
||||
print("See you soon! :3")
|
||||
|
||||
exit()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def print_cute_message():
|
||||
message = get_random_message()
|
||||
try:
|
||||
print(message)
|
||||
except UnicodeEncodeError:
|
||||
message = str(c for c in message if 0 < ord(c) < 127)
|
||||
print(message)
|
||||
|
||||
|
||||
AGREE_INPUTS = {"y", "yes", "ok"}
|
||||
def ask_for_bool(msg: str) -> bool:
|
||||
i = input(f"{msg} ({BColors.OKGREEN.value}Y{BColors.ENDC.value}/{BColors.FAIL.value}N{BColors.ENDC.value})? ").lower()
|
||||
return i in AGREE_INPUTS
|
||||
|
||||
82
music_kraken/development_cli/__init__.py
Normal file
82
music_kraken/development_cli/__init__.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import argparse
|
||||
from functools import cached_property
|
||||
|
||||
from ..__meta__ import DESCRIPTION, PROGRAMM
|
||||
from ..download import Downloader
|
||||
from ..utils import BColors
|
||||
from ..utils.string_processing import unify
|
||||
from .utils import HELP_MESSAGE, ask_for_bool, ask_for_create
|
||||
|
||||
|
||||
class DevelopmentCli:
|
||||
def __init__(self, args: argparse.Namespace):
|
||||
self.args = args
|
||||
|
||||
if args.genre:
|
||||
self.genre = args.genre
|
||||
|
||||
self.downloader: Downloader = Downloader()
|
||||
|
||||
@cached_property
|
||||
def genre(self) -> str:
|
||||
"""This is a cached property, which means if it isn't set in the constructor or before it is accessed,
|
||||
the program will be thrown in a shell
|
||||
|
||||
Returns:
|
||||
str: the genre that should be used
|
||||
"""
|
||||
option_string = f"{BColors.HEADER}Genres{BColors.ENDC}"
|
||||
genre_map = {}
|
||||
|
||||
_string_list = []
|
||||
for i, genre in enumerate(self.downloader.get_existing_genres()):
|
||||
option_string += f"\n{BColors.BOLD}{i}{BColors.ENDC}: {genre}"
|
||||
|
||||
genre_map[str(i)] = genre
|
||||
genre_map[unify(genre)] = genre
|
||||
|
||||
genre = None
|
||||
while genre is None:
|
||||
print(option_string)
|
||||
print()
|
||||
|
||||
i = input("> ")
|
||||
u = unify(i)
|
||||
if u in genre_map:
|
||||
genre = genre_map[u]
|
||||
break
|
||||
|
||||
if ask_for_create("genre", i):
|
||||
genre = i
|
||||
break
|
||||
|
||||
return genre
|
||||
|
||||
|
||||
def help_screen(self) -> None:
|
||||
print(HELP_MESSAGE)
|
||||
|
||||
def shell(self) -> None:
|
||||
print(f"Welcome to the {PROGRAMM} shell!")
|
||||
print(f"Type '{BColors.OKBLUE}help{BColors.ENDC}' for a list of commands.")
|
||||
print("")
|
||||
|
||||
while True:
|
||||
i = input("> ")
|
||||
if i == "help":
|
||||
self.help_screen()
|
||||
elif i == "genre":
|
||||
self.genre
|
||||
elif i == "exit":
|
||||
break
|
||||
else:
|
||||
print("Unknown command. Type 'help' for a list of commands.")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=PROGRAMM,
|
||||
description=DESCRIPTION,
|
||||
epilog='This is just a development cli. The real frontend is coming soon.'
|
||||
)
|
||||
parser.add_argument('--genre', '-g', action='store_const', required=False, help="choose a genre to download from")
|
||||
args = parser.parse_args()
|
||||
85
music_kraken/development_cli/utils.py
Normal file
85
music_kraken/development_cli/utils.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from ..utils import BColors
|
||||
from ..utils.shared import get_random_message
|
||||
|
||||
|
||||
def cli_function(function):
|
||||
def wrapper(*args, **kwargs):
|
||||
silent = kwargs.get("no_cli", False)
|
||||
if "no_cli" in kwargs:
|
||||
del kwargs["no_cli"]
|
||||
|
||||
if silent:
|
||||
return function(*args, **kwargs)
|
||||
return
|
||||
|
||||
code = 0
|
||||
|
||||
print_cute_message()
|
||||
print()
|
||||
try:
|
||||
code = function(*args, **kwargs)
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nRaise an issue if I fucked up:\nhttps://github.com/HeIIow2/music-downloader/issues")
|
||||
|
||||
finally:
|
||||
print()
|
||||
print_cute_message()
|
||||
print("See you soon! :3")
|
||||
|
||||
exit()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def print_cute_message():
|
||||
message = get_random_message()
|
||||
try:
|
||||
print(message)
|
||||
except UnicodeEncodeError:
|
||||
message = str(c for c in message if 0 < ord(c) < 127)
|
||||
print(message)
|
||||
|
||||
|
||||
def highlight_placeholder(text: str) -> str:
|
||||
return text.replace("<", f"{BColors.BOLD}<").replace(">", f">{BColors.ENDC}")
|
||||
|
||||
|
||||
HELP_MESSAGE = highlight_placeholder(f"""{BColors.HEADER}To search:{BColors.ENDC}
|
||||
> s: <query/url>
|
||||
> s: https://musify.club/release/some-random-release-183028492
|
||||
> s: #a <artist> #r <release> #t <track>
|
||||
|
||||
If you found the same object twice from different sources you can merge those objects.
|
||||
Then it will use those sources. To do so, use the {BColors.BOLD}m{BColors.ENDC} command.
|
||||
|
||||
{BColors.HEADER}To download:{BColors.ENDC}
|
||||
> d: <id/url>
|
||||
> dm: 0, 3, 4 # merge all objects into one and download this object
|
||||
> d: 1
|
||||
> d: https://musify.club/release/some-random-release-183028492
|
||||
|
||||
{BColors.HEADER}To inspect an object:{BColors.ENDC}
|
||||
If you inspect an object, you see its adjacent object. This means for example the releases of an artist, or the tracks of a release.
|
||||
You can also merge objects with the {BColors.BOLD}m{BColors.ENDC} command here.
|
||||
|
||||
> g: <id/url>
|
||||
> gm: 0, 3, 4 # merge all objects into one and inspect this object
|
||||
> g: 1
|
||||
> g: https://musify.club/release/some-random-release-183028492""")
|
||||
|
||||
|
||||
class COMMANDS:
|
||||
AGREE = {"y", "yes", "ok"}
|
||||
DISAGREE = {"n", "no"}
|
||||
EXIT = {"exit"}
|
||||
HELP = {"help", "h"}
|
||||
|
||||
|
||||
def ask_for_bool(msg: str) -> bool:
|
||||
i = input(f"{msg} ({BColors.OKGREEN.value}Y{BColors.ENDC.value}/{BColors.FAIL.value}N{BColors.ENDC.value})? ").lower()
|
||||
return i in COMMANDS.AGREE
|
||||
|
||||
|
||||
def ask_for_create(name: str, value: str) -> bool:
|
||||
return ask_for_bool(f"Do you want to create the {name} {BColors.OKBLUE}{value}{BColors.ENDC}?")
|
||||
|
||||
@@ -415,6 +415,31 @@ class Downloader:
|
||||
|
||||
return source.page.fetch_object_from_source(source=source, **kwargs)
|
||||
|
||||
# misc function
|
||||
|
||||
def get_existing_genres(self) -> Generator[str, None, None]:
|
||||
"""Yields every existing genre, for the user to select from.
|
||||
|
||||
Yields:
|
||||
Generator[str, None, None]: a generator that yields every existing genre.
|
||||
"""
|
||||
|
||||
def is_valid_genre(genre: Path) -> bool:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
if not genre.is_dir():
|
||||
return False
|
||||
|
||||
if any(re.match(regex_pattern, genre.name) for regex_pattern in main_settings["not_a_genre_regex"]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
for genre in filter(is_valid_genre, main_settings["music_directory"].iterdir()):
|
||||
yield genre.name
|
||||
|
||||
class Page:
|
||||
REGISTER = True
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BColors(Enum):
|
||||
class BColors:
|
||||
# https://stackoverflow.com/a/287944
|
||||
HEADER = "\033[95m"
|
||||
OKBLUE = "\033[94m"
|
||||
|
||||
Reference in New Issue
Block a user