mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 07:40:41 -08:00
feat: review previews in python
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import sys
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.rule import Rule
|
||||||
|
from rich.markdown import Markdown
|
||||||
|
|
||||||
|
console = Console(force_terminal=True, color_system="truecolor")
|
||||||
|
|
||||||
|
HEADER_COLOR = sys.argv[1]
|
||||||
|
SEPARATOR_COLOR = sys.argv[2]
|
||||||
|
|
||||||
|
|
||||||
|
def rule(title: str | None = None):
|
||||||
|
console.print(Rule(style=f"rgb({SEPARATOR_COLOR})"))
|
||||||
|
|
||||||
|
|
||||||
|
console.print("{REVIEWER_NAME}", justify="center")
|
||||||
|
|
||||||
|
left = [
|
||||||
|
("Summary",),
|
||||||
|
]
|
||||||
|
right = [
|
||||||
|
("{REVIEW_SUMMARY}",),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
for L_grp, R_grp in zip(left, right):
|
||||||
|
table = Table.grid(expand=True)
|
||||||
|
table.add_column(justify="left", no_wrap=True)
|
||||||
|
table.add_column(justify="right", overflow="fold")
|
||||||
|
for L, R in zip(L_grp, R_grp):
|
||||||
|
table.add_row(f"[bold rgb({HEADER_COLOR})]{L} [/]", f"{R}")
|
||||||
|
|
||||||
|
rule()
|
||||||
|
console.print(table)
|
||||||
|
|
||||||
|
|
||||||
|
rule()
|
||||||
|
console.print(Markdown("""{REVIEW_BODY}"""))
|
||||||
|
|||||||
@@ -124,12 +124,10 @@ logger = logging.getLogger(__name__)
|
|||||||
PREVIEWS_CACHE_DIR = APP_CACHE_DIR / "previews"
|
PREVIEWS_CACHE_DIR = APP_CACHE_DIR / "previews"
|
||||||
IMAGES_CACHE_DIR = PREVIEWS_CACHE_DIR / "images"
|
IMAGES_CACHE_DIR = PREVIEWS_CACHE_DIR / "images"
|
||||||
INFO_CACHE_DIR = PREVIEWS_CACHE_DIR / "info"
|
INFO_CACHE_DIR = PREVIEWS_CACHE_DIR / "info"
|
||||||
REVIEWS_CACHE_DIR = PREVIEWS_CACHE_DIR / "reviews"
|
|
||||||
AIRING_SCHEDULE_CACHE_DIR = PREVIEWS_CACHE_DIR / "airing_schedule"
|
AIRING_SCHEDULE_CACHE_DIR = PREVIEWS_CACHE_DIR / "airing_schedule"
|
||||||
|
|
||||||
FZF_SCRIPTS_DIR = SCRIPTS_DIR / "fzf"
|
FZF_SCRIPTS_DIR = SCRIPTS_DIR / "fzf"
|
||||||
TEMPLATE_PREVIEW_SCRIPT = (FZF_SCRIPTS_DIR / "preview.py").read_text(encoding="utf-8")
|
TEMPLATE_PREVIEW_SCRIPT = (FZF_SCRIPTS_DIR / "preview.py").read_text(encoding="utf-8")
|
||||||
TEMPLATE_REVIEW_PREVIEW_SCRIPT = ""
|
|
||||||
TEMPLATE_AIRING_SCHEDULE_PREVIEW_SCRIPT = ""
|
TEMPLATE_AIRING_SCHEDULE_PREVIEW_SCRIPT = ""
|
||||||
DYNAMIC_PREVIEW_SCRIPT = ""
|
DYNAMIC_PREVIEW_SCRIPT = ""
|
||||||
|
|
||||||
@@ -417,6 +415,48 @@ def get_character_preview(choice_map: Dict[str, Character], config: AppConfig) -
|
|||||||
return preview_script
|
return preview_script
|
||||||
|
|
||||||
|
|
||||||
|
def get_review_preview(choice_map: Dict[str, MediaReview], config: AppConfig) -> str:
|
||||||
|
"""
|
||||||
|
Generate the generic loader script for review previews and start background caching.
|
||||||
|
"""
|
||||||
|
|
||||||
|
IMAGES_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
INFO_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
HEADER_COLOR = config.fzf.preview_header_color.split(",")
|
||||||
|
SEPARATOR_COLOR = config.fzf.preview_separator_color.split(",")
|
||||||
|
|
||||||
|
# Start managed background caching for episodes
|
||||||
|
try:
|
||||||
|
preview_manager = _get_preview_manager()
|
||||||
|
worker = preview_manager.get_review_worker()
|
||||||
|
worker.cache_review_previews(choice_map, config)
|
||||||
|
logger.debug("Started background caching for review previews")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to start episode background caching: {e}")
|
||||||
|
|
||||||
|
# Use the generic loader script
|
||||||
|
preview_script = TEMPLATE_PREVIEW_SCRIPT
|
||||||
|
|
||||||
|
replacements = {
|
||||||
|
"PREVIEW_MODE": config.general.preview,
|
||||||
|
"IMAGE_CACHE_DIR": str(IMAGES_CACHE_DIR),
|
||||||
|
"INFO_CACHE_DIR": str(INFO_CACHE_DIR),
|
||||||
|
"IMAGE_RENDERER": config.general.image_renderer,
|
||||||
|
# Color codes
|
||||||
|
"HEADER_COLOR": ",".join(HEADER_COLOR),
|
||||||
|
"SEPARATOR_COLOR": ",".join(SEPARATOR_COLOR),
|
||||||
|
"PREFIX": "review",
|
||||||
|
"KEY": "",
|
||||||
|
"SCALE_UP": str(config.general.preview_scale_up),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in replacements.items():
|
||||||
|
preview_script = preview_script.replace(f"{{{key}}}", value)
|
||||||
|
|
||||||
|
return preview_script
|
||||||
|
|
||||||
|
|
||||||
def get_dynamic_anime_preview(config: AppConfig) -> str:
|
def get_dynamic_anime_preview(config: AppConfig) -> str:
|
||||||
"""
|
"""
|
||||||
Generate dynamic anime preview script for search functionality.
|
Generate dynamic anime preview script for search functionality.
|
||||||
@@ -475,9 +515,7 @@ def _get_preview_manager() -> PreviewWorkerManager:
|
|||||||
"""Get or create the global preview worker manager."""
|
"""Get or create the global preview worker manager."""
|
||||||
global _preview_manager
|
global _preview_manager
|
||||||
if _preview_manager is None:
|
if _preview_manager is None:
|
||||||
_preview_manager = PreviewWorkerManager(
|
_preview_manager = PreviewWorkerManager(IMAGES_CACHE_DIR, INFO_CACHE_DIR)
|
||||||
IMAGES_CACHE_DIR, INFO_CACHE_DIR, REVIEWS_CACHE_DIR
|
|
||||||
)
|
|
||||||
return _preview_manager
|
return _preview_manager
|
||||||
|
|
||||||
|
|
||||||
@@ -503,41 +541,6 @@ def get_preview_worker_status() -> dict:
|
|||||||
return {"preview_worker": None, "episode_worker": None}
|
return {"preview_worker": None, "episode_worker": None}
|
||||||
|
|
||||||
|
|
||||||
def get_review_preview(choice_map: Dict[str, MediaReview], config: AppConfig) -> str:
|
|
||||||
"""
|
|
||||||
Generate the generic loader script for review previews and start background caching.
|
|
||||||
"""
|
|
||||||
|
|
||||||
REVIEWS_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
preview_manager = _get_preview_manager()
|
|
||||||
worker = preview_manager.get_review_worker()
|
|
||||||
worker.cache_review_previews(choice_map, config)
|
|
||||||
logger.debug("Started background caching for review previews")
|
|
||||||
|
|
||||||
# Use the generic loader script
|
|
||||||
preview_script = TEMPLATE_REVIEW_PREVIEW_SCRIPT
|
|
||||||
path_sep = "\\" if PLATFORM == "win32" else "/"
|
|
||||||
|
|
||||||
# Inject the correct cache path and color codes
|
|
||||||
replacements = {
|
|
||||||
"PREVIEW_MODE": config.general.preview,
|
|
||||||
"INFO_CACHE_DIR": str(REVIEWS_CACHE_DIR),
|
|
||||||
"PATH_SEP": path_sep,
|
|
||||||
"C_TITLE": ansi.get_true_fg(config.fzf.header_color.split(","), bold=True),
|
|
||||||
"C_KEY": ansi.get_true_fg(config.fzf.header_color.split(","), bold=True),
|
|
||||||
"C_VALUE": ansi.get_true_fg(config.fzf.header_color.split(","), bold=True),
|
|
||||||
"C_RULE": ansi.get_true_fg(
|
|
||||||
config.fzf.preview_separator_color.split(","), bold=True
|
|
||||||
),
|
|
||||||
"RESET": ansi.RESET,
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value in replacements.items():
|
|
||||||
preview_script = preview_script.replace(f"{{{key}}}", value)
|
|
||||||
|
|
||||||
return preview_script
|
|
||||||
|
|
||||||
|
|
||||||
def get_airing_schedule_preview(
|
def get_airing_schedule_preview(
|
||||||
schedule_result: AiringScheduleResult, config: AppConfig, anime_title: str = "Anime"
|
schedule_result: AiringScheduleResult, config: AppConfig, anime_title: str = "Anime"
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ including image downloads and info text generation with proper lifecycle managem
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -421,9 +422,12 @@ class ReviewCacheWorker(ManagedBackgroundWorker):
|
|||||||
Specialized background worker for caching fully-rendered media review previews.
|
Specialized background worker for caching fully-rendered media review previews.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, reviews_cache_dir, max_workers: int = 10):
|
def __init__(
|
||||||
|
self, images_cache_dir: Path, info_cache_dir: Path, max_workers: int = 10
|
||||||
|
):
|
||||||
super().__init__(max_workers=max_workers, name="ReviewCacheWorker")
|
super().__init__(max_workers=max_workers, name="ReviewCacheWorker")
|
||||||
self.reviews_cache_dir = reviews_cache_dir
|
self.images_cache_dir = images_cache_dir
|
||||||
|
self.info_cache_dir = info_cache_dir
|
||||||
|
|
||||||
def cache_review_previews(
|
def cache_review_previews(
|
||||||
self, choice_map: Dict[str, MediaReview], config: AppConfig
|
self, choice_map: Dict[str, MediaReview], config: AppConfig
|
||||||
@@ -471,7 +475,7 @@ class ReviewCacheWorker(ManagedBackgroundWorker):
|
|||||||
def _save_preview_content(self, content: str, hash_id: str) -> None:
|
def _save_preview_content(self, content: str, hash_id: str) -> None:
|
||||||
"""Saves the final preview content to the cache."""
|
"""Saves the final preview content to the cache."""
|
||||||
try:
|
try:
|
||||||
info_path = self.reviews_cache_dir / hash_id
|
info_path = self.info_cache_dir / hash_id
|
||||||
with AtomicWriter(info_path) as f:
|
with AtomicWriter(info_path) as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
logger.debug(f"Successfully cached review preview: {hash_id}")
|
logger.debug(f"Successfully cached review preview: {hash_id}")
|
||||||
@@ -482,7 +486,7 @@ class ReviewCacheWorker(ManagedBackgroundWorker):
|
|||||||
def _get_cache_hash(self, text: str) -> str:
|
def _get_cache_hash(self, text: str) -> str:
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
|
|
||||||
return sha256(text.encode("utf-8")).hexdigest()
|
return "review-" + sha256(text.encode("utf-8")).hexdigest() + ".py"
|
||||||
|
|
||||||
def _on_task_completed(self, task: WorkerTask, future) -> None:
|
def _on_task_completed(self, task: WorkerTask, future) -> None:
|
||||||
super()._on_task_completed(task, future)
|
super()._on_task_completed(task, future)
|
||||||
@@ -757,7 +761,7 @@ class PreviewWorkerManager:
|
|||||||
caching workers with automatic lifecycle management.
|
caching workers with automatic lifecycle management.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, images_cache_dir, info_cache_dir, reviews_cache_dir):
|
def __init__(self, images_cache_dir, info_cache_dir):
|
||||||
"""
|
"""
|
||||||
Initialize the preview worker manager.
|
Initialize the preview worker manager.
|
||||||
|
|
||||||
@@ -768,7 +772,6 @@ class PreviewWorkerManager:
|
|||||||
"""
|
"""
|
||||||
self.images_cache_dir = images_cache_dir
|
self.images_cache_dir = images_cache_dir
|
||||||
self.info_cache_dir = info_cache_dir
|
self.info_cache_dir = info_cache_dir
|
||||||
self.reviews_cache_dir = reviews_cache_dir
|
|
||||||
self._preview_worker: Optional[PreviewCacheWorker] = None
|
self._preview_worker: Optional[PreviewCacheWorker] = None
|
||||||
self._episode_worker: Optional[EpisodeCacheWorker] = None
|
self._episode_worker: Optional[EpisodeCacheWorker] = None
|
||||||
self._review_worker: Optional[ReviewCacheWorker] = None
|
self._review_worker: Optional[ReviewCacheWorker] = None
|
||||||
@@ -812,7 +815,9 @@ class PreviewWorkerManager:
|
|||||||
# Clean up old worker
|
# Clean up old worker
|
||||||
thread_manager.shutdown_worker("review_cache_worker")
|
thread_manager.shutdown_worker("review_cache_worker")
|
||||||
|
|
||||||
self._review_worker = ReviewCacheWorker(self.reviews_cache_dir)
|
self._review_worker = ReviewCacheWorker(
|
||||||
|
self.images_cache_dir, self.info_cache_dir
|
||||||
|
)
|
||||||
self._review_worker.start()
|
self._review_worker.start()
|
||||||
thread_manager.register_worker("review_cache_worker", self._review_worker)
|
thread_manager.register_worker("review_cache_worker", self._review_worker)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user