mirror of
https://github.com/usetrmnl/byos_fastapi.git
synced 2026-04-29 13:44:09 -07:00
93 lines
2.8 KiB
Python
93 lines
2.8 KiB
Python
import asyncio
|
|
import logging
|
|
import os
|
|
import random
|
|
from pathlib import Path
|
|
from typing import Optional, Sequence, Tuple
|
|
|
|
from PIL import Image, ImageOps
|
|
|
|
from .base import PhotographicPlugin, PluginOutput
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DEFAULT_IMAGE_ROOT = os.getenv('RANDOM_IMAGE_ROOT', Path(os.environ.get('HOME', '~')).expanduser() / 'Pictures')
|
|
|
|
SUPPORTED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.bmp', '.gif'}
|
|
|
|
|
|
class RandomImagePlugin(PhotographicPlugin):
|
|
"""Select a random image from disk, adapt it, and add it to the rotation."""
|
|
|
|
BASENAME = "random_image"
|
|
OUTPUT_SUBDIR = "random"
|
|
REGISTRY_ORDER = 50
|
|
REFRESH_INTERVAL = 1800
|
|
DISPLAY_NAME = "Random Image"
|
|
|
|
def get_content_ttl(self) -> int:
|
|
return 3600 # 60 minutes
|
|
|
|
async def run(self, **kwargs) -> Optional[PluginOutput]:
|
|
target_size = kwargs.get('target_size', (800, 480))
|
|
output_dir = kwargs.get('output_dir', 'web')
|
|
source_root = Path(kwargs.get('image_root', DEFAULT_IMAGE_ROOT))
|
|
return await asyncio.to_thread(
|
|
self._run_sync,
|
|
target_size,
|
|
output_dir,
|
|
source_root
|
|
)
|
|
|
|
def _run_sync(
|
|
self,
|
|
target_size: Sequence[int],
|
|
output_dir: str,
|
|
source_root: Path
|
|
) -> Optional[PluginOutput]:
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
if not source_root.exists():
|
|
logger.warning("Random image root %s does not exist", source_root)
|
|
return None
|
|
|
|
image_path = self._pick_random_image(source_root)
|
|
if not image_path:
|
|
logger.warning("No images found under %s", source_root)
|
|
return None
|
|
|
|
adapted = self._prepare_image(image_path, target_size)
|
|
output = self.save_assets(
|
|
adapted,
|
|
output_dir,
|
|
'random_image',
|
|
dither_mode='floyd-steinberg'
|
|
)
|
|
logger.info(
|
|
"Random image assets saved to %s and %s",
|
|
output.monochrome_path,
|
|
output.grayscale_path
|
|
)
|
|
return output
|
|
|
|
def _pick_random_image(self, root: Path) -> Optional[Path]:
|
|
candidates = [
|
|
path for path in root.rglob('*')
|
|
if path.is_file() and path.suffix.lower() in SUPPORTED_EXTENSIONS
|
|
]
|
|
if not candidates:
|
|
return None
|
|
return random.choice(candidates)
|
|
|
|
def _prepare_image(self, image_path: Path, target_size: Sequence[int]) -> Image.Image:
|
|
with Image.open(image_path) as img:
|
|
rgb_image = img.convert('RGB')
|
|
fitted = ImageOps.fit(
|
|
rgb_image,
|
|
target_size,
|
|
method=Image.Resampling.LANCZOS,
|
|
bleed=0.0,
|
|
centering=(0.5, 0.5)
|
|
)
|
|
return fitted.convert('L')
|