chore: format with ruff

This commit is contained in:
Benexl
2025-07-27 12:49:44 +03:00
parent abe36296c1
commit 5b06039cef
16 changed files with 96 additions and 83 deletions

View File

@@ -40,16 +40,18 @@ def stats(config: "AppConfig"):
"Authentication Required",
f"You must be logged in to {config.general.media_api} to sync your media list.",
)
feedback.info("Run this command to authenticate:", f"fastanime {config.general.media_api} auth")
feedback.info(
"Run this command to authenticate:",
f"fastanime {config.general.media_api} auth",
)
raise click.Abort()
# Check if kitten is available for image display
KITTEN_EXECUTABLE = shutil.which("kitten")
if not KITTEN_EXECUTABLE:
feedback.warning("Kitten not found - profile image will not be displayed")
feedback.warning(
"Kitten not found - profile image will not be displayed"
)
else:
# Display profile image using kitten icat
if profile.avatar_url:
@@ -92,4 +94,4 @@ def stats(config: "AppConfig"):
raise click.Abort()
except Exception as e:
feedback.error("Unexpected error occurred", str(e))
raise click.Abort()
raise click.Abort()

View File

@@ -176,16 +176,12 @@ def stream_anime(
f"Failed to get stream link for anime: {anime.title}, episode: {episode}"
)
print(f"[green bold]Now Streaming:[/] {anime.title} Episode: {episode}")
# Check if IPC player should be used
if config.mpv.use_ipc:
# Get available episodes for current translation type
available_episodes = getattr(
anime.episodes,
config.stream.translation_type,
[]
)
available_episodes = getattr(anime.episodes, config.stream.translation_type, [])
# Use IPC player with episode navigation capabilities
player.play(
PlayerParams(
@@ -200,7 +196,7 @@ def stream_anime(
current_episode=episode,
current_anime_id=anime.id,
current_anime_title=anime.title,
current_translation_type=config.stream.translation_type
current_translation_type=config.stream.translation_type,
)
)
else:

View File

@@ -237,9 +237,7 @@ def _create_local_recent_media_action(ctx: Context, state: State) -> MenuAction:
),
)
else:
ctx.feedback.info(
"No recently watched media found in local registry"
)
ctx.feedback.info("No recently watched media found in local registry")
return InternalDirective.RELOAD
return action

View File

@@ -2,7 +2,6 @@ import json
import logging
import os
import tempfile
from pathlib import Path
from .....core.constants import APP_CACHE_DIR, SCRIPTS_DIR
from .....libs.media_api.params import MediaSearchParams
@@ -30,20 +29,22 @@ def dynamic_search(ctx: Context, state: State) -> State | InternalDirective:
# Read the GraphQL search query
from .....libs.media_api.anilist import gql
search_query = gql.SEARCH_MEDIA.read_text(encoding="utf-8")
# Properly escape the GraphQL query for JSON
search_query_escaped = json.dumps(search_query)
# Prepare the search script
auth_header = ""
if ctx.media_api.is_authenticated() and hasattr(ctx.media_api, 'token'):
if ctx.media_api.is_authenticated() and hasattr(ctx.media_api, "token"):
auth_header = f"Bearer {ctx.media_api.token}"
# Create a temporary search script
with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', delete=False) as temp_script:
with tempfile.NamedTemporaryFile(
mode="w", suffix=".sh", delete=False
) as temp_script:
script_content = SEARCH_TEMPLATE_SCRIPT
replacements = {
"GRAPHQL_ENDPOINT": "https://graphql.anilist.co",
"GRAPHQL_QUERY": search_query_escaped,
@@ -51,17 +52,17 @@ def dynamic_search(ctx: Context, state: State) -> State | InternalDirective:
"SEARCH_RESULTS_FILE": str(SEARCH_RESULTS_FILE),
"AUTH_HEADER": auth_header,
}
for key, value in replacements.items():
script_content = script_content.replace(f"{{{key}}}", str(value))
temp_script.write(script_content)
temp_script_path = temp_script.name
try:
# Make the script executable
os.chmod(temp_script_path, 0o755)
# Use the selector's search functionality
try:
# Prepare preview functionality
@@ -76,56 +77,69 @@ def dynamic_search(ctx: Context, state: State) -> State | InternalDirective:
prompt="Search Anime",
search_command=f"bash {temp_script_path} {{q}}",
preview=preview_command,
header="Type to search for anime dynamically"
header="Type to search for anime dynamically",
)
else:
choice = ctx.selector.search(
prompt="Search Anime",
search_command=f"bash {temp_script_path} {{q}}",
header="Type to search for anime dynamically"
header="Type to search for anime dynamically",
)
except NotImplementedError:
feedback.error("Dynamic search is not supported by your current selector")
feedback.info("Please use the regular search option or switch to fzf selector")
feedback.info(
"Please use the regular search option or switch to fzf selector"
)
return InternalDirective.MAIN
if not choice:
return InternalDirective.MAIN
# Read the cached search results
if not SEARCH_RESULTS_FILE.exists():
logger.error("Search results file not found")
return InternalDirective.MAIN
try:
with open(SEARCH_RESULTS_FILE, 'r', encoding='utf-8') as f:
with open(SEARCH_RESULTS_FILE, "r", encoding="utf-8") as f:
raw_data = json.load(f)
# Transform the raw data into MediaSearchResult
search_result = ctx.media_api.transform_raw_search_data(raw_data)
if not search_result or not search_result.media:
feedback.info("No results found")
return InternalDirective.MAIN
# Find the selected media item by matching the choice with the displayed format
selected_media = None
for media_item in search_result.media:
title = media_item.title.english or media_item.title.romaji or media_item.title.native or "Unknown"
year = media_item.start_date.year if media_item.start_date else "Unknown"
title = (
media_item.title.english
or media_item.title.romaji
or media_item.title.native
or "Unknown"
)
year = (
media_item.start_date.year if media_item.start_date else "Unknown"
)
status = media_item.status.value if media_item.status else "Unknown"
genres = ", ".join([genre.value for genre in media_item.genres[:3]]) if media_item.genres else "Unknown"
genres = (
", ".join([genre.value for genre in media_item.genres[:3]])
if media_item.genres
else "Unknown"
)
display_format = f"{title} ({year}) [{status}] - {genres}"
if choice.strip() == display_format.strip():
selected_media = media_item
break
if not selected_media:
logger.error(f"Could not find selected media for choice: {choice}")
return InternalDirective.MAIN
# Navigate to media actions with the selected item
return State(
menu_name=MenuName.MEDIA_ACTIONS,
@@ -136,12 +150,12 @@ def dynamic_search(ctx: Context, state: State) -> State | InternalDirective:
page_info=search_result.page_info,
),
)
except (json.JSONDecodeError, KeyError, Exception) as e:
logger.error(f"Error processing search results: {e}")
feedback.error("Failed to process search results")
return InternalDirective.MAIN
finally:
# Clean up the temporary script
try:

View File

@@ -39,7 +39,9 @@ def main(ctx: Context, state: State) -> State | InternalDirective:
ctx, state, UserMediaListStatus.PLANNING
),
f"{'🔎 ' if icons else ''}Search": _create_search_media_list(ctx, state),
f"{'🔍 ' if icons else ''}Dynamic Search": _create_dynamic_search_action(ctx, state),
f"{'🔍 ' if icons else ''}Dynamic Search": _create_dynamic_search_action(
ctx, state
),
f"{'🏠 ' if icons else ''}Downloads": _create_downloads_action(ctx, state),
f"{'🔔 ' if icons else ''}Recently Updated": _create_media_list_action(
ctx, state, MediaSort.UPDATED_AT_DESC

View File

@@ -82,18 +82,17 @@ def servers(ctx: Context, state: State) -> State | InternalDirective:
# TODO: Refine implementation mpv ipc player
# Check if IPC player should be used and if we have the required data
if (config.mpv.use_ipc and
state.provider.anime and
provider_anime and
episode_number):
if (
config.mpv.use_ipc
and state.provider.anime
and provider_anime
and episode_number
):
# Get available episodes for current translation type
available_episodes = getattr(
provider_anime.episodes,
config.stream.translation_type,
[]
provider_anime.episodes, config.stream.translation_type, []
)
# Create player params with IPC dependencies for episode navigation
player_result = ctx.player.play(
PlayerParams(
@@ -109,7 +108,7 @@ def servers(ctx: Context, state: State) -> State | InternalDirective:
current_episode=episode_number,
current_anime_id=provider_anime.id,
current_anime_title=provider_anime.title,
current_translation_type=config.stream.translation_type
current_translation_type=config.stream.translation_type,
)
)
else:

View File

@@ -1,4 +1,4 @@
from enum import Enum, auto
from enum import Enum
from typing import Dict, Optional, Union
from pydantic import BaseModel, ConfigDict, Field

View File

@@ -225,15 +225,15 @@ def get_episode_preview(
def get_dynamic_anime_preview(config: AppConfig) -> str:
"""
Generate dynamic anime preview script for search functionality.
This is different from regular anime preview because:
1. We don't have media items upfront
2. The preview needs to work with search results as they come in
3. Preview is handled entirely in shell by parsing JSON results
Args:
config: Application configuration
Returns:
Preview script content for fzf dynamic search
"""
@@ -249,6 +249,7 @@ def get_dynamic_anime_preview(config: AppConfig) -> str:
# We need to return the path to the search results file
from ...core.constants import APP_CACHE_DIR
search_cache_dir = APP_CACHE_DIR / "search"
search_results_file = search_cache_dir / "current_search_results.json"

View File

@@ -91,7 +91,9 @@ MPV_DISABLE_POPEN = (
"Disable using subprocess.Popen for MPV, which can be unstable on some systems."
)
MPV_USE_PYTHON_MPV = "Use the python-mpv library for enhanced player control."
MPV_USE_IPC = "Use IPC communication with MPV for advanced features like episode navigation."
MPV_USE_IPC = (
"Use IPC communication with MPV for advanced features like episode navigation."
)
# VlcConfig
VLC_ARGS = "Comma-separated arguments to pass to the Vlc player."

View File

@@ -83,10 +83,10 @@ class BaseApiClient(abc.ABC):
def transform_raw_search_data(self, raw_data: Dict) -> Optional[MediaSearchResult]:
"""
Transform raw API response data into a MediaSearchResult.
Args:
raw_data: Raw response data from the API
Returns:
MediaSearchResult object or None if transformation fails
"""

View File

@@ -22,11 +22,11 @@ def create_ipc_player_params(
translation_type: Literal["sub", "dub"] = "sub",
subtitles: Optional[List[str]] = None,
headers: Optional[dict] = None,
start_time: Optional[str] = None
start_time: Optional[str] = None,
) -> PlayerParams:
"""
Create PlayerParams with IPC player dependencies for episode navigation.
Args:
url: Stream URL
title: Episode title
@@ -37,13 +37,13 @@ def create_ipc_player_params(
subtitles: List of subtitle URLs
headers: HTTP headers for streaming
start_time: Start time for playback
Returns:
PlayerParams configured for IPC player
"""
# Get available episodes for the translation type
available_episodes: List[str] = getattr(anime.episodes, translation_type, [])
return PlayerParams(
url=url,
title=title,
@@ -57,7 +57,7 @@ def create_ipc_player_params(
current_episode=current_episode,
current_anime_id=anime.id,
current_anime_title=anime.title,
current_translation_type=translation_type
current_translation_type=translation_type,
)
@@ -65,7 +65,7 @@ def example_usage():
"""Example of how to use the IPC player in an interactive session."""
# This would typically be called from within the servers.py menu
# when the IPC player is enabled
# Updated integration example:
"""
# In servers.py, around line 82:
@@ -112,28 +112,28 @@ def example_usage():
# Key features enabled by IPC player:
#
#
# 1. Episode Navigation:
# - Shift+N: Next episode
# - Shift+P: Previous episode
# - Shift+R: Reload current episode
#
#
# 2. Quality/Server switching:
# - Script message: select-quality 720
# - Script message: select-server gogoanime
#
#
# 3. Episode jumping:
# - Script message: select-episode 5
#
#
# 4. Translation type switching:
# - Shift+T: Toggle between sub/dub
#
#
# 5. Auto-next episode (when implemented):
# - Automatically plays next episode when current one ends
#
# To send script messages from MPV console (` key):
# script-message select-episode 5
# script-message select-quality 1080
# script-message select-quality 1080
# script-message select-server top
#
# Configuration:

View File

@@ -25,7 +25,6 @@ Requirements:
import json
import logging
import random
import re
import socket
import subprocess
import tempfile

View File

@@ -112,7 +112,7 @@ class MpvPlayer(BasePlayer):
def _stream_on_desktop_with_ipc(self, params: PlayerParams) -> PlayerResult:
"""Stream using IPC player for enhanced features."""
from .ipc import MpvIPCPlayer
ipc_player = MpvIPCPlayer(self.config)
return ipc_player.play(params)

View File

@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, List, Literal, Optional
from typing import TYPE_CHECKING, List, Literal, Optional
if TYPE_CHECKING:
from ..provider.anime.base import BaseAnimeProvider
@@ -14,7 +14,7 @@ class PlayerParams:
subtitles: list[str] | None = None
headers: dict[str, str] | None = None
start_time: str | None = None
# IPC player specific parameters for episode navigation
anime_provider: Optional["BaseAnimeProvider"] = None
current_anime: Optional["Anime"] = None

View File

@@ -115,16 +115,16 @@ class BaseSelector(ABC):
) -> str | None:
"""
Provides dynamic search functionality that reloads results based on user input.
Args:
prompt: The message to display to the user.
search_command: The command to execute for searching/reloading results.
preview: An optional command or string for a preview window.
header: An optional header to display above the choices.
Returns:
The string of the chosen item.
Raises:
NotImplementedError: If the selector doesn't support dynamic search.
"""

View File

@@ -128,7 +128,7 @@ class FzfSelector(BaseSelector):
f"change:reload({search_command})",
"--ansi",
]
if preview:
commands.extend(["--preview", preview])