"""海报输出格式注册表。 逻辑尺寸用于 HTML 排版,输出尺寸用于最终 PNG,背景素材尺寸用于图片模型。 三者不得通过同一个宽高字符串互相推断。 """ from copy import deepcopy class PosterFormatError(ValueError): """海报格式不受支持。""" CUSTOM_LONG_HEIGHT_MIN = 1920 CUSTOM_LONG_HEIGHT_MAX = 30000 _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)) 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 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)