fix(media): 规范比例与 OpenAI 图像尺寸参数

统一过滤非 x:x 比例,避免默认值参与候选匹配或透传上游。

支持 xK 分辨率归一化,并按 Gemini、Volces 和 OpenAI 的参数规范生成上游请求;OpenAI 显式 WxH 始终保留用户原始尺寸。

验证:在独立临时 worktree 中执行 apps/api 全量 go test ./... -count=1 通过,真实 OpenAI 请求已到达上游但受本地无效凭据阻断。
This commit is contained in:
2026-07-27 23:50:17 +08:00
parent 039220c835
commit 95776c84f6
11 changed files with 1086 additions and 71 deletions
@@ -393,23 +393,39 @@ func numberPair(value any) ([2]float64, bool) {
}
func validAspectRatio(value string) bool {
if value == "adaptive" || value == "keep_ratio" {
return true
}
_, ok := aspectRatioNumber(value)
_, ok := normalizedAspectRatio(value)
return ok
}
func aspectRatioNumber(value string) (float64, bool) {
parts := strings.Split(value, ":")
func normalizedAspectRatio(value string) (string, bool) {
compact := strings.Map(func(r rune) rune {
switch r {
case ' ', '\t', '\r', '\n':
return -1
default:
return r
}
}, strings.TrimSpace(value))
parts := strings.Split(compact, ":")
if len(parts) != 2 {
return 0, false
return "", false
}
width := parsePositiveFloat(parts[0])
height := parsePositiveFloat(parts[1])
if width <= 0 || height <= 0 {
return "", false
}
return parts[0] + ":" + parts[1], true
}
func aspectRatioNumber(value string) (float64, bool) {
normalized, ok := normalizedAspectRatio(value)
if !ok {
return 0, false
}
parts := strings.Split(normalized, ":")
width := parsePositiveFloat(parts[0])
height := parsePositiveFloat(parts[1])
return width / height, true
}
@@ -537,11 +553,53 @@ func isEmptyParamString(value string) bool {
}
func isImageResolution(modelType string, value string) bool {
return (modelType == "image_generate" || modelType == "image_edit") && containsString([]string{"1K", "2K", "3K", "4K", "8K"}, value)
if modelType != "image_generate" && modelType != "image_edit" {
return false
}
return normalizeImageResolution(value) != ""
}
func isVideoResolution(modelType string, value string) bool {
return isVideoModelType(modelType) && containsString([]string{"480p", "720p", "1080p", "1440p", "2160p"}, value)
return isVideoModelType(modelType) && normalizeVideoResolution(value) != ""
}
func normalizeMediaResolution(modelType string, value string) string {
if modelType == "image_generate" || modelType == "image_edit" {
return normalizeImageResolution(value)
}
if isVideoModelType(modelType) {
return normalizeVideoResolution(value)
}
return ""
}
func normalizeImageResolution(value string) string {
value = strings.ToLower(strings.TrimSpace(value))
if len(value) < 2 || value[len(value)-1] != 'k' {
return ""
}
magnitude, err := strconv.Atoi(value[:len(value)-1])
if err != nil || magnitude <= 0 {
return ""
}
return strconv.Itoa(magnitude) + "K"
}
func normalizeVideoResolution(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "480p":
return "480p"
case "720p":
return "720p"
case "1080p":
return "1080p"
case "1440p":
return "1440p"
case "2160p", "4k":
return "2160p"
default:
return ""
}
}
func isVideoModelType(modelType string) bool {