2026-07-31 21:42:32 +08:00
|
|
|
|
"""海报输出格式注册表。
|
|
|
|
|
|
|
|
|
|
|
|
逻辑尺寸用于 HTML 排版,输出尺寸用于最终 PNG,背景素材尺寸用于图片模型。
|
|
|
|
|
|
三者不得通过同一个宽高字符串互相推断。
|
|
|
|
|
|
"""
|
|
|
|
|
|
from copy import deepcopy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PosterFormatError(ValueError):
|
|
|
|
|
|
"""海报格式不受支持。"""
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 22:05:57 +08:00
|
|
|
|
CUSTOM_LONG_HEIGHT_MIN = 1920
|
|
|
|
|
|
CUSTOM_LONG_HEIGHT_MAX = 30000
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 21:42:32 +08:00
|
|
|
|
_FORMATS = (
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "single_2_3",
|
|
|
|
|
|
"label": "竖版单图 2:3",
|
|
|
|
|
|
"outputMode": "single",
|
|
|
|
|
|
"layout": {"width": 512, "height": 768},
|
|
|
|
|
|
"output": {"width": 1024, "height": 1536},
|
|
|
|
|
|
"exportSize": "1024x1536",
|
|
|
|
|
|
"backgroundAssetSize": "1024x1536",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "single_9_16",
|
|
|
|
|
|
"label": "竖屏单图 9:16",
|
|
|
|
|
|
"outputMode": "single",
|
|
|
|
|
|
"layout": {"width": 540, "height": 960},
|
|
|
|
|
|
"output": {"width": 1080, "height": 1920},
|
|
|
|
|
|
"exportSize": "1080x1920",
|
|
|
|
|
|
"backgroundAssetSize": "1024x1536",
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"id": "long_1242_auto",
|
|
|
|
|
|
"label": "完整方案长图",
|
|
|
|
|
|
"outputMode": "long",
|
|
|
|
|
|
"layout": {"width": 414, "height": None},
|
|
|
|
|
|
"output": {"width": 1242, "height": None},
|
|
|
|
|
|
"exportSize": "1242xauto",
|
|
|
|
|
|
"backgroundAssetSize": "1024x1536",
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
_FORMAT_BY_ID = {item["id"]: item for item in _FORMATS}
|
|
|
|
|
|
_DEFAULT_BY_MODE = {
|
|
|
|
|
|
"single": "single_2_3",
|
|
|
|
|
|
"long": "long_1242_auto",
|
|
|
|
|
|
}
|
|
|
|
|
|
_LEGACY_FORMATS = {
|
|
|
|
|
|
("single", "1024x1792"): "single_2_3",
|
|
|
|
|
|
("single", "1024x1536"): "single_2_3",
|
|
|
|
|
|
("single", "1080x1920"): "single_9_16",
|
|
|
|
|
|
("long", "1080x2160"): "long_1242_auto",
|
|
|
|
|
|
("long", "1080x3240"): "long_1242_auto",
|
|
|
|
|
|
("long", "1080x4320"): "long_1242_auto",
|
|
|
|
|
|
("long", "1242xauto"): "long_1242_auto",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def list_poster_formats() -> list[dict]:
|
|
|
|
|
|
"""返回可供前端选择的安全格式副本。"""
|
|
|
|
|
|
return deepcopy(list(_FORMATS))
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 22:05:57 +08:00
|
|
|
|
def resolve_custom_long_height(value, output_mode: str) -> int | None:
|
|
|
|
|
|
"""解析长图自定义输出高度;None 表示内容驱动的自动高度。"""
|
|
|
|
|
|
if value is None or value == "" or value == "auto":
|
|
|
|
|
|
return None
|
|
|
|
|
|
if output_mode != "long":
|
|
|
|
|
|
raise PosterFormatError("自定义高度仅长图模式可用")
|
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
|
raise PosterFormatError("长图高度必须是整数")
|
|
|
|
|
|
try:
|
|
|
|
|
|
height = int(value)
|
|
|
|
|
|
except (TypeError, ValueError) as exc:
|
|
|
|
|
|
raise PosterFormatError("长图高度必须是整数") from exc
|
|
|
|
|
|
if isinstance(value, float) and not value.is_integer():
|
|
|
|
|
|
raise PosterFormatError("长图高度必须是整数")
|
|
|
|
|
|
if not CUSTOM_LONG_HEIGHT_MIN <= height <= CUSTOM_LONG_HEIGHT_MAX:
|
|
|
|
|
|
raise PosterFormatError(
|
|
|
|
|
|
f"长图高度必须在 {CUSTOM_LONG_HEIGHT_MIN} 到 {CUSTOM_LONG_HEIGHT_MAX}px 之间"
|
|
|
|
|
|
)
|
|
|
|
|
|
return height
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-31 21:42:32 +08:00
|
|
|
|
def resolve_poster_format(
|
|
|
|
|
|
format_id: str | None = None,
|
|
|
|
|
|
legacy_size: str | None = None,
|
|
|
|
|
|
output_mode: str = "single",
|
|
|
|
|
|
) -> dict:
|
|
|
|
|
|
"""解析并校验海报格式,未知值必须明确失败。"""
|
|
|
|
|
|
mode = str(output_mode or "single")
|
|
|
|
|
|
if mode not in _DEFAULT_BY_MODE:
|
|
|
|
|
|
raise PosterFormatError(f"不支持的输出模式: {output_mode}")
|
|
|
|
|
|
|
|
|
|
|
|
if format_id:
|
|
|
|
|
|
spec = _FORMAT_BY_ID.get(str(format_id))
|
|
|
|
|
|
if not spec:
|
|
|
|
|
|
raise PosterFormatError(f"不支持的海报格式: {format_id}")
|
|
|
|
|
|
elif legacy_size:
|
|
|
|
|
|
resolved_id = _LEGACY_FORMATS.get((mode, str(legacy_size)))
|
|
|
|
|
|
if not resolved_id:
|
|
|
|
|
|
raise PosterFormatError(f"不支持的海报尺寸: {legacy_size}")
|
|
|
|
|
|
spec = _FORMAT_BY_ID[resolved_id]
|
|
|
|
|
|
else:
|
|
|
|
|
|
spec = _FORMAT_BY_ID[_DEFAULT_BY_MODE[mode]]
|
|
|
|
|
|
|
|
|
|
|
|
if spec["outputMode"] != mode:
|
|
|
|
|
|
mode_label = "长图" if mode == "long" else "单图"
|
|
|
|
|
|
raise PosterFormatError(f"格式 {spec['id']} 不支持{mode_label}模式")
|
|
|
|
|
|
return deepcopy(spec)
|