feat(image-render): gpt_image provider + media helper (SP image)
This commit is contained in:
18
services/image-render/providers/_media.py
Normal file
18
services/image-render/providers/_media.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""b64 이미지 → NAS SMB 경로 저장 → /media/image URL 반환."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
IMAGE_MEDIA_ROOT = os.getenv("IMAGE_MEDIA_ROOT", "/mnt/nas/webpage/data/image")
|
||||||
|
IMAGE_MEDIA_URL_PREFIX = os.getenv("IMAGE_MEDIA_URL_PREFIX", "/media/image")
|
||||||
|
|
||||||
|
|
||||||
|
def save_b64_png(task_id: str, b64_data: str) -> str:
|
||||||
|
os.makedirs(IMAGE_MEDIA_ROOT, exist_ok=True)
|
||||||
|
fname = f"{task_id}-{uuid.uuid4().hex[:8]}.png"
|
||||||
|
path = os.path.join(IMAGE_MEDIA_ROOT, fname)
|
||||||
|
with open(path, "wb") as f:
|
||||||
|
f.write(base64.b64decode(b64_data))
|
||||||
|
return f"{IMAGE_MEDIA_URL_PREFIX}/{fname}"
|
||||||
47
services/image-render/providers/gpt_image.py
Normal file
47
services/image-render/providers/gpt_image.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""GPT Image 2.0 — OpenAI Images API.
|
||||||
|
|
||||||
|
POST https://api.openai.com/v1/images/generations
|
||||||
|
body {model:"gpt-image-1", prompt, size, n:1} → data[0].b64_json
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from nas_client import webhook_update_task
|
||||||
|
from providers._media import save_b64_png
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
OPENAI_URL = "https://api.openai.com/v1/images/generations"
|
||||||
|
DEFAULT_MODEL = "gpt-image-1"
|
||||||
|
|
||||||
|
|
||||||
|
def run_gpt_image_generation(task_id: str, params: dict) -> None:
|
||||||
|
try:
|
||||||
|
if not os.getenv("OPENAI_API_KEY"):
|
||||||
|
webhook_update_task(task_id, "failed", 0, "", error="OPENAI_API_KEY 미설정 (Windows .env)")
|
||||||
|
return
|
||||||
|
webhook_update_task(task_id, "processing", 10, "GPT Image 호출 중...")
|
||||||
|
body = {
|
||||||
|
"model": params.get("model") or DEFAULT_MODEL,
|
||||||
|
"prompt": params["prompt"],
|
||||||
|
"size": params.get("size") or "1024x1024",
|
||||||
|
"n": 1,
|
||||||
|
}
|
||||||
|
resp = requests.post(
|
||||||
|
OPENAI_URL,
|
||||||
|
headers={"Authorization": f"Bearer {os.getenv('OPENAI_API_KEY')}", "Content-Type": "application/json"},
|
||||||
|
json=body,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
webhook_update_task(task_id, "failed", 0, "", error=f"OpenAI {resp.status_code}: {resp.text[:200]}")
|
||||||
|
return
|
||||||
|
b64 = resp.json()["data"][0]["b64_json"]
|
||||||
|
url = save_b64_png(task_id, b64)
|
||||||
|
webhook_update_task(task_id, "succeeded", 100, "완료", image_url=url)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("gpt_image task=%s 실패", task_id)
|
||||||
|
webhook_update_task(task_id, "failed", 0, "", error=str(e))
|
||||||
32
services/image-render/tests/test_gpt_image.py
Normal file
32
services/image-render/tests/test_gpt_image.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import providers.gpt_image as gi
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_key_reports_failed(monkeypatch):
|
||||||
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(gi, "webhook_update_task", lambda *a, **k: calls.append((a, k)))
|
||||||
|
gi.run_gpt_image_generation("t1", {"prompt": "a cat"})
|
||||||
|
# 마지막 호출이 failed
|
||||||
|
assert calls[-1][0][1] == "failed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_success_saves_and_reports_url(monkeypatch):
|
||||||
|
monkeypatch.setenv("OPENAI_API_KEY", "sk-test")
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(gi, "webhook_update_task", lambda *a, **k: calls.append((a, k)))
|
||||||
|
monkeypatch.setattr(gi, "save_b64_png", lambda tid, b64: "/media/image/t1.png")
|
||||||
|
|
||||||
|
class FakeResp:
|
||||||
|
status_code = 200
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return {"data": [{"b64_json": "ZmFrZQ=="}]}
|
||||||
|
|
||||||
|
def raise_for_status(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
monkeypatch.setattr(gi.requests, "post", lambda *a, **k: FakeResp())
|
||||||
|
|
||||||
|
gi.run_gpt_image_generation("t1", {"prompt": "a cat"})
|
||||||
|
succeeded = [c for c in calls if c[0][1] == "succeeded"]
|
||||||
|
assert succeeded and succeeded[-1][1]["image_url"] == "/media/image/t1.png"
|
||||||
Reference in New Issue
Block a user