feat: review previews in python

This commit is contained in:
Benexl
2025-11-30 15:15:11 +03:00
parent e8387f3db9
commit 6ccd96d252
3 changed files with 94 additions and 47 deletions

View File

@@ -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}"""))

View File

@@ -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:

View File

@@ -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)