diff --git a/fastanime/cli/commands/downloads.py b/fastanime/cli/commands/downloads.py index 421ca73..42d5888 100644 --- a/fastanime/cli/commands/downloads.py +++ b/fastanime/cli/commands/downloads.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: def downloads(config: "Config", path: bool): import os - from ...cli.utils.mpv import mpv + from ...cli.utils.mpv import run_mpv from ...libs.fzf import fzf from ...libs.rofi import Rofi from ..utils.tools import exit_app @@ -41,7 +41,7 @@ def downloads(config: "Config", path: bool): exit_app() return playlist = os.path.join(USER_VIDEOS_DIR, playlist_name) - mpv(playlist) + run_mpv(playlist) stream() stream() diff --git a/fastanime/cli/commands/search.py b/fastanime/cli/commands/search.py index 8cb375f..dd33465 100644 --- a/fastanime/cli/commands/search.py +++ b/fastanime/cli/commands/search.py @@ -23,7 +23,7 @@ def search(config: Config, anime_title: str, episode_range: str): from ...libs.anime_provider.types import Anime from ...libs.fzf import fzf from ...libs.rofi import Rofi - from ..utils.mpv import mpv + from ..utils.mpv import run_mpv from ..utils.tools import exit_app from ..utils.utils import clear, fuzzy_inquirer @@ -138,7 +138,7 @@ def search(config: Config, anime_title: str, episode_range: str): print(f"[purple]Now Playing:[/] {search_result} Episode {episode}") - mpv(link, search_result) + run_mpv(link, search_result) stream_anime() stream_anime() diff --git a/fastanime/cli/config.py b/fastanime/cli/config.py index 6a9f104..7002e65 100644 --- a/fastanime/cli/config.py +++ b/fastanime/cli/config.py @@ -47,6 +47,7 @@ class Config(object): "rofi_theme": "", "rofi_theme_input": "", "rofi_theme_confirm": "", + "use_mpv_mod": "true", } ) self.configparser.add_section("stream") @@ -71,6 +72,7 @@ class Config(object): self.continue_from_history = self.get_continue_from_history() self.auto_next = self.get_auto_next() self.auto_select = self.get_auto_select() + self.use_mpv_mod = self.get_use_mpv_mod() self.quality = self.get_quality() self.notification_duration = self.get_notification_duration() self.error = self.get_error() @@ -170,6 +172,9 @@ class Config(object): def get_quality(self): return self.configparser.getint("stream", "quality") + def get_use_mpv_mod(self): + return self.configparser.getboolean("stream", "use_mpv_mod") + def get_notification_duration(self): return self.configparser.getint("general", "notification_duration") diff --git a/fastanime/cli/interfaces/anilist_interfaces.py b/fastanime/cli/interfaces/anilist_interfaces.py index 24fc5ad..d375c50 100644 --- a/fastanime/cli/interfaces/anilist_interfaces.py +++ b/fastanime/cli/interfaces/anilist_interfaces.py @@ -17,7 +17,8 @@ from ...libs.fzf import fzf from ...libs.rofi import Rofi from ...Utility.data import anime_normalizer from ...Utility.utils import anime_title_percentage_match, sanitize_filename -from ..utils.mpv import mpv +from ..utils.mpv import run_mpv +from ..utils.player import player from ..utils.tools import QueryDict, exit_app from ..utils.utils import clear, fuzzy_inquirer from .utils import aniskip @@ -75,7 +76,7 @@ def player_controls(config: "Config", anilist_config: QueryDict): anilist_config.selected_anime_anilist["idMal"], current_episode ): custom_args = args - stop_time, total_time = mpv( + stop_time, total_time = run_mpv( current_link, selected_server["episode_title"], start_time=start_time, @@ -341,18 +342,28 @@ def fetch_streams(config: "Config", anilist_config: QueryDict): if start_time != "0": print("[green]Continuing from:[/] ", start_time) custom_args = [] - if config.skip: - if args := aniskip( - anilist_config.selected_anime_anilist["idMal"], episode_number - ): - custom_args = args + if config.use_mpv_mod: + mpv = player.create_player( + anime_provider, anilist_config, config, selected_server["episode_title"] + ) + mpv.play(stream_link) + mpv.wait_for_shutdown() + mpv.terminate() + stop_time = player.last_stop_time + total_time = player.last_total_time - stop_time, total_time = mpv( - stream_link, - selected_server["episode_title"], - start_time=start_time, - custom_args=custom_args, - ) + else: + if config.skip: + if args := aniskip( + anilist_config.selected_anime_anilist["idMal"], episode_number + ): + custom_args.extend(args) + stop_time, total_time = run_mpv( + stream_link, + selected_server["episode_title"], + start_time=start_time, + custom_args=custom_args, + ) print("Finished at: ", stop_time) # update_watch_history @@ -541,7 +552,7 @@ def anilist_options(config, anilist_config: QueryDict): if trailer := selected_anime.get("trailer"): trailer_url = "https://youtube.com/watch?v=" + trailer["id"] print("[bold magenta]Watching Trailer of:[/]", selected_anime_title) - mpv( + run_mpv( trailer_url, ytdl_format=config.format, ) diff --git a/fastanime/cli/utils/mpv.py b/fastanime/cli/utils/mpv.py index 14bc11f..2eafe41 100644 --- a/fastanime/cli/utils/mpv.py +++ b/fastanime/cli/utils/mpv.py @@ -1,29 +1,6 @@ import re import shutil import subprocess -from typing import Optional - -# legacy -# def mpv(link, title: None | str = "anime", *custom_args): -# MPV = shutil.which("mpv") -# if not MPV: -# args = [ -# "nohup", -# "am", -# "start", -# "--user", -# "0", -# "-a", -# "android.intent.action.VIEW", -# "-d", -# link, -# "-n", -# "is.xyz.mpv/.MPVActivity", -# ] -# subprocess.run(args) -# else: -# subprocess.run([MPV, *custom_args, f"--title={title}", link]) -# def stream_video(MPV, url, mpv_args, custom_args): @@ -69,9 +46,9 @@ def stream_video(MPV, url, mpv_args, custom_args): return last_time, total_time -def mpv( +def run_mpv( link: str, - title: Optional[str] = "", + title: str | None = "", start_time: str = "0", ytdl_format="", custom_args=[], @@ -135,7 +112,7 @@ def mpv( # Example usage if __name__ == "__main__": - mpv( + run_mpv( "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "Example Video", "--fullscreen", diff --git a/fastanime/cli/utils/player.py b/fastanime/cli/utils/player.py new file mode 100644 index 0000000..41658b3 --- /dev/null +++ b/fastanime/cli/utils/player.py @@ -0,0 +1,133 @@ +from typing import TYPE_CHECKING + +import mpv + +from ...anilist import AniList + +if TYPE_CHECKING: + from typing import Literal + + from ...AnimeProvider import AnimeProvider + from ..config import Config + + +def format_time(duration_in_secs: float): + h = duration_in_secs // 3600 + m = duration_in_secs // 60 + s = duration_in_secs - ((h * 3600) + (m * 60)) + return f"{int(h):2d}:{int(m):2d}:{int(s):2d}".replace(" ", "0") + + +class MpvPlayer(object): + anime_provider: "AnimeProvider" + config: "Config" + mpv_player: "mpv.MPV" + last_stop_time: str = "0" + last_total_time: str = "0" + last_stop_time_secs = 0 + last_total_time_secs = 0 + current_media_title = "" + + def get_episode(self, type: "Literal['next','previous']"): + anilist_config = self.anilist_config + config = self.config + episode_number: str = anilist_config.episode_number + quality = config.quality + episodes: list = sorted(anilist_config.episodes, key=float) + anime_id: int = anilist_config.anime_id + anime = anilist_config.anime + translation_type = config.translation_type + anime_provider = config.anime_provider + + if type == "next": + next_episode = episodes.index(episode_number) + 1 + if next_episode >= len(episodes): + next_episode = len(episodes) - 1 + anilist_config.episode_number = episodes[next_episode] + episode_number = anilist_config.episode_number + config.update_watch_history(anime_id, episodes[next_episode]) + else: + prev_episode = episodes.index(episode_number) - 1 + if prev_episode <= 0: + prev_episode = 0 + anilist_config.episode_number = episodes[prev_episode] + episode_number = anilist_config.episode_number + config.update_watch_history(anime_id, episodes[prev_episode]) + if config.user and episode_number: + AniList.update_anime_list( + { + "mediaId": anime_id, + "progress": episode_number, + } + ) + episode_streams = anime_provider.get_episode_streams( + anime, + episode_number, + translation_type, + anilist_config.selected_anime_anilist, + ) + if not episode_streams: + self.mpv_player.print_text("No streams were found") + return None + + selected_server = next(episode_streams) + self.current_media_title = selected_server["episode_title"] + self.mpv_player.script_message(f"{self.current_media_title}") + links = selected_server["links"] + if quality > len(links) - 1: + quality = config.quality = len(links) - 1 + elif quality < 0: + quality = config.quality = 0 + stream_link = links[quality]["link"] + return stream_link + + def create_player( + self, anime_provider: "AnimeProvider", anilist_config, config: "Config", title + ): + self.anime_provider = anime_provider + self.anilist_config = anilist_config + self.config = config + mpv_player = mpv.MPV( + config=True, + input_default_bindings=True, + input_vo_keyboard=True, + osc=True, + ytdl=True, + ) + mpv_player.title = title + + @mpv_player.on_key_press("shift+n") + def _next_episode(): + url = self.get_episode("next") + if url: + mpv_player.loadfile(url, options=f"title={self.current_media_title}") + mpv_player.title = self.current_media_title + + @mpv_player.on_key_press("shift+p") + def _previous_episode(): + url = self.get_episode("previous") + if url: + mpv_player.loadfile(url, options=f"title={self.current_media_title}") + mpv_player.title = self.current_media_title + + @mpv_player.property_observer("time-pos") + def handle_time_start_update(*args): + if len(args) > 1: + value = args[1] + if value is not None: + self.last_stop_time = format_time(value) + + @mpv_player.property_observer("time-remaining") + def handle_time_remaining_update(*args): + if len(args) > 1: + value = args[1] + if value is not None: + self.last_total_time = format_time(value) + + mpv_player.observe_property("time-pos", handle_time_start_update) + mpv_player.observe_property("time-remaining", handle_time_remaining_update) + self.mpv_player = mpv_player + return mpv_player + + +player = MpvPlayer()