playreel/backend/pipelines/artifact.py
2026-09-08 13:00:23 +09:00

48 lines
1.5 KiB
Python

"""단계 산출물의 blob 저장·회수
경로는 <파이프라인>/<task_id>/<이름>
"""
import io
from PIL import Image
from utils import blob
JPEG_QUALITY = 90
def artifact_path(pipeline: str, task_id: str, name: str) -> str:
return f"{pipeline}/{task_id}/{name}"
async def store_bytes(pipeline: str, task_id: str, name: str, data: bytes) -> str:
return await blob.upload_bytes(data, artifact_path(pipeline, task_id, name))
async def store_image(pipeline: str, task_id: str, name: str, image: Image.Image) -> str:
buffer = io.BytesIO()
if name.endswith(".png"):
image.save(buffer, "PNG")
else:
image.convert("RGB").save(buffer, "JPEG", quality=JPEG_QUALITY)
return await store_bytes(pipeline, task_id, name, buffer.getvalue())
async def load_image(url: str) -> Image.Image:
return Image.open(io.BytesIO(await blob.download_bytes(url)))
async def load_optional(url: str | None) -> bytes | None:
return await blob.download_bytes(url) if url else None
def contact_sheet(images: list[Image.Image], tile_width: int = 240,
gap: int = 8) -> Image.Image:
"""검수 화면에 한 줄로 늘어놓는 시트"""
tiles = [image.resize((tile_width, round(tile_width * image.height / image.width)))
for image in images]
height = max(tile.height for tile in tiles)
sheet = Image.new("RGB", ((tile_width + gap) * len(tiles) - gap, height), "black")
for order, tile in enumerate(tiles):
sheet.paste(tile, (order * (tile_width + gap), 0))
return sheet