Files
popiart-server/internal/server/image_options.go
T
wtgoku 6ff9ad6997 Restore MiniMax media routing parity across image and video flows
The server now keeps portable image ratio normalization, enforces documented Seedream size presets, routes MiniMax img2img through generations-style payloads, and submits Hailuo video variants through the video generations API with proper reference-image handling.

Constraint: The deployed test chain relies on popiartServer as the lowest-risk place to adapt provider-specific routing without broad newapi changes
Rejected: Expand newapi relay modes for every MiniMax image/video branch | larger blast radius than needed for the verified server-managed flows
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep MiniMax-specific media routing in popiartServer unless upstream newapi adapters are upgraded to the same contract
Tested: go test ./internal/server -run TestInferRouteKeyForModelRecognizesViduAsVideo|TestGenerate|TestResolve|TestSubmitMiniMaxVideoTask|TestExecuteImageToImageJobUsesGenerationsPathForMiniMax|TestExecuteImageToVideoJob|TestResolveVideoReferencesSupportsImagesArray
Not-tested: Full PopiNewAPI unit suite (blocked earlier by external module checksum drift in local checkout)
2026-04-15 11:57:05 +08:00

198 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"fmt"
"strconv"
"strings"
)
var imageAspectRatioSeparators = strings.NewReplacer(
"", ":",
"", ":",
"/", ":",
"×", ":",
"*", ":",
"x", ":",
"X", ":",
)
var canonicalImageAspectRatioLabels = map[string]string{
"7:3": "21:9",
"16:9": "16:9",
"4:3": "4:3",
"3:2": "3:2",
"1:1": "1:1",
"9:16": "9:16",
"3:4": "3:4",
"2:3": "2:3",
"5:4": "5:4",
"4:5": "4:5",
}
var seedreamAspectRatioSizes = map[string]string{
"21:9": "2520x1080",
"16:9": "2048x1152",
"4:3": "2048x1536",
"3:2": "2304x1536",
"1:1": "2048x2048",
"9:16": "1152x2048",
"3:4": "1536x2048",
"2:3": "1536x2304",
"5:4": "1920x1536",
"4:5": "1536x1920",
}
var exactImageSizeAspectRatios = map[string]string{
"1024x1024": "1:1",
"1920x1920": "1:1",
"1536x1024": "3:2",
"1024x1536": "2:3",
"1792x1024": "16:9",
"1024x1792": "9:16",
}
func normalizeImageAspectRatio(value string) string {
width, height, ok := parseAspectRatioDimensions(value)
if !ok {
return strings.TrimSpace(value)
}
return canonicalAspectRatioLabel(width, height)
}
func parseAspectRatioDimensions(value string) (int, int, bool) {
value = strings.TrimSpace(value)
if value == "" {
return 0, 0, false
}
normalized := imageAspectRatioSeparators.Replace(value)
parts := strings.Split(normalized, ":")
if len(parts) != 2 {
return 0, 0, false
}
width, errWidth := strconv.Atoi(strings.TrimSpace(parts[0]))
height, errHeight := strconv.Atoi(strings.TrimSpace(parts[1]))
if errWidth != nil || errHeight != nil || width <= 0 || height <= 0 {
return 0, 0, false
}
return width, height, true
}
func canonicalAspectRatioLabel(width, height int) string {
rw, rh := reduceImageRatio(width, height)
key := fmt.Sprintf("%d:%d", rw, rh)
if label, ok := canonicalImageAspectRatioLabels[key]; ok {
return label
}
return key
}
func reduceImageRatio(width, height int) (int, int) {
g := imageGCD(width, height)
if g == 0 {
return width, height
}
return width / g, height / g
}
func imageGCD(a, b int) int {
for b != 0 {
a, b = b, a%b
}
if a < 0 {
return -a
}
return a
}
func parseExactImageSize(value string) (int, int, bool) {
value = strings.TrimSpace(strings.ToLower(value))
if value == "" {
return 0, 0, false
}
parts := strings.Split(value, "x")
if len(parts) != 2 {
return 0, 0, false
}
width, errWidth := strconv.Atoi(strings.TrimSpace(parts[0]))
height, errHeight := strconv.Atoi(strings.TrimSpace(parts[1]))
if errWidth != nil || errHeight != nil || width <= 0 || height <= 0 {
return 0, 0, false
}
return width, height, true
}
func normalizeImageSizeToken(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return ""
}
switch strings.ToUpper(value) {
case "1K", "2K", "4K":
return strings.ToUpper(value)
}
width, height, ok := parseExactImageSize(value)
if !ok {
return value
}
return fmt.Sprintf("%dx%d", width, height)
}
func deriveAspectRatioFromImageSize(value string) string {
value = normalizeImageSizeToken(value)
if ratio, ok := exactImageSizeAspectRatios[value]; ok {
return ratio
}
width, height, ok := parseExactImageSize(value)
if !ok {
if ratio := normalizeImageAspectRatio(value); ratio != "" {
return ratio
}
return ""
}
return canonicalAspectRatioLabel(width, height)
}
func geminiImageSizeFromValue(value string) string {
value = normalizeImageSizeToken(value)
switch value {
case "1K", "2K", "4K":
return value
}
width, height, ok := parseExactImageSize(value)
if !ok {
return ""
}
longEdge := width
if height > longEdge {
longEdge = height
}
switch {
case longEdge <= 1024:
return "1K"
case longEdge <= 2560:
return "2K"
default:
return "4K"
}
}
func seedreamImageSizeFromAspectRatio(value string) string {
if ratio := normalizeImageAspectRatio(value); ratio != "" {
if size, ok := seedreamAspectRatioSizes[ratio]; ok {
return size
}
}
return ""
}
func supportedSeedreamSize(modelID, value string) bool {
value = strings.ToUpper(strings.TrimSpace(value))
if value == "" {
return false
}
_, ok := supportedSeedreamSizes(modelID)[value]
return ok
}