playreel/backend/services/upscale_poster.py

58 lines
2.3 KiB
Python

"""③ upscale — Topaz Text Refine 2배.
공개 채널의 공연 포스터는 전부 750px대라 그대로는 못 쓴다. Bytedance Upscale은 광선을
재작화하고 글자에 없던 무늬를 그려서(MAE 13 vs 5) Text Refine만 쓴다.
목적은 확대보다 GIF 128색 밴딩 해소다 — 밴딩이 남으면 생성 모델이 그것을 무늬로 읽는다.
"""
import io
import tempfile
from contextlib import contextmanager
from pathlib import Path
import httpx
from PIL import Image
from models.upscale import UpscaleResult
from settings import settings
from utils.higgsfield import (TOPAZ_IMAGE, TOPAZ_TEXT_REFINE, assert_account,
parse_result_url, run_cli)
OUTPUT_WIDTH, OUTPUT_HEIGHT = 1500, 2000
UPSCALE_CREDITS = 2
CLI_TIMEOUT = 420
MIN_IMAGE_BYTES = 10_000
@contextmanager
def as_png_path(image: bytes | Path):
"""CLI의 --image가 파일 경로만 받으므로 임시 파일로 내린다.
NOL 포스터는 팔레트 GIF(모드 P)로 오는데 그대로 올리면 잡이 failed로 돌아온다.
RGB PNG로 정규화해 보낸다.
"""
source = Image.open(image if isinstance(image, Path) else io.BytesIO(image))
with tempfile.NamedTemporaryFile(suffix=".png") as handle:
source.convert("RGB").save(handle, "PNG")
handle.flush()
yield Path(handle.name)
async def upscale_poster(poster: bytes | Path) -> UpscaleResult:
await assert_account(settings.higgsfield_account) # 크레딧 쓰기 전에
with as_png_path(poster) as source:
output = await run_cli(
"generate", "create", TOPAZ_IMAGE, "--image", str(source),
"--output_width", str(OUTPUT_WIDTH), "--output_height", str(OUTPUT_HEIGHT),
"--variant", TOPAZ_TEXT_REFINE, "--wait", "--wait-timeout", "4m", "--json",
timeout=CLI_TIMEOUT)
async with httpx.AsyncClient(timeout=300, follow_redirects=True) as client:
response = await client.get(parse_result_url(output))
response.raise_for_status()
if len(response.content) < MIN_IMAGE_BYTES:
raise RuntimeError(f"업스케일 결과가 너무 작다 ({len(response.content)}바이트)")
return UpscaleResult(image=response.content, credits=UPSCALE_CREDITS,
variant=TOPAZ_TEXT_REFINE)