diff --git a/fastanime/cli/cli.py b/fastanime/cli/cli.py index 9414ebe..d529ee3 100644 --- a/fastanime/cli/cli.py +++ b/fastanime/cli/cli.py @@ -2,8 +2,9 @@ import click from click.core import ParameterSource from .. import __version__ +from ..core.config import AppConfig from ..core.constants import APP_NAME -from .config import AppConfig, ConfigLoader +from .config import ConfigLoader from .constants import USER_CONFIG_PATH from .options import options_from_model from .utils.lazyloader import LazyGroup diff --git a/fastanime/cli/commands/config.py b/fastanime/cli/commands/config.py index e6090ee..d2c9dce 100644 --- a/fastanime/cli/commands/config.py +++ b/fastanime/cli/commands/config.py @@ -1,6 +1,6 @@ import click -from ..config.model import AppConfig +from ...core.config import AppConfig @click.command( @@ -47,7 +47,6 @@ def config(user_config: AppConfig, path, view, desktop_entry, update): from ..config.generate import generate_config_ini_from_app_model from ..constants import USER_CONFIG_PATH - print(user_config.mpv.args) if path: print(USER_CONFIG_PATH) elif view: diff --git a/fastanime/cli/config/__init__.py b/fastanime/cli/config/__init__.py index 0d8b27f..ac60aec 100644 --- a/fastanime/cli/config/__init__.py +++ b/fastanime/cli/config/__init__.py @@ -1,4 +1,4 @@ +from .generate import generate_config_ini_from_app_model from .loader import ConfigLoader -from .model import AppConfig -__all__ = ["AppConfig", "ConfigLoader"] +__all__ = ["ConfigLoader", "generate_config_ini_from_app_model"] diff --git a/fastanime/cli/config/generate.py b/fastanime/cli/config/generate.py index fd20fc5..ebe6ced 100644 --- a/fastanime/cli/config/generate.py +++ b/fastanime/cli/config/generate.py @@ -1,8 +1,8 @@ import textwrap from pathlib import Path +from ...core.config import AppConfig from ..constants import APP_ASCII_ART -from .model import AppConfig # The header for the config file. config_asci = "\n".join([f"# {line}" for line in APP_ASCII_ART.split()]) diff --git a/fastanime/cli/config/loader.py b/fastanime/cli/config/loader.py index b82dc18..6e9dbfc 100644 --- a/fastanime/cli/config/loader.py +++ b/fastanime/cli/config/loader.py @@ -4,10 +4,10 @@ from pathlib import Path import click from pydantic import ValidationError +from ...core.config import AppConfig from ...core.exceptions import ConfigError from ..constants import USER_CONFIG_PATH from .generate import generate_config_ini_from_app_model -from .model import AppConfig class ConfigLoader: diff --git a/fastanime/cli/constants.py b/fastanime/cli/constants.py index 2ae31a9..15099eb 100644 --- a/fastanime/cli/constants.py +++ b/fastanime/cli/constants.py @@ -1,10 +1,9 @@ import os -import sys from pathlib import Path import click -from ..core.constants import APP_NAME, ICONS_DIR +from ..core.constants import APP_NAME, ICONS_DIR, PLATFORM APP_ASCII_ART = """\ ███████╗░█████╗░░██████╗████████╗░█████╗░███╗░░██╗██╗███╗░░░███╗███████╗ @@ -14,7 +13,6 @@ APP_ASCII_ART = """\ ██║░░░░░██║░░██║██████╔╝░░░██║░░░██║░░██║██║░╚███║██║██║░╚═╝░██║███████╗ ╚═╝░░░░░╚═╝░░╚═╝╚═════╝░░░░╚═╝░░░╚═╝░░╚═╝╚═╝░░╚══╝╚═╝╚═╝░░░░░╚═╝╚══════╝ """ -PLATFORM = sys.platform USER_NAME = os.environ.get("USERNAME", "Anime Fan") diff --git a/fastanime/core/config/__init__.py b/fastanime/core/config/__init__.py new file mode 100644 index 0000000..d2b9234 --- /dev/null +++ b/fastanime/core/config/__init__.py @@ -0,0 +1,17 @@ +from .model import ( + AnilistConfig, + AppConfig, + FzfConfig, + GeneralConfig, + MpvConfig, + StreamConfig, +) + +__all__ = [ + "AppConfig", + "FzfConfig", + "MpvConfig", + "AnilistConfig", + "StreamConfig", + "GeneralConfig", +] diff --git a/fastanime/cli/config/model.py b/fastanime/core/config/model.py similarity index 100% rename from fastanime/cli/config/model.py rename to fastanime/core/config/model.py diff --git a/fastanime/core/constants.py b/fastanime/core/constants.py index 42ece9a..be148c5 100644 --- a/fastanime/core/constants.py +++ b/fastanime/core/constants.py @@ -1,6 +1,8 @@ import os +import sys from importlib import resources +PLATFORM = sys.platform APP_NAME = os.environ.get("FASTANIME_APP_NAME", "fastanime") try: diff --git a/fastanime/libs/players/__init__.py b/fastanime/libs/players/__init__.py index e69de29..3ac4c2d 100644 --- a/fastanime/libs/players/__init__.py +++ b/fastanime/libs/players/__init__.py @@ -0,0 +1,3 @@ +from .player import create_player + +__all__ = ["create_player"] diff --git a/fastanime/libs/players/base.py b/fastanime/libs/players/base.py index e69de29..ba42625 100644 --- a/fastanime/libs/players/base.py +++ b/fastanime/libs/players/base.py @@ -0,0 +1,50 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import TYPE_CHECKING, List, Tuple + +if TYPE_CHECKING: + from ..providers.anime.types import Subtitle + + +@dataclass(frozen=True) +class PlayerResult: + """ + Represents the result of a completed playback session. + + Attributes: + stop_time: The timestamp where playback stopped (e.g., "00:15:30"). + total_time: The total duration of the media (e.g., "00:23:45"). + """ + + stop_time: str | None = None + total_time: str | None = None + + +class BasePlayer(ABC): + """ + Abstract Base Class defining the contract for all media players. + """ + + @abstractmethod + def play( + self, + url: str, + title: str, + subtitles: List["Subtitle"] | None = None, + headers: dict | None = None, + start_time: str = "0", + ) -> PlayerResult: + """ + Plays the given media URL. + + Args: + url: The stream URL to play. + title: The title to display in the player window. + subtitles: A list of subtitle objects. + headers: Any required HTTP headers for the stream. + start_time: The timestamp to start playback from (e.g., "00:10:30"). + + Returns: + A tuple containing (stop_time, total_time) as strings. + """ + pass diff --git a/fastanime/libs/players/mpv/__init__.py b/fastanime/libs/players/mpv/__init__.py index e69de29..12f8561 100644 --- a/fastanime/libs/players/mpv/__init__.py +++ b/fastanime/libs/players/mpv/__init__.py @@ -0,0 +1 @@ +from .player import MpvPlayer diff --git a/fastanime/libs/players/mpv/player.py b/fastanime/libs/players/mpv/player.py index 2fa6adb..14b3647 100644 --- a/fastanime/libs/players/mpv/player.py +++ b/fastanime/libs/players/mpv/player.py @@ -1,66 +1,57 @@ import logging -import os import re import shutil import subprocess -import time -from ...constants import S_PLATFORM +from ....core.config import MpvConfig +from ..base import BasePlayer, PlayerResult logger = logging.getLogger(__name__) -mpv_av_time_pattern = re.compile(r"AV: ([0-9:]*) / ([0-9:]*) \(([0-9]*)%\)") +MPV_AV_TIME_PATTERN = re.compile(r"AV: ([0-9:]*) / ([0-9:]*) \(([0-9]*)%\)") -def stream_video(MPV, url, mpv_args, custom_args, pre_args=[]): - last_time = "0" - total_time = "0" - if os.environ.get("FASTANIME_DISABLE_MPV_POPEN", "False") == "False": - process = subprocess.Popen( - pre_args - + [ - MPV, - url, - *mpv_args, - *custom_args, - "--no-terminal", - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - bufsize=1, - encoding="utf-8", - ) +class MpvPlayer(BasePlayer): + def __init__(self, config: MpvConfig): + self.config = config + self.executable = shutil.which("mpv") - try: - while True: - if not process.stderr: - time.sleep(0.1) - continue - output = process.stderr.readline() + def play(self, url, title, subtitles=None, headers=None, start_time="0"): + if not self.executable: + raise FileNotFoundError("MPV executable not found in PATH.") - if output: - # Match the timestamp in the output - match = mpv_av_time_pattern.search(output.strip()) - if match: - current_time = match.group(1) - total_time = match.group(2) - last_time = current_time + mpv_args = [] + if headers: + header_str = ",".join([f"{k}:{v}" for k, v in headers.items()]) + mpv_args.append(f"--http-header-fields={header_str}") - # Check if the process has terminated - retcode = process.poll() - if retcode is not None: - break + if subtitles: + for sub in subtitles: + mpv_args.append(f"--sub-file={sub.url}") + + if start_time != "0": + mpv_args.append(f"--start={start_time}") + + if title: + mpv_args.append(f"--title={title}") + + if self.config.args: + mpv_args.extend(self.config.args.split(",")) + + pre_args = self.config.pre_args.split(",") if self.config.pre_args else [] + + if self.config.use_python_mpv: + self._stream_with_python_mpv() + else: + self._stream_with_subprocess(self.executable, url, [], pre_args) + return PlayerResult() + + def _stream_with_subprocess(self, mpv_executable, url, mpv_args, pre_args): + last_time = "0" + total_time = "0" - except Exception as e: - print(f"An error occurred: {e}") - logger.error(f"An error occurred: {e}") - finally: - process.terminate() - process.wait() - else: proc = subprocess.run( - pre_args + [MPV, url, *mpv_args, *custom_args], + pre_args + [mpv_executable, url, *mpv_args], capture_output=True, text=True, encoding="utf-8", @@ -68,156 +59,12 @@ def stream_video(MPV, url, mpv_args, custom_args, pre_args=[]): ) if proc.stdout: for line in reversed(proc.stdout.split("\n")): - match = mpv_av_time_pattern.search(line.strip()) + match = MPV_AV_TIME_PATTERN.search(line.strip()) if match: last_time = match.group(1) total_time = match.group(2) break - return last_time, total_time + return last_time, total_time - -def run_mpv( - link: str, - title: str = "", - start_time: str = "0", - ytdl_format="", - custom_args=[], - headers={}, - subtitles=[], - player="", -): - # If title is None, set a default value - - # Regex to check if the link is a YouTube URL - youtube_regex = r"(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/.+" - - if link.endswith(".torrent"): - WEBTORRENT_CLI = shutil.which("webtorrent") - if not WEBTORRENT_CLI: - import time - - print( - "webtorrent cli is not installed which is required for downloading and streaming from nyaa\nplease install it or use another provider" - ) - time.sleep(120) - return "0", "0" - cmd = [WEBTORRENT_CLI, link, f"--{player}"] - subprocess.run(cmd, encoding="utf-8", check=False) + def _stream_with_python_mpv(self): return "0", "0" - if player == "vlc": - VLC = shutil.which("vlc") - if not VLC and not S_PLATFORM == "win32": - # Determine if the link is a YouTube URL - if re.match(youtube_regex, link): - # Android specific commands to launch mpv with a YouTube URL - args = [ - "nohup", - "am", - "start", - "--user", - "0", - "-a", - "android.intent.action.VIEW", - "-d", - link, - "-n", - "com.google.android.youtube/.UrlActivity", - ] - return "0", "0" - else: - args = [ - "nohup", - "am", - "start", - "--user", - "0", - "-a", - "android.intent.action.VIEW", - "-d", - link, - "-n", - "org.videolan.vlc/org.videolan.vlc.gui.video.VideoPlayerActivity", - "-e", - "title", - title, - ] - - subprocess.run(args, check=False) - return "0", "0" - else: - args = ["vlc", link] - for subtitle in subtitles: - args.append("--sub-file") - args.append(subtitle["url"]) - break - if title: - args.append("--video-title") - args.append(title) - subprocess.run(args, encoding="utf-8", check=False) - return "0", "0" - else: - # Determine if mpv is available - MPV = shutil.which("mpv") - if not MPV and not S_PLATFORM == "win32": - # Determine if the link is a YouTube URL - if re.match(youtube_regex, link): - # Android specific commands to launch mpv with a YouTube URL - args = [ - "nohup", - "am", - "start", - "--user", - "0", - "-a", - "android.intent.action.VIEW", - "-d", - link, - "-n", - "com.google.android.youtube/.UrlActivity", - ] - return "0", "0" - else: - # Android specific commands to launch mpv with a regular URL - args = [ - "nohup", - "am", - "start", - "--user", - "0", - "-a", - "android.intent.action.VIEW", - "-d", - link, - "-n", - "is.xyz.mpv/.MPVActivity", - ] - - subprocess.run(args, check=False) - return "0", "0" - else: - # General mpv command with custom arguments - mpv_args = [] - if headers: - mpv_headers = "--http-header-fields=" - for header_name, header_value in headers.items(): - mpv_headers += f"{header_name}:{header_value}," - mpv_args.append(mpv_headers) - for subtitle in subtitles: - mpv_args.append(f"--sub-file={subtitle['url']}") - if start_time != "0": - mpv_args.append(f"--start={start_time}") - if title: - mpv_args.append(f"--title={title}") - if ytdl_format: - mpv_args.append(f"--ytdl-format={ytdl_format}") - - if user_args := os.environ.get("FASTANIME_MPV_ARGS"): - mpv_args.extend(user_args.split(",")) - - pre_args = [] - if user_args := os.environ.get("FASTANIME_MPV_PRE_ARGS"): - pre_args = user_args.split(",") - stop_time, total_time = stream_video( - MPV, link, mpv_args, custom_args, pre_args - ) - return stop_time, total_time diff --git a/fastanime/libs/players/player.py b/fastanime/libs/players/player.py index 38d4182..d5b4d2d 100644 --- a/fastanime/libs/players/player.py +++ b/fastanime/libs/players/player.py @@ -1,385 +1,42 @@ from typing import TYPE_CHECKING -import mpv +# from .vlc.player import VlcPlayer # When you create it +# from .syncplay.player import SyncplayPlayer # When you create it +from ...core.config import AppConfig +from .base import BasePlayer -from ...anilist import AniList -from .utils import filter_by_quality, move_preferred_subtitle_lang_to_top - -if TYPE_CHECKING: - from typing import Literal - - from ...AnimeProvider import AnimeProvider - from ..config import Config - from .tools import FastAnimeRuntimeState +PLAYERS = ["mpv", "vlc", "syncplay"] -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 PlayerFactory: + @staticmethod + def create(player_name: str, config: AppConfig) -> BasePlayer: + """ + Factory method to create a player instance based on its name. + Args: + player_name: The name of the player (e.g., 'mpv', 'vlc'). + config: The full application configuration object. -class MpvPlayer: - anime_provider: "AnimeProvider" - config: "Config" - subs = [] - 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 = "" - player_fetching = False + Returns: + An instance of a class that inherits from BasePlayer. - def get_episode( - self, - type: "Literal['next','previous','reload','custom']", - ep_no=None, - server="top", - ): - fastanime_runtime_state = self.fastanime_runtime_state - config = self.config - current_episode_number: str = ( - fastanime_runtime_state.provider_current_episode_number - ) - quality = config.quality - total_episodes: list = sorted( - fastanime_runtime_state.provider_available_episodes, key=float - ) - anime_id_anilist: int = fastanime_runtime_state.selected_anime_id_anilist - provider_anime = fastanime_runtime_state.provider_anime - translation_type = config.translation_type - anime_provider = config.anime_provider - self.last_stop_time: str = "0" - self.last_total_time: str = "0" - self.last_stop_time_secs = 0 - self.last_total_time_secs = 0 + Raises: + ValueError: If the player_name is not supported. + """ - # next or prev - if type == "next": - self.mpv_player.show_text("Fetching next episode...") - next_episode = total_episodes.index(current_episode_number) + 1 - if next_episode >= len(total_episodes): - next_episode = len(total_episodes) - 1 - fastanime_runtime_state.provider_current_episode_number = total_episodes[ - next_episode - ] - current_episode_number = ( - fastanime_runtime_state.provider_current_episode_number + if player_name not in PLAYERS: + raise ValueError( + f"Unsupported player: '{player_name}'. Supported players are: {PLAYERS}" ) - config.media_list_track( - anime_id_anilist, - episode_no=str(current_episode_number), - progress_tracking=fastanime_runtime_state.progress_tracking, - ) - elif type == "reload": - if current_episode_number not in total_episodes: - self.mpv_player.show_text("Episode not available") - return - self.mpv_player.show_text("Replaying Episode...") - elif type == "custom": - if not ep_no or ep_no not in total_episodes: - self.mpv_player.show_text("Episode number not specified or invalid") - self.mpv_player.show_text( - f"Acceptable episodes are: {total_episodes}", - ) - return - self.mpv_player.show_text(f"Fetching episode {ep_no}") - current_episode_number = ep_no - config.media_list_track( - anime_id_anilist, - episode_no=str(ep_no), - progress_tracking=fastanime_runtime_state.progress_tracking, - ) - fastanime_runtime_state.provider_current_episode_number = str(ep_no) - else: - self.mpv_player.show_text("Fetching previous episode...") - prev_episode = total_episodes.index(current_episode_number) - 1 - prev_episode = max(0, prev_episode) - fastanime_runtime_state.provider_current_episode_number = total_episodes[ - prev_episode - ] - current_episode_number = ( - fastanime_runtime_state.provider_current_episode_number - ) - config.media_list_track( - anime_id_anilist, - episode_no=str(current_episode_number), - progress_tracking=fastanime_runtime_state.progress_tracking, - ) - # update episode progress - if config.user and current_episode_number: - AniList.update_anime_list( - { - "mediaId": anime_id_anilist, - "progress": int(float(current_episode_number)), - } - ) - # get them juicy streams - episode_streams = anime_provider.get_episode_streams( - provider_anime["id"], - current_episode_number, - translation_type, - ) - if not episode_streams: - self.mpv_player.show_text("No streams were found") - return + if player_name == "mpv": + from .mpv import MpvPlayer - # always select the first - if server == "top": - selected_server = next(episode_streams, None) - if not selected_server: - self.mpv_player.show_text("Sth went wrong when loading the episode") - return - else: - episode_streams_dict = { - episode_stream["server"]: episode_stream - for episode_stream in episode_streams - } - selected_server = episode_streams_dict.get(server) - if selected_server is None: - self.mpv_player.show_text( - f"Invalid server!!; servers available are: {episode_streams_dict.keys()}", - ) - return - self.current_media_title = selected_server["episode_title"] - if config.normalize_titles: - import re - - for episode_detail in fastanime_runtime_state.selected_anime_anilist[ - "streamingEpisodes" - ]: - if re.match( - f"Episode {current_episode_number} ", episode_detail["title"] - ): - self.current_media_title = episode_detail["title"] - break - - links = selected_server["links"] - - stream_link_ = filter_by_quality(quality, links) - if not stream_link_: - self.mpv_player.show_text("Quality not found") - return - self.mpv_player._set_property("start", "0") - stream_link = stream_link_["link"] - fastanime_runtime_state.provider_current_episode_stream_link = stream_link - self.subs = move_preferred_subtitle_lang_to_top( - selected_server["subtitles"], config.sub_lang - ) - return stream_link - - def create_player( - self, - stream_link, - anime_provider: "AnimeProvider", - fastanime_runtime_state: "FastAnimeRuntimeState", - config: "Config", - title, - start_time, - headers={}, - subtitles=[], - ): - self.subs = subtitles - self.anime_provider = anime_provider - self.fastanime_runtime_state = fastanime_runtime_state - self.config = config - self.last_stop_time: str = "0" - self.last_total_time: str = "0" - self.last_stop_time_secs = 0 - self.last_total_time_secs = 0 - self.current_media_title = "" - - mpv_player = mpv.MPV( - log_handler=print, - loglevel="error", - config=True, - input_default_bindings=True, - input_vo_keyboard=True, - osc=True, - ytdl=True, + return MpvPlayer(config.mpv) + raise NotImplementedError( + f"Configuration logic for player '{player_name}' not implemented in factory." ) - # -- events -- - @mpv_player.event_callback("file-loaded") - def set_total_time(event, *args): - d = mpv_player._get_property("duration") - self.player_fetching = False - if isinstance(d, float): - self.last_total_time = format_time(d) - try: - if not mpv_player.core_shutdown: - if self.subs: - for i, subtitle in enumerate(self.subs): - if i == 0: - flag = "select" - else: - flag = "auto" - mpv_player.sub_add( - subtitle["url"], flag, None, subtitle["language"] - ) - self.subs = [] - except mpv.ShutdownError: - pass - except Exception: - pass - @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( - property, time_remaining: float | None = None, *args - ): - if time_remaining is not None: - if time_remaining < 1 and config.auto_next and not self.player_fetching: - print("Auto Fetching Next Episode") - self.player_fetching = True - url = self.get_episode("next") - if url: - mpv_player.loadfile( - url, - ) - mpv_player.title = self.current_media_title - - # -- keybindings -- - @mpv_player.on_key_press("shift+n") - def _next_episode(): - url = self.get_episode("next") - if url: - mpv_player.loadfile( - url, - ) - 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, - ) - mpv_player.title = self.current_media_title - - @mpv_player.on_key_press("shift+a") - def _toggle_auto_next(): - config.auto_next = not config.auto_next - if config.auto_next: - mpv_player.show_text("Auto next enabled") - else: - mpv_player.show_text("Auto next disabled") - - @mpv_player.on_key_press("shift+t") - def _toggle_translation_type(): - translation_type = "sub" if config.translation_type == "dub" else "dub" - mpv_player.show_text("Changing translation type...") - anime = anime_provider.get_anime( - fastanime_runtime_state.provider_anime_search_result["id"], - ) - if not anime: - mpv_player.show_text("Failed to update translation type") - return - fastanime_runtime_state.provider_available_episodes = anime[ - "availableEpisodesDetail" - ][translation_type] - config.translation_type = translation_type - - if config.translation_type == "dub": - mpv_player.show_text("Translation Type set to dub") - else: - mpv_player.show_text("Translation Type set to sub") - - @mpv_player.on_key_press("shift+r") - def _reload(): - url = self.get_episode("reload") - if url: - mpv_player.loadfile( - url, - ) - mpv_player.title = self.current_media_title - - # -- script messages -- - @mpv_player.message_handler("select-episode") - def select_episode(episode: bytes | None = None, *args): - if not episode: - mpv_player.show_text("No episode was selected") - return - url = self.get_episode("custom", episode.decode()) - if url: - mpv_player.loadfile( - url, - ) - mpv_player.title = self.current_media_title - - @mpv_player.message_handler("select-server") - def select_server(server: bytes | None = None, *args): - if not server: - mpv_player.show_text("No server was selected") - return - url = self.get_episode("reload", server=server.decode()) - if url: - mpv_player.loadfile( - url, - ) - mpv_player.title = self.current_media_title - else: - pass - - @mpv_player.message_handler("select-quality") - def select_quality(quality_raw: bytes | None = None, *args): - if not quality_raw: - mpv_player.show_text("No quality was selected") - return - q = ["360", "720", "1080"] - quality = quality_raw.decode() - links: list = fastanime_runtime_state.provider_server_episode_streams - q = [link["quality"] for link in links] - if quality in q: - config.quality = quality - stream_link_ = filter_by_quality(quality, links) - if not stream_link_: - mpv_player.show_text("Quality not found") - return - mpv_player.show_text(f"Changing to stream of quality {quality}") - stream_link = stream_link_["link"] - mpv_player.loadfile(stream_link) - else: - mpv_player.show_text(f"invalid quality!! Valid quality includes: {q}") - - # -- events -- - mpv_player.observe_property("time-pos", handle_time_start_update) - mpv_player.observe_property("time-remaining", handle_time_remaining_update) - mpv_player.register_event_callback(set_total_time) - - # --script-messages -- - mpv_player.register_message_handler("select-episode", select_episode) - mpv_player.register_message_handler("select-server", select_server) - mpv_player.register_message_handler("select-quality", select_quality) - - self.mpv_player = mpv_player - mpv_player.force_window = config.force_window - # mpv_player.cache = "yes" - # mpv_player.cache_pause = "no" - mpv_player.title = title - mpv_headers = "" - if headers: - for header_name, header_value in headers.items(): - mpv_headers += f"{header_name}:{header_value}," - mpv_player.http_header_fields = mpv_headers - - mpv_player.play(stream_link) - - if not start_time == "0": - mpv_player.start = start_time - - mpv_player.wait_for_shutdown() - mpv_player.terminate() - - -player = MpvPlayer() +create_player = PlayerFactory.create diff --git a/fastanime/cli/selectors/__init__.py b/fastanime/libs/selectors/__init__.py similarity index 100% rename from fastanime/cli/selectors/__init__.py rename to fastanime/libs/selectors/__init__.py diff --git a/fastanime/cli/selectors/base.py b/fastanime/libs/selectors/base.py similarity index 100% rename from fastanime/cli/selectors/base.py rename to fastanime/libs/selectors/base.py diff --git a/fastanime/cli/selectors/fzf/__init__.py b/fastanime/libs/selectors/fzf/__init__.py similarity index 100% rename from fastanime/cli/selectors/fzf/__init__.py rename to fastanime/libs/selectors/fzf/__init__.py diff --git a/fastanime/cli/selectors/fzf/scripts/search.sh b/fastanime/libs/selectors/fzf/scripts/search.sh similarity index 100% rename from fastanime/cli/selectors/fzf/scripts/search.sh rename to fastanime/libs/selectors/fzf/scripts/search.sh diff --git a/fastanime/cli/selectors/fzf/selector.py b/fastanime/libs/selectors/fzf/selector.py similarity index 100% rename from fastanime/cli/selectors/fzf/selector.py rename to fastanime/libs/selectors/fzf/selector.py diff --git a/fastanime/cli/selectors/inquirer/__init__.py b/fastanime/libs/selectors/inquirer/__init__.py similarity index 100% rename from fastanime/cli/selectors/inquirer/__init__.py rename to fastanime/libs/selectors/inquirer/__init__.py diff --git a/fastanime/cli/selectors/inquirer/selector.py b/fastanime/libs/selectors/inquirer/selector.py similarity index 100% rename from fastanime/cli/selectors/inquirer/selector.py rename to fastanime/libs/selectors/inquirer/selector.py diff --git a/fastanime/cli/selectors/rofi/__init__.py b/fastanime/libs/selectors/rofi/__init__.py similarity index 100% rename from fastanime/cli/selectors/rofi/__init__.py rename to fastanime/libs/selectors/rofi/__init__.py diff --git a/fastanime/cli/selectors/rofi/selector.py b/fastanime/libs/selectors/rofi/selector.py similarity index 100% rename from fastanime/cli/selectors/rofi/selector.py rename to fastanime/libs/selectors/rofi/selector.py diff --git a/fastanime/cli/selectors/selector.py b/fastanime/libs/selectors/selector.py similarity index 100% rename from fastanime/cli/selectors/selector.py rename to fastanime/libs/selectors/selector.py