diff --git a/fastanime/Utility/utils.py b/fastanime/Utility/utils.py index 1d69481..3fe958d 100644 --- a/fastanime/Utility/utils.py +++ b/fastanime/Utility/utils.py @@ -4,6 +4,12 @@ import shutil from datetime import datetime from functools import lru_cache +from fuzzywuzzy import fuzz + +from fastanime.libs.anilist.anilist_data_schema import AnilistBaseMediaDataSchema + +from .data import anime_normalizer + # TODO: make it use color_text instead of fixed vals # from .kivy_markup_helper import color_text @@ -98,6 +104,32 @@ def sanitize_filename(filename: str): return sanitized +def anime_title_percentage_match( + possible_user_requested_anime_title: str, anime: AnilistBaseMediaDataSchema +) -> float: + """Returns the percentage match between the possible title and user title + + Args: + possible_user_requested_anime_title (str): an Animdl search result title + title (str): the anime title the user wants + + Returns: + int: the percentage match + """ + if normalized_anime_title := anime_normalizer.get( + possible_user_requested_anime_title + ): + possible_user_requested_anime_title = normalized_anime_title + # compares both the romaji and english names and gets highest Score + title_a = str(anime["title"]["romaji"]) + title_b = str(anime["title"]["english"]) + percentage_ratio = max( + fuzz.ratio(title_a.lower(), possible_user_requested_anime_title.lower()), + fuzz.ratio(title_b.lower(), possible_user_requested_anime_title.lower()), + ) + return percentage_ratio + + if __name__ == "__main__": # Example usage unsafe_filename = "CON:example?file*name.txt" diff --git a/fastanime/cli/__init__.py b/fastanime/cli/__init__.py index 0699e0b..514a41d 100644 --- a/fastanime/cli/__init__.py +++ b/fastanime/cli/__init__.py @@ -48,7 +48,8 @@ signal.signal(signal.SIGINT, handle_exit) @click.option("-c/-no-c", "--continue/--no-continue", "continue_", type=bool) @click.option("-q", "--quality", type=int) @click.option("-t", "--translation_type") -@click.option("-a", "--auto-next", type=bool) +@click.option("-A/-no-A", "--auto-next/--no-auto-next", type=bool) +@click.option("-a/-no-a", "--auto-select/--no-auto-select", type=bool) @click.option( "-S", "--sort-by", @@ -63,6 +64,7 @@ def run_cli( translation_type, quality, auto_next, + auto_select, sort_by, downloads_dir, ): @@ -75,6 +77,8 @@ def run_cli( ctx.obj.quality = quality if auto_next: ctx.obj.auto_next = auto_next + if auto_select: + ctx.obj.auto_select = auto_select if sort_by: ctx.obj.sort_by = sort_by if downloads_dir: diff --git a/fastanime/cli/commands/download.py b/fastanime/cli/commands/download.py index 6639a17..66ebed8 100644 --- a/fastanime/cli/commands/download.py +++ b/fastanime/cli/commands/download.py @@ -1,4 +1,6 @@ import click +from fuzzywuzzy import fuzz +from rich import print from ...libs.anime_provider.allanime.api import anime_provider from ...libs.anime_provider.types import Anime @@ -35,9 +37,15 @@ def download(config: Config, anime_title, episode_range): search_result["title"]: search_result for search_result in search_results } - search_result = fzf.run( - list(search_results_.keys()), "Please Select title: ", "FastAnime" - ) + if config.auto_select: + search_result = max( + search_results_.keys(), key=lambda title: fuzz.ratio(title, anime_title) + ) + print("[cyan]Auto selecting:[/] ", search_result) + else: + search_result = fzf.run( + list(search_results_.keys()), "Please Select title: ", "FastAnime" + ) anime: Anime = anime_provider.get_anime(search_results_[search_result]["id"]) @@ -51,7 +59,7 @@ def download(config: Config, anime_title, episode_range): try: episode = str(episode) if episode not in episodes: - print("Episode not found skipping") + print(f"[cyan]Warning[/]: Episode {episode} not found, skipping") continue streams = anime_provider.get_episode_streams( anime, episode, config.translation_type diff --git a/fastanime/cli/commands/search.py b/fastanime/cli/commands/search.py index fb1cdfb..dfadaec 100644 --- a/fastanime/cli/commands/search.py +++ b/fastanime/cli/commands/search.py @@ -1,4 +1,6 @@ import click +from fuzzywuzzy import fuzz +from rich import print from ...cli.config import Config from ...libs.anime_provider.allanime.api import anime_provider @@ -24,16 +26,23 @@ def search( search_results_ = { search_result["title"]: search_result for search_result in search_results } + if config.auto_select: + search_result = max( + search_results_.keys(), key=lambda title: fuzz.ratio(title, anime_title) + ) + print("[cyan]Auto Selecting:[/] ", search_result) - search_result = fzf.run( - list(search_results_.keys()), "Please Select title: ", "FastAnime" - ) + else: + search_result = fzf.run( + list(search_results_.keys()), "Please Select title: ", "FastAnime" + ) anime: Anime = anime_provider.get_anime(search_results_[search_result]["id"]) def stream_anime(): episodes = anime["availableEpisodesDetail"][config.translation_type] - episode = fzf.run(episodes, "Select an episode: ", header="Episodes") + + episode = fzf.run(episodes, "Select an episode: ", header=search_result) streams = anime_provider.get_episode_streams( anime, episode, config.translation_type ) diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index 88368e8..1a283a7 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -21,6 +21,7 @@ class Config(object): "continue_from_history": "False", "quality": "0", "auto_next": "True", + "auto_select": "True", "sort_by": "search match", "downloads_dir": USER_VIDEOS_DIR, "translation_type": "sub", @@ -41,6 +42,7 @@ class Config(object): self.sort_by = self.get_sort_by() self.continue_from_history = self.get_continue_from_history() self.auto_next = self.get_auto_next() + self.auto_select = self.get_auto_select() self.quality = self.get_quality() self.server = self.get_server() self.preferred_language = self.get_preferred_language() @@ -84,6 +86,9 @@ class Config(object): def get_auto_next(self): return self.configparser.getboolean("stream", "auto_next") + def get_auto_select(self): + return self.configparser.getboolean("stream", "auto_select") + def get_quality(self): return self.configparser.getint("stream", "quality") diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index 4187641..075e5cb 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -14,7 +14,11 @@ from ...libs.anime_provider.types import Anime, SearchResult, Server from ...libs.fzf import fzf from ...Utility import anilist_data_helper from ...Utility.data import anime_normalizer -from ...Utility.utils import remove_html_tags, sanitize_filename +from ...Utility.utils import ( + anime_title_percentage_match, + remove_html_tags, + sanitize_filename, +) from ..config import Config from ..utils.mpv import mpv from ..utils.tools import QueryDict, exit_app @@ -303,6 +307,7 @@ def fetch_episode(config: Config, anilist_config: QueryDict): continue_from_history: bool = config.continue_from_history user_watch_history: dict = config.watch_history anime_id: int = anilist_config.anime_id + anime_title: str = anilist_config.anime_title # internal config anime: Anime = anilist_config.anime @@ -317,7 +322,7 @@ def fetch_episode(config: Config, anilist_config: QueryDict): episode_number = fzf.run( [*episodes, "Back"], prompt="Select Episode:", - header="Episodes", + header=anime_title, ) if episode_number == "Back": @@ -354,6 +359,8 @@ def provide_anime(config: Config, anilist_config: QueryDict): # internal config selected_anime_title = anilist_config.selected_anime_title + anime_data: AnilistBaseMediaDataSchema = anilist_config.selected_anime_anilist + # search and get the requested title from provider search_results = anime_provider.search_for_anime( selected_anime_title, translation_type @@ -371,15 +378,22 @@ def provide_anime(config: Config, anilist_config: QueryDict): ): _title = _title - anime_title = fzf.run( - [*search_results.keys(), "Back"], - prompt="Select Search Result:", - header="Anime Search Results", - ) + if config.auto_select: + anime_title = max( + search_results.keys(), + key=lambda title: anime_title_percentage_match(title, anime_data), + ) + print(f"[cyan]Auto selecting[/]: {anime_title}") + else: + anime_title = fzf.run( + [*search_results.keys(), "Back"], + prompt="Select Search Result:", + header="Anime Search Results", + ) - if anime_title == "Back": - anilist_options(config, anilist_config) - return + if anime_title == "Back": + anilist_options(config, anilist_config) + return anilist_config.anime_title = anime_normalizer.get(anime_title) or anime_title anilist_config._anime = search_results[anime_title] fetch_anime_episode(config, anilist_config) diff --git a/fastanime/libs/anime_provider/allanime/api.py b/fastanime/libs/anime_provider/allanime/api.py index dc0aef8..602a0ae 100644 --- a/fastanime/libs/anime_provider/allanime/api.py +++ b/fastanime/libs/anime_provider/allanime/api.py @@ -151,7 +151,7 @@ class AllAnimeAPI: yield { "server": "gogoanime", "episode_title": allanime_episode["notes"] - or f"{anime["title"]} Episode:{episode_number}", + or f"{anime["title"]}: Episode {episode_number}", "links": resp.json()["links"], } # pyright:ignore case "Kir": @@ -160,7 +160,7 @@ class AllAnimeAPI: yield { "server": "wetransfer", "episode_title": allanime_episode["notes"] - or f"{anime["title"]} Episode:{episode_number}", + or f"{anime["title"]}: Episode {episode_number}", "links": resp.json()["links"], } # pyright:ignore case "S-mp4": @@ -169,7 +169,7 @@ class AllAnimeAPI: yield { "server": "sharepoint", "episode_title": allanime_episode["notes"] - or f"{anime["title"]} Episode:{episode_number}", + or f"{anime["title"]}: Episode {episode_number}", "links": resp.json()["links"], } # pyright:ignore case "Sak": @@ -178,7 +178,7 @@ class AllAnimeAPI: yield { "server": "dropbox", "episode_title": allanime_episode["notes"] - or f"{anime["title"]} Episode:{episode_number}", + or f"{anime["title"]}: Episode {episode_number}", "links": resp.json()["links"], } # pyright:ignore except Timeout: