mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 15:50:01 -08:00
chore: clean up
This commit is contained in:
@@ -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"
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
)
|
||||
Reference in New Issue
Block a user