mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-05 20:40:09 -08:00
feat: mass refactor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
17
fastanime/core/config/__init__.py
Normal file
17
fastanime/core/config/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from .model import (
|
||||
AnilistConfig,
|
||||
AppConfig,
|
||||
FzfConfig,
|
||||
GeneralConfig,
|
||||
MpvConfig,
|
||||
StreamConfig,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AppConfig",
|
||||
"FzfConfig",
|
||||
"MpvConfig",
|
||||
"AnilistConfig",
|
||||
"StreamConfig",
|
||||
"GeneralConfig",
|
||||
]
|
||||
@@ -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:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from .player import create_player
|
||||
|
||||
__all__ = ["create_player"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .player import MpvPlayer
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user