fix: align Gemini image sizes with official presets
This commit is contained in:
96
api/main.py
96
api/main.py
@@ -246,6 +246,36 @@ IMAGE_SIZE_CHOICES = [
|
||||
"description": "适合高清横版视频封面和大屏展示",
|
||||
},
|
||||
]
|
||||
GEMINI_IMAGE_SIZE_CHOICES = [
|
||||
{
|
||||
"id": "auto",
|
||||
"label": "自动",
|
||||
"value": "auto",
|
||||
"ratio": "auto",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"description": "由 Gemini 自行决定输出尺寸,生成后显示实际像素",
|
||||
},
|
||||
{"id": "1024x1024", "label": "方图 1:1 · 1K · 1024×1024", "value": "1024x1024", "ratio": "1:1", "image_size": "1K", "width": 1024, "height": 1024},
|
||||
{"id": "848x1264", "label": "竖图 2:3 · 1K · 848×1264", "value": "848x1264", "ratio": "2:3", "image_size": "1K", "width": 848, "height": 1264},
|
||||
{"id": "1264x848", "label": "横图 3:2 · 1K · 1264×848", "value": "1264x848", "ratio": "3:2", "image_size": "1K", "width": 1264, "height": 848},
|
||||
{"id": "896x1200", "label": "竖图 3:4 · 1K · 896×1200", "value": "896x1200", "ratio": "3:4", "image_size": "1K", "width": 896, "height": 1200},
|
||||
{"id": "928x1152", "label": "竖图 4:5 · 1K · 928×1152", "value": "928x1152", "ratio": "4:5", "image_size": "1K", "width": 928, "height": 1152},
|
||||
{"id": "768x1376", "label": "竖屏 9:16 · 1K · 768×1376", "value": "768x1376", "ratio": "9:16", "image_size": "1K", "width": 768, "height": 1376},
|
||||
{"id": "1376x768", "label": "横屏 16:9 · 1K · 1376×768", "value": "1376x768", "ratio": "16:9", "image_size": "1K", "width": 1376, "height": 768},
|
||||
{"id": "2048x2048", "label": "方图 1:1 · 2K · 2048×2048", "value": "2048x2048", "ratio": "1:1", "image_size": "2K", "width": 2048, "height": 2048},
|
||||
{"id": "1696x2528", "label": "竖图 2:3 · 2K · 1696×2528", "value": "1696x2528", "ratio": "2:3", "image_size": "2K", "width": 1696, "height": 2528},
|
||||
{"id": "2528x1696", "label": "横图 3:2 · 2K · 2528×1696", "value": "2528x1696", "ratio": "3:2", "image_size": "2K", "width": 2528, "height": 1696},
|
||||
{"id": "1792x2400", "label": "竖图 3:4 · 2K · 1792×2400", "value": "1792x2400", "ratio": "3:4", "image_size": "2K", "width": 1792, "height": 2400},
|
||||
{"id": "1856x2304", "label": "竖图 4:5 · 2K · 1856×2304", "value": "1856x2304", "ratio": "4:5", "image_size": "2K", "width": 1856, "height": 2304},
|
||||
{"id": "1536x2752", "label": "竖屏 9:16 · 2K · 1536×2752", "value": "1536x2752", "ratio": "9:16", "image_size": "2K", "width": 1536, "height": 2752},
|
||||
{"id": "2752x1536", "label": "横屏 16:9 · 2K · 2752×1536", "value": "2752x1536", "ratio": "16:9", "image_size": "2K", "width": 2752, "height": 1536},
|
||||
{"id": "4096x4096", "label": "方图 1:1 · 4K · 4096×4096", "value": "4096x4096", "ratio": "1:1", "image_size": "4K", "width": 4096, "height": 4096},
|
||||
{"id": "3392x5056", "label": "竖图 2:3 · 4K · 3392×5056", "value": "3392x5056", "ratio": "2:3", "image_size": "4K", "width": 3392, "height": 5056},
|
||||
{"id": "5056x3392", "label": "横图 3:2 · 4K · 5056×3392", "value": "5056x3392", "ratio": "3:2", "image_size": "4K", "width": 5056, "height": 3392},
|
||||
{"id": "3072x5504", "label": "竖屏 9:16 · 4K · 3072×5504", "value": "3072x5504", "ratio": "9:16", "image_size": "4K", "width": 3072, "height": 5504},
|
||||
{"id": "5504x3072", "label": "横屏 16:9 · 4K · 5504×3072", "value": "5504x3072", "ratio": "16:9", "image_size": "4K", "width": 5504, "height": 3072},
|
||||
]
|
||||
IMAGE_QUALITY_CHOICES = [
|
||||
{
|
||||
"id": "low",
|
||||
@@ -4684,6 +4714,9 @@ def image_model_options() -> list[dict]:
|
||||
"model": GPT_IMAGE_MODEL,
|
||||
"description": "优先 GPT Image 2,必要时按后端熔断和兜底策略切到备用图片模型",
|
||||
"available": bool(IMAGE_API_KEY),
|
||||
"size_options": IMAGE_SIZE_CHOICES,
|
||||
"quality_options": IMAGE_QUALITY_CHOICES,
|
||||
"supports_custom_size": True,
|
||||
},
|
||||
{
|
||||
"id": GPT_IMAGE_MODEL,
|
||||
@@ -4691,6 +4724,9 @@ def image_model_options() -> list[dict]:
|
||||
"model": GPT_IMAGE_MODEL,
|
||||
"description": "主生图模型,适合营销图和参考图重绘",
|
||||
"available": bool(IMAGE_API_KEY),
|
||||
"size_options": IMAGE_SIZE_CHOICES,
|
||||
"quality_options": IMAGE_QUALITY_CHOICES,
|
||||
"supports_custom_size": True,
|
||||
},
|
||||
]
|
||||
if IMAGE_FALLBACK_ENABLED and IMAGE_FALLBACK_MODEL and IMAGE_FALLBACK_MODEL != GPT_IMAGE_MODEL:
|
||||
@@ -4698,8 +4734,11 @@ def image_model_options() -> list[dict]:
|
||||
"id": IMAGE_FALLBACK_MODEL,
|
||||
"label": "Gemini 图片",
|
||||
"model": IMAGE_FALLBACK_MODEL,
|
||||
"description": "备用图片模型,适合主模型慢或失败时手动选择",
|
||||
"description": "备用图片模型,使用 Gemini 官方比例和 1K/2K/4K 固定规格",
|
||||
"available": bool(IMAGE_API_KEY),
|
||||
"size_options": GEMINI_IMAGE_SIZE_CHOICES,
|
||||
"quality_options": [],
|
||||
"supports_custom_size": False,
|
||||
})
|
||||
return options
|
||||
|
||||
@@ -4708,6 +4747,10 @@ def image_size_options() -> list[dict]:
|
||||
return IMAGE_SIZE_CHOICES
|
||||
|
||||
|
||||
def gemini_image_size_options() -> list[dict]:
|
||||
return GEMINI_IMAGE_SIZE_CHOICES
|
||||
|
||||
|
||||
def image_quality_options() -> list[dict]:
|
||||
return IMAGE_QUALITY_CHOICES
|
||||
|
||||
@@ -4735,9 +4778,16 @@ def _validate_custom_image_size(width: int, height: int, raw: str) -> str:
|
||||
return f"{width}x{height}"
|
||||
|
||||
|
||||
def _normalize_image_size(raw: str | None) -> str:
|
||||
def _is_gemini_image_model(model: str | None) -> bool:
|
||||
normalized = (model or "").strip().lower()
|
||||
return bool(normalized and normalized.startswith("gemini")) or (
|
||||
bool(IMAGE_FALLBACK_MODEL) and normalized == IMAGE_FALLBACK_MODEL.lower()
|
||||
)
|
||||
|
||||
|
||||
def _normalize_image_size(raw: str | None, model: str | None = GPT_IMAGE_MODEL, fallback_to_auto: bool = False) -> str:
|
||||
value = (raw or "auto").strip().lower()
|
||||
aliases = {
|
||||
gpt_aliases = {
|
||||
"9:16": "1088x1920",
|
||||
"9x16": "1088x1920",
|
||||
"16:9": "1280x720",
|
||||
@@ -4763,7 +4813,34 @@ def _normalize_image_size(raw: str | None) -> str:
|
||||
"横图": "1536x1024",
|
||||
"横屏": "1280x720",
|
||||
}
|
||||
value = aliases.get(value, value)
|
||||
gemini_aliases = {
|
||||
"1:1": "1024x1024",
|
||||
"1x1": "1024x1024",
|
||||
"2:3": "848x1264",
|
||||
"2x3": "848x1264",
|
||||
"3:2": "1264x848",
|
||||
"3x2": "1264x848",
|
||||
"3:4": "896x1200",
|
||||
"3x4": "896x1200",
|
||||
"4:5": "928x1152",
|
||||
"4x5": "928x1152",
|
||||
"9:16": "768x1376",
|
||||
"9x16": "768x1376",
|
||||
"16:9": "1376x768",
|
||||
"16x9": "1376x768",
|
||||
"竖屏": "768x1376",
|
||||
"横屏": "1376x768",
|
||||
"方图": "1024x1024",
|
||||
}
|
||||
if _is_gemini_image_model(model):
|
||||
value = gemini_aliases.get(value, value)
|
||||
allowed = {str(item["value"]) for item in GEMINI_IMAGE_SIZE_CHOICES}
|
||||
if value in allowed:
|
||||
return value
|
||||
if fallback_to_auto:
|
||||
return "auto"
|
||||
raise HTTPException(400, f"unsupported Gemini image size: {raw}")
|
||||
value = gpt_aliases.get(value, value)
|
||||
allowed = {str(item["value"]) for item in IMAGE_SIZE_CHOICES}
|
||||
if value in allowed:
|
||||
return value
|
||||
@@ -4773,8 +4850,8 @@ def _normalize_image_size(raw: str | None) -> str:
|
||||
raise HTTPException(400, f"unsupported image size: {raw}")
|
||||
|
||||
|
||||
def _image_size_payload(raw: str | None) -> dict:
|
||||
size = _normalize_image_size(raw)
|
||||
def _image_size_payload(raw: str | None, model: str | None = GPT_IMAGE_MODEL, fallback_to_auto: bool = False) -> dict:
|
||||
size = _normalize_image_size(raw, model, fallback_to_auto=fallback_to_auto)
|
||||
return {} if size == "auto" else {"size": size}
|
||||
|
||||
|
||||
@@ -4808,7 +4885,7 @@ def _image_quality_payload(raw: str | None, model: str | None) -> dict:
|
||||
|
||||
|
||||
def _image_options_payload(size: str | None, quality: str | None, model: str | None) -> dict:
|
||||
return {**_image_size_payload(size), **_image_quality_payload(quality, model)}
|
||||
return {**_image_size_payload(size, model, fallback_to_auto=True), **_image_quality_payload(quality, model)}
|
||||
|
||||
|
||||
def video_duration_options() -> list[int]:
|
||||
@@ -6304,6 +6381,7 @@ def health() -> dict:
|
||||
"image_request_timeout_seconds": IMAGE_REQUEST_TIMEOUT_SECONDS,
|
||||
"image_options": image_model_options(),
|
||||
"image_size_options": image_size_options(),
|
||||
"gemini_image_size_options": gemini_image_size_options(),
|
||||
"image_quality_options": image_quality_options(),
|
||||
"ai_proxy_configured": bool(AI_HTTP_PROXY),
|
||||
"image_fallbacks": _image_fallback_models(),
|
||||
@@ -6831,7 +6909,9 @@ def generate_image(job_id: str, idx: int, req: GenerateReq) -> Job:
|
||||
if not raw_prompt:
|
||||
raise HTTPException(400, "prompt required")
|
||||
full_prompt = _ensure_english(raw_prompt)
|
||||
image_size = _normalize_image_size(req.size)
|
||||
requested_model = _normalize_image_model_preference(req.model)
|
||||
strict_size_model = IMAGE_FALLBACK_MODEL if requested_model == IMAGE_FALLBACK_MODEL else GPT_IMAGE_MODEL
|
||||
image_size = _normalize_image_size(req.size, strict_size_model)
|
||||
image_quality = _normalize_image_quality(req.quality)
|
||||
if not IMAGE_API_KEY:
|
||||
raise HTTPException(503, "IMAGE_API_KEY 或 LLM_API_KEY 未配置")
|
||||
|
||||
Reference in New Issue
Block a user