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"
IMAGES_CACHE_DIR = PREVIEWS_CACHE_DIR / "images"
INFO_CACHE_DIR = PREVIEWS_CACHE_DIR / "info"
REVIEWS_CACHE_DIR = PREVIEWS_CACHE_DIR / "reviews"
AIRING_SCHEDULE_CACHE_DIR = PREVIEWS_CACHE_DIR / "airing_schedule"
FZF_SCRIPTS_DIR = SCRIPTS_DIR / "fzf"
TEMPLATE_PREVIEW_SCRIPT = (FZF_SCRIPTS_DIR / "preview.py").read_text(encoding="utf-8")
TEMPLATE_REVIEW_PREVIEW_SCRIPT = ""
TEMPLATE_AIRING_SCHEDULE_PREVIEW_SCRIPT = ""
DYNAMIC_PREVIEW_SCRIPT = ""
@@ -417,6 +415,48 @@ def get_character_preview(choice_map: Dict[str, Character], config: AppConfig) -
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:
"""
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."""
global _preview_manager
if _preview_manager is None:
_preview_manager = PreviewWorkerManager(
IMAGES_CACHE_DIR, INFO_CACHE_DIR, REVIEWS_CACHE_DIR
)
_preview_manager = PreviewWorkerManager(IMAGES_CACHE_DIR, INFO_CACHE_DIR)
return _preview_manager
@@ -503,41 +541,6 @@ def get_preview_worker_status() -> dict:
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(
schedule_result: AiringScheduleResult, config: AppConfig, anime_title: str = "Anime"
) -> str:

View File

@@ -6,6 +6,7 @@ including image downloads and info text generation with proper lifecycle managem
"""
import logging
from pathlib import Path
from typing import Dict, List, Optional
import httpx
@@ -421,9 +422,12 @@ class ReviewCacheWorker(ManagedBackgroundWorker):
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")
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(
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:
"""Saves the final preview content to the cache."""
try:
info_path = self.reviews_cache_dir / hash_id
info_path = self.info_cache_dir / hash_id
with AtomicWriter(info_path) as f:
f.write(content)
logger.debug(f"Successfully cached review preview: {hash_id}")
@@ -482,7 +486,7 @@ class ReviewCacheWorker(ManagedBackgroundWorker):
def _get_cache_hash(self, text: str) -> str:
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:
super()._on_task_completed(task, future)
@@ -757,7 +761,7 @@ class PreviewWorkerManager:
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.
@@ -768,7 +772,6 @@ class PreviewWorkerManager:
"""
self.images_cache_dir = images_cache_dir
self.info_cache_dir = info_cache_dir
self.reviews_cache_dir = reviews_cache_dir
self._preview_worker: Optional[PreviewCacheWorker] = None
self._episode_worker: Optional[EpisodeCacheWorker] = None
self._review_worker: Optional[ReviewCacheWorker] = None
@@ -812,7 +815,9 @@ class PreviewWorkerManager:
# Clean up old 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()
thread_manager.register_worker("review_cache_worker", self._review_worker)