mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 15:50:01 -08:00
feat: improve preview
This commit is contained in:
@@ -30,7 +30,7 @@ def episodes(ctx: Context, state: State) -> State | ControlFlow:
|
||||
feedback.warning(
|
||||
f"No '{config.stream.translation_type}' episodes found for this anime."
|
||||
)
|
||||
return ControlFlow.BACK
|
||||
return ControlFlow.BACKX2
|
||||
|
||||
chosen_episode: str | None = None
|
||||
|
||||
@@ -54,8 +54,8 @@ def episodes(ctx: Context, state: State) -> State | ControlFlow:
|
||||
)
|
||||
|
||||
if not chosen_episode_str or chosen_episode_str == "Back":
|
||||
# FIX: back broken
|
||||
return ControlFlow.BACK
|
||||
# TODO: should improve the back logic for menus that can be pass through
|
||||
return ControlFlow.BACKX2
|
||||
|
||||
chosen_episode = chosen_episode_str
|
||||
|
||||
|
||||
@@ -146,6 +146,15 @@ class Session:
|
||||
elif next_step == ControlFlow.BACK:
|
||||
if len(self._history) > 1:
|
||||
self._history.pop()
|
||||
elif next_step == ControlFlow.BACKX2:
|
||||
if len(self._history) > 2:
|
||||
self._history.pop()
|
||||
self._history.pop()
|
||||
elif next_step == ControlFlow.BACKX3:
|
||||
if len(self._history) > 3:
|
||||
self._history.pop()
|
||||
self._history.pop()
|
||||
self._history.pop()
|
||||
elif next_step == ControlFlow.CONFIG_EDIT:
|
||||
self._edit_config()
|
||||
else:
|
||||
|
||||
@@ -24,6 +24,12 @@ class ControlFlow(Enum):
|
||||
BACK = auto()
|
||||
"""Pop the current state from history and return to the previous one."""
|
||||
|
||||
BACKX2 = auto()
|
||||
"""Pop x2 the current state from history and return to the previous one."""
|
||||
|
||||
BACKX3 = auto()
|
||||
"""Pop x3 the current state from history and return to the previous one."""
|
||||
|
||||
EXIT = auto()
|
||||
"""Terminate the interactive session gracefully."""
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ class MediaFilter:
|
||||
)
|
||||
)
|
||||
or (item.description and query_lower in item.description.lower())
|
||||
or any(query_lower in syn.lower() for syn in item.synonyms)
|
||||
or any(query_lower in syn.lower() for syn in item.synonymns)
|
||||
]
|
||||
|
||||
# IDs
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from yt_dlp.utils import clean_html as ytdlp_clean_html
|
||||
@@ -8,6 +9,24 @@ from ...libs.api.types import AiringSchedule, MediaItem
|
||||
COMMA_REGEX = re.compile(r"([0-9]{3})(?=\d)")
|
||||
|
||||
|
||||
def format_date(dt: Optional[datetime], format_str: str = "%A, %d %B %Y") -> str:
|
||||
"""
|
||||
Formats a datetime object to a readable string.
|
||||
|
||||
Default format: '2025-22 July'
|
||||
|
||||
Params:
|
||||
dt (datetime): The datetime object to format.
|
||||
format_str (str): Optional custom format string (defaults to "%Y-%d %B").
|
||||
|
||||
Returns:
|
||||
str: The formatted date.
|
||||
"""
|
||||
if not dt:
|
||||
return "N/A"
|
||||
return dt.strftime(format_str)
|
||||
|
||||
|
||||
def clean_html(raw_html: str) -> str:
|
||||
"""A wrapper around yt-dlp's clean_html to handle None inputs."""
|
||||
return ytdlp_clean_html(raw_html) if raw_html else ""
|
||||
@@ -30,9 +49,9 @@ def format_airing_schedule(airing: Optional[AiringSchedule]) -> str:
|
||||
return f"Ep {airing.episode} on {air_date}"
|
||||
|
||||
|
||||
def format_genres(genres: List[str]) -> str:
|
||||
def format_list_with_commas(list_of_strs: List[str]) -> str:
|
||||
"""Joins a list of genres into a single, comma-separated string."""
|
||||
return ", ".join(genres) if genres else "N/A"
|
||||
return ", ".join(list_of_strs) if list_of_strs else "N/A"
|
||||
|
||||
|
||||
def format_score_stars_full(score: Optional[float]) -> str:
|
||||
|
||||
@@ -72,17 +72,66 @@ def _populate_info_template(item: MediaItem, config: AppConfig) -> str:
|
||||
|
||||
# Escape all variables before injecting them into the script
|
||||
replacements = {
|
||||
#
|
||||
# plain text
|
||||
#
|
||||
"TITLE": formatters.shell_safe(item.title.english or item.title.romaji),
|
||||
"STATUS": formatters.shell_safe(item.status),
|
||||
"FORMAT": formatters.shell_safe(item.format),
|
||||
#
|
||||
# numerical
|
||||
#
|
||||
"NEXT_EPISODE": formatters.shell_safe(
|
||||
f"Episode {item.next_airing.episode} on {formatters.format_date(item.next_airing.airing_at)}"
|
||||
if item.next_airing
|
||||
else "N/A"
|
||||
),
|
||||
"EPISODES": formatters.shell_safe(str(item.episodes)),
|
||||
"SCORE": formatters.shell_safe(
|
||||
formatters.format_score_stars_full(item.average_score)
|
||||
),
|
||||
"STATUS": formatters.shell_safe(item.status),
|
||||
"FAVOURITES": formatters.shell_safe(
|
||||
formatters.format_number_with_commas(item.favourites)
|
||||
),
|
||||
"GENRES": formatters.shell_safe(formatters.format_genres(item.genres)),
|
||||
"POPULARITY": formatters.shell_safe(
|
||||
formatters.format_number_with_commas(item.popularity)
|
||||
),
|
||||
#
|
||||
# list
|
||||
#
|
||||
"GENRES": formatters.shell_safe(
|
||||
formatters.format_list_with_commas(item.genres)
|
||||
),
|
||||
"TAGS": formatters.shell_safe(
|
||||
formatters.format_list_with_commas([t.name for t in item.tags])
|
||||
),
|
||||
"STUDIOS": formatters.shell_safe(
|
||||
formatters.format_list_with_commas([t.name for t in item.studios if t.name])
|
||||
),
|
||||
"SYNONYMNS": formatters.shell_safe(
|
||||
formatters.format_list_with_commas(item.synonymns)
|
||||
),
|
||||
#
|
||||
# user
|
||||
#
|
||||
"USER_STATUS": formatters.shell_safe(
|
||||
item.user_status.status if item.user_status else "NOT_ON_LIST"
|
||||
),
|
||||
"USER_PROGRESS": formatters.shell_safe(
|
||||
f"Episode {item.user_status.progress}" if item.user_status else "0"
|
||||
),
|
||||
#
|
||||
# dates
|
||||
#
|
||||
"START_DATE": formatters.shell_safe(formatters.format_date(item.start_date)),
|
||||
"END_DATE": formatters.shell_safe(formatters.format_date(item.end_date)),
|
||||
#
|
||||
# big guy
|
||||
#
|
||||
"SYNOPSIS": formatters.shell_safe(description),
|
||||
#
|
||||
# Color codes
|
||||
#
|
||||
"C_TITLE": ansi.get_true_fg(HEADER_COLOR, bold=True),
|
||||
"C_KEY": ansi.get_true_fg(HEADER_COLOR, bold=True),
|
||||
"C_VALUE": ansi.get_true_fg(HEADER_COLOR, bold=True),
|
||||
@@ -107,12 +156,12 @@ def _cache_worker(items: List[MediaItem], titles: List[str], config: AppConfig):
|
||||
_save_image_from_url, item.cover_image.large, hash_id
|
||||
)
|
||||
if config.general.preview in ("full", "text"):
|
||||
if not (INFO_CACHE_DIR / hash_id).exists():
|
||||
# TODO: Come up with a better caching pattern for now just let it be remade
|
||||
if not (INFO_CACHE_DIR / hash_id).exists() or True:
|
||||
info_text = _populate_info_template(item, config)
|
||||
executor.submit(_save_info_text, info_text, hash_id)
|
||||
|
||||
|
||||
# --- THIS IS THE MODIFIED FUNCTION ---
|
||||
def get_anime_preview(
|
||||
items: List[MediaItem], titles: List[str], config: AppConfig
|
||||
) -> str:
|
||||
@@ -205,7 +254,7 @@ def _episode_cache_worker(episodes: List[str], anime: MediaItem, config: AppConf
|
||||
# Find matching streaming episode
|
||||
episode_data = None
|
||||
for title, ep in streaming_episodes.items():
|
||||
if f"Episode {episode_str}" in title or title.endswith(
|
||||
if f"Episode {episode_str} -" in title or title.endswith(
|
||||
f" {episode_str}"
|
||||
):
|
||||
episode_data = {
|
||||
|
||||
@@ -247,7 +247,7 @@ def _to_generic_media_item(
|
||||
genres=data.get("genres", []),
|
||||
tags=_to_generic_tags(data.get("tags")),
|
||||
studios=_to_generic_studios(data.get("studios")),
|
||||
synonyms=data.get("synonyms", []),
|
||||
synonymns=data.get("synonyms", []),
|
||||
average_score=data.get("averageScore"),
|
||||
popularity=data.get("popularity"),
|
||||
favourites=data.get("favourites"),
|
||||
|
||||
@@ -15,6 +15,7 @@ query (
|
||||
media {
|
||||
id
|
||||
idMal
|
||||
format
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
|
||||
@@ -60,6 +60,7 @@ query (
|
||||
) {
|
||||
id
|
||||
idMal
|
||||
format
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
|
||||
@@ -109,7 +109,7 @@ class MediaItem(BaseApiModel):
|
||||
genres: List[str] = Field(default_factory=list)
|
||||
tags: List[MediaTag] = Field(default_factory=list)
|
||||
studios: List[Studio] = Field(default_factory=list)
|
||||
synonyms: List[str] = Field(default_factory=list)
|
||||
synonymns: List[str] = Field(default_factory=list)
|
||||
|
||||
average_score: Optional[float] = None
|
||||
popularity: Optional[int] = None
|
||||
|
||||
@@ -52,6 +52,7 @@ draw_rule(){
|
||||
# --- Display Content ---
|
||||
draw_rule
|
||||
print_kv "Title" "{TITLE}"
|
||||
|
||||
draw_rule
|
||||
|
||||
# Key-Value Stats Section
|
||||
@@ -60,11 +61,33 @@ if ! [ "{SCORE}" = "N/A" ];then
|
||||
score_multiplier=2
|
||||
fi
|
||||
print_kv "Score" "{SCORE}" $score_multiplier
|
||||
print_kv "Status" "{STATUS}"
|
||||
print_kv "Favourites" "{FAVOURITES}"
|
||||
print_kv "Popularity" "{POPULARITY}"
|
||||
print_kv "Status" "{STATUS}"
|
||||
print_kv "Episodes" "{EPISODES}"
|
||||
print_kv "Next Episode" "{NEXT_EPISODE}"
|
||||
|
||||
draw_rule
|
||||
|
||||
print_kv "Genres" "{GENRES}"
|
||||
print_kv "Format" "{FORMAT}"
|
||||
|
||||
draw_rule
|
||||
|
||||
print_kv "List Status" "{USER_STATUS}"
|
||||
print_kv "Progress" "{USER_PROGRESS}"
|
||||
|
||||
draw_rule
|
||||
|
||||
print_kv "Start Date" "{START_DATE}"
|
||||
print_kv "End Date" "{END_DATE}"
|
||||
|
||||
draw_rule
|
||||
|
||||
print_kv "Studios" "{STUDIOS}"
|
||||
print_kv "Synonymns" "{SYNONYMNS}"
|
||||
print_kv "Tags" "{TAGS}"
|
||||
|
||||
draw_rule
|
||||
|
||||
# Synopsis
|
||||
|
||||
Reference in New Issue
Block a user