| Server IP : 3.147.158.171 / Your IP : 216.73.216.216 Web Server : Apache/2.4.67 (Amazon Linux) OpenSSL/3.5.5 System : Linux ip-172-31-2-178.us-east-2.compute.internal 6.1.172-216.329.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Wed May 20 06:31:34 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.21 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /tsai/www/html/api.johnturman.net/scripts/ |
Upload File : |
"""
Thumbnail Backfill Script
Walks every configured site's `create/` and `upload/` image directories and
generates any missing thumbnails via ImageService.get_or_create_thumbnail.
Idempotent — the cache check inside get_or_create_thumbnail skips up-to-date
thumbs, so re-running produces zero work.
Usage:
cd api
python scripts/backfill_thumbnails.py
Run once locally, and once per production server.
"""
import logging
import os
import sys
from pathlib import Path
API_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(API_DIR))
from dotenv import load_dotenv
load_dotenv(API_DIR / '.env')
from app.services.image import ImageService
from app.services.site_config_service import SiteConfigService
from app.utilities.path_utils import PathUtils
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"}
BACKFILL_TYPES = ("create", "upload")
logging.basicConfig(
level=logging.INFO,
format='%(levelname)s %(asctime)s %(message)s',
datefmt='%m/%d/%Y %I:%M:%S %p',
)
logger = logging.getLogger(__name__)
def backfill_directory(image_service: ImageService, type_dir: str) -> tuple[int, int, int]:
"""Generate missing thumbs under type_dir. Returns (generated, cached, errors)."""
if not os.path.isdir(type_dir):
return 0, 0, 0
generated = 0
cached = 0
errors = 0
for entry in os.scandir(type_dir):
if not entry.is_file():
continue
ext = os.path.splitext(entry.name)[1].lower()
if ext not in ALLOWED_EXTENSIONS:
continue
thumb_dir = os.path.join(type_dir, 'thumb')
basename = os.path.splitext(entry.name)[0]
thumb_path = os.path.join(thumb_dir, f"{basename}.jpg")
had_thumb = (
os.path.exists(thumb_path)
and os.path.getmtime(thumb_path) >= entry.stat().st_mtime
)
try:
image_service.get_or_create_thumbnail(entry.path)
except Exception as e:
logger.warning(f"Failed to generate thumbnail for {entry.path}: {e}")
errors += 1
continue
if had_thumb:
cached += 1
else:
generated += 1
logger.info(f"Generated thumb for {entry.path}")
return generated, cached, errors
def main() -> int:
image_service = ImageService()
path_utils = PathUtils()
sites = SiteConfigService().get_all_sites()
total_generated = 0
total_cached = 0
total_errors = 0
for site in sites:
site_dir = path_utils.get_site_files_dir(site)
if not os.path.isdir(site_dir):
logger.info(f"[{site.slug}] no files dir at {site_dir} — skipping")
continue
site_generated = 0
site_cached = 0
site_errors = 0
for img_type in BACKFILL_TYPES:
type_dir = os.path.join(site_dir, img_type)
g, c, e = backfill_directory(image_service, type_dir)
site_generated += g
site_cached += c
site_errors += e
logger.info(
f"[{site.slug}] generated={site_generated} cached={site_cached} errors={site_errors}"
)
total_generated += site_generated
total_cached += site_cached
total_errors += site_errors
logger.info(
f"Done. generated={total_generated} cached={total_cached} errors={total_errors}"
)
return 0 if total_errors == 0 else 1
if __name__ == '__main__':
sys.exit(main())