diff --git a/fastanime/cli/utils/anilist.py b/fastanime/cli/utils/anilist.py deleted file mode 100644 index 9cd4525..0000000 --- a/fastanime/cli/utils/anilist.py +++ /dev/null @@ -1,43 +0,0 @@ -import re -from datetime import datetime -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from ..libs.anilist.types import AnilistDateObject, AnilistMediaNextAiringEpisode - -COMMA_REGEX = re.compile(r"([0-9]{3})(?=\d)") - - -# TODO: Add formating options for the final date -def format_anilist_date_object(anilist_date_object: "AnilistDateObject"): - if anilist_date_object and anilist_date_object["day"]: - return f"{anilist_date_object['day']}/{anilist_date_object['month']}/{anilist_date_object['year']}" - else: - return "Unknown" - - -def format_anilist_timestamp(anilist_timestamp: int | None): - if anilist_timestamp: - return datetime.fromtimestamp(anilist_timestamp).strftime("%d/%m/%Y %H:%M:%S") - else: - return "Unknown" - - -def format_list_data_with_comma(data: list | None): - if data: - return ", ".join(data) - else: - return "None" - - -def format_number_with_commas(number: int | None): - if not number: - return "0" - return COMMA_REGEX.sub(lambda match: f"{match.group(1)},", str(number)[::-1])[::-1] - - -def extract_next_airing_episode(airing_episode: "AnilistMediaNextAiringEpisode"): - if airing_episode: - return f"{airing_episode['episode']} on {format_anilist_timestamp(airing_episode['airingAt'])}" - else: - return "Completed" diff --git a/fastanime/cli/utils/tools.py b/fastanime/cli/utils/tools.py deleted file mode 100644 index 11aabea..0000000 --- a/fastanime/cli/utils/tools.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Callable - from typing import Any - - from ...libs.anilist.types import AnilistBaseMediaDataSchema - from ...libs.anime_provider.types import Anime, EpisodeStream, SearchResult, Server - - -class FastAnimeRuntimeState: - """A class that manages fastanime runtime during anilist command runtime""" - - provider_current_episode_stream_link: str - provider_current_server: "Server" - provider_current_server_name: str - provider_available_episodes: list[str] - provider_current_episode_number: str - provider_server_episode_streams: list["EpisodeStream"] - provider_anime_title: str - provider_anime: "Anime" - provider_anime_search_result: "SearchResult" - progress_tracking: str = "" - - selected_anime_anilist: "AnilistBaseMediaDataSchema" - selected_anime_id_anilist: int - selected_anime_title_anilist: str - # current_anilist_data: "AnilistDataSchema | AnilistMediaList" - anilist_results_data: "Any" - current_page: int - current_data_loader: "Callable" - - -def exit_app(exit_code=0, *args, **kwargs): - import sys - - from rich.console import Console - - from ...constants import APP_NAME, ICON_PATH, USER_NAME - - console = Console() - if not console.is_terminal: - try: - from plyer import notification - except ImportError: - print( - "Plyer is not installed; install it for desktop notifications to be enabled" - ) - exit(1) - notification.notify( - app_name=APP_NAME, - app_icon=ICON_PATH, - message=f"Have a good day {USER_NAME}", - title="Shutting down", - ) # pyright:ignore - else: - console.clear() - console.print("Have a good day :smile:", USER_NAME) - sys.exit(exit_code) diff --git a/fastanime/cli/utils/utils.py b/fastanime/cli/utils/utils.py deleted file mode 100644 index 6264864..0000000 --- a/fastanime/cli/utils/utils.py +++ /dev/null @@ -1,200 +0,0 @@ -import logging -import shutil -from typing import TYPE_CHECKING - -from InquirerPy import inquirer - -from fastanime.constants import S_PLATFORM - -logger = logging.getLogger(__name__) -if TYPE_CHECKING: - from ...libs.anime_provider.types import EpisodeStream - -# Define ANSI escape codes as constants -RESET = "\033[0m" -BOLD = "\033[1m" -INVISIBLE_CURSOR = "\033[?25l" -VISIBLE_CURSOR = "\033[?25h" -UNDERLINE = "\033[4m" - -# ESC[38;2;{r};{g};{b}m -BG_GREEN = "\033[48;2;120;233;12;m" -GREEN = "\033[38;2;45;24;45;m" - - -def get_requested_quality_or_default_to_first(url, quality): - import yt_dlp - - with yt_dlp.YoutubeDL({"quiet": True, "silent": True, "no_warnings": True}) as ydl: - m3u8_info = ydl.extract_info(url, False) - if not m3u8_info: - return - - m3u8_formats = m3u8_info["formats"] - quality = int(quality) - quality_u = quality - 80 - quality_l = quality + 80 - for m3u8_format in m3u8_formats: - if m3u8_format["height"] == quality or ( - m3u8_format["height"] < quality_u and m3u8_format["height"] > quality_l - ): - return m3u8_format["url"] - return m3u8_formats[0]["url"] - - -def move_preferred_subtitle_lang_to_top(sub_list, lang_str): - """Moves the dictionary with the given ID to the front of the list. - - Args: - sub_list: list of subs - lang_str: the sub lang pref - - Returns: - The modified list. - """ - import re - - for i, d in enumerate(sub_list): - if re.search(lang_str, d["language"], re.IGNORECASE): - sub_list.insert(0, sub_list.pop(i)) - break - return sub_list - - -def filter_by_quality(quality: str, stream_links: "list[EpisodeStream]", default=True): - """Helper function used to filter a list of EpisodeStream objects to one that has a corresponding quality - - Args: - quality: the quality to use - stream_links: a list of EpisodeStream objects - - Returns: - an EpisodeStream object or None incase the quality was not found - """ - for stream_link in stream_links: - q = float(quality) - Q = float(stream_link["quality"]) - # some providers have inaccurate/weird/non-standard eg qualities 718 instead of 720 - if Q <= q + 80 and Q >= q - 80: - return stream_link - if stream_links and default: - from rich import print - - try: - print("[yellow bold]WARNING Qualities were:[/] ", stream_links) - print( - "[cyan bold]Using default of quality:[/] ", - stream_links[0]["quality"], - ) - return stream_links[0] - except Exception as e: - print(e) - return - - -def format_bytes_to_human(num_of_bytes: float, suffix: str = "B"): - """Helper function used to format bytes to human - - Args: - num_of_bytes: the number of bytes to format - suffix: the suffix to use - - Returns: - formated bytes - """ - for unit in ("", "K", "M", "G", "T", "P", "E", "Z"): - if abs(num_of_bytes) < 1024.0: - return f"{num_of_bytes:3.1f}{unit}{suffix}" - num_of_bytes /= 1024.0 - return f"{num_of_bytes:.1f}Yi{suffix}" - - -def get_true_fg(string: str, r, g, b, bold: bool = True) -> str: - """Custom helper function that enables colored text in the terminal - - Args: - bold: whether to bolden the text - string: string to color - r: red - g: green - b: blue - - Returns: - colored string - """ - # NOTE: Currently only supports terminals that support true color - if bold: - return f"{BOLD}\033[38;2;{r};{g};{b};m{string}{RESET}" - else: - return f"\033[38;2;{r};{g};{b};m{string}{RESET}" - - -def get_true_bg(string, r: int, g: int, b: int) -> str: - return f"\033[48;2;{r};{g};{b};m{string}{RESET}" - - -def fuzzy_inquirer(choices: list, prompt: str, **kwargs): - """helper function that enables easier interaction with InquirerPy lib - - Args: - choices: the choices to prompt - prompt: the prompt string to use - **kwargs: other options to pass to fuzzy_inquirer - - Returns: - a choice - """ - from click import clear - - clear() - action = inquirer.fuzzy( # pyright:ignore - prompt, - choices, - height="100%", - border=True, - validate=lambda result: result in choices, - **kwargs, - ).execute() - return action - - -def which_win32_gitbash(): - """Helper function that returns absolute path to the git bash executable - (came with Git for Windows) on Windows - - Returns: - the path to the git bash executable or None if not found - """ - from os import path - - gb_path = shutil.which("bash") - - # Windows came with its own bash.exe but it's just an entry point for WSL not Git Bash - if gb_path and not path.dirname(gb_path).lower().endswith("windows\\system32"): - return gb_path - - git_path = shutil.which("git") - - if git_path: - if path.dirname(git_path).endswith("cmd"): - gb_path = path.abspath( - path.join(path.dirname(git_path), "..", "bin", "bash.exe") - ) - else: - gb_path = path.join(path.dirname(git_path), "bash.exe") - - if path.exists(gb_path): - return gb_path - - -def which_bashlike(): - """Helper function that returns absolute path to the bash executable for the current platform - - Returns: - the path to the bash executable or None if not found - """ - return ( - (shutil.which("bash") or "bash") - if S_PLATFORM != "win32" - else which_win32_gitbash() - )