Files
2025-12-14 00:05:05 +00:00

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