API: use ImageData type

This commit is contained in:
Jörn-Michael Miehe 2025-11-30 14:19:05 +01:00
parent af08917155
commit 60a417dc64
2 changed files with 54 additions and 26 deletions

View file

@ -1,3 +1,4 @@
import base64
import itertools
import random
import re
@ -5,9 +6,9 @@ from datetime import date, datetime, timedelta
from io import BytesIO
from typing import Any, Awaitable, Callable, Iterable, Self, Sequence, TypeVar
from fastapi.responses import StreamingResponse
from PIL import Image as PILImage
from PIL.Image import Image, Resampling
from pydantic import BaseModel
from .config import get_config
from .dav.webdav import WebDAV
@ -115,25 +116,51 @@ async def load_image(file_name: str) -> Image:
return PILImage.open(BytesIO(await WebDAV.read_bytes(file_name)))
async def api_return_ico(img: Image) -> StreamingResponse:
class ImageData(BaseModel):
width: int
height: int
aspect_ratio: float
data_url: str
@classmethod
def create(
cls,
*,
media_type: str,
content: BytesIO,
width: int,
height: int,
) -> Self:
img_data = base64.b64encode(content.getvalue()).decode("utf-8")
return cls(
width=width,
height=height,
aspect_ratio=width / height,
data_url=f"data:{media_type};base64,{img_data}",
)
async def api_return_ico(img: Image) -> ImageData:
"""
ICO-Bild mit API zurückgeben
"""
# JPEG-Daten in Puffer speichern
# ICO-Daten in Puffer speichern (256px)
img_buffer = BytesIO()
img.resize(size=(256, 256), resample=Resampling.LANCZOS)
img.save(img_buffer, format="ICO")
img_buffer.seek(0)
# zurückgeben
return StreamingResponse(
return ImageData.create(
media_type="image/x-icon",
content=img_buffer,
width=img.width,
height=img.height,
)
async def api_return_jpeg(img: Image) -> StreamingResponse:
async def api_return_jpeg(img: Image) -> ImageData:
"""
JPEG-Bild mit API zurückgeben
"""
@ -141,12 +168,13 @@ async def api_return_jpeg(img: Image) -> StreamingResponse:
# JPEG-Daten in Puffer speichern
img_buffer = BytesIO()
img.save(img_buffer, format="JPEG", quality=85)
img_buffer.seek(0)
# zurückgeben
return StreamingResponse(
return ImageData.create(
media_type="image/jpeg",
content=img_buffer,
width=img.width,
height=img.height,
)

View file

@ -1,25 +1,31 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import StreamingResponse
from PIL.Image import Image
from ..core.calendar_config import CalendarConfig, DoorsSaved, get_calendar_config
from ..core.calendar_config import (
CalendarConfig,
DoorsSaved,
get_calendar_config,
)
from ..core.config import Config, Site, get_config
from ..core.depends import get_all_event_dates, get_day_image
from ..core.helpers import EventDates, api_return_ico, api_return_jpeg, load_image
from ..core.helpers import (
EventDates,
ImageData,
api_return_ico,
api_return_jpeg,
load_image,
)
from ._security import user_can_view_day, user_is_admin, user_visible_days
router = APIRouter(prefix="/user", tags=["user"])
@router.get(
"/background_image",
response_class=StreamingResponse,
)
@router.get("/background_image")
async def get_background_image(
cal_cfg: CalendarConfig = Depends(get_calendar_config),
) -> StreamingResponse:
) -> ImageData:
"""
Hintergrundbild laden
"""
@ -27,13 +33,10 @@ async def get_background_image(
return await api_return_jpeg(await load_image(f"files/{cal_cfg.background}"))
@router.get(
"/favicon",
response_class=StreamingResponse,
)
@router.get("/favicon")
async def get_favicon(
cal_cfg: CalendarConfig = Depends(get_calendar_config),
) -> StreamingResponse:
) -> ImageData:
"""
Favicon laden
"""
@ -68,15 +71,12 @@ async def get_doors(
return [door for door in cal_cfg.doors if door.day in visible_days]
@router.get(
"/image_{day}",
response_class=StreamingResponse,
)
@router.get("/image_{day}")
async def get_image_for_day(
user_can_view: bool = Depends(user_can_view_day),
is_admin: bool = Depends(user_is_admin),
image: Image | None = Depends(get_day_image),
) -> StreamingResponse:
) -> ImageData:
"""
Bild für einen Tag erstellen
"""