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)
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user