package app import ( "context" "strconv" "strings" "time" ) func isPublicMediaURL(value string) bool { lower := strings.ToLower(value) return strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") } func isSeedanceVideoConfig(config providerConfig) bool { model := strings.ToLower(config.Model) return strings.Contains(model, "seedance") || strings.Contains(model, "doubao-seedance") || isArkPlanVideoConfig(config) } func isGrokVideoConfig(config providerConfig) bool { return strings.Contains(strings.ToLower(strings.TrimSpace(config.Model)), "grok") } func isArkPlanVideoConfig(config providerConfig) bool { return strings.Contains(strings.ToLower(config.BaseURL), "/api/plan/v3") } func normalizeImageQuality(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case "1k": return "low" case "2k": return "medium" case "4k": return "high" default: return value } } func imageParameterSupported(profile *ImageCapabilityConfig, parameter string) bool { if profile == nil { return true } if parameter == "response_format" { return profile.ResponseFormat.Supported } return profile.OutputFormat.Supported } func imageQualitySupported(profile *ImageCapabilityConfig) bool { return profile == nil || profile.Quality.Supported } func imageTransparentBackgroundSupported(profile *ImageCapabilityConfig) bool { return profile == nil || profile.TransparentBackground.Supported } func imageSizeParameter(profile *ImageCapabilityConfig, value string) (string, string) { if profile == nil { return "size", normalizePixelSize(value) } value = strings.TrimSpace(value) if strings.EqualFold(value, "auto") { return "", "" } if value == "" { value = strings.TrimSpace(profile.Size.Default) } switch profile.Size.Parameter { case "size": return "size", normalizePixelSize(value) case "aspect_ratio": return "aspect_ratio", normalizeImageAspectRatio(value) default: return "", "" } } func normalizeImageAspectRatio(value string) string { value = strings.TrimSpace(strings.ToLower(strings.ReplaceAll(value, "×", "x"))) if strings.Contains(value, ":") { return value } parts := strings.Split(value, "x") if len(parts) != 2 { return "" } width, widthErr := strconv.Atoi(parts[0]) height, heightErr := strconv.Atoi(parts[1]) if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { return "" } divisor := imageDimensionGCD(width, height) return strconv.Itoa(width/divisor) + ":" + strconv.Itoa(height/divisor) } func imageDimensionGCD(left int, right int) int { for right != 0 { left, right = right, left%right } if left < 1 { return 1 } return left } func normalizePixelSize(value string) string { value = strings.TrimSpace(value) if value == "" || value == "auto" { return "" } // 画布按比例保存常用预设;图片接口只接受像素尺寸,必须在请求边界完成转换。 switch value { case "1:1": return "1024x1024" case "3:2": return "1536x1024" case "2:3": return "1024x1536" case "4:3": return "1360x1024" case "3:4": return "1024x1360" case "16:9": return "1824x1024" case "9:16": return "1024x1824" case "21:9": return "2352x1008" } if strings.Contains(value, "x") { return value } return "" } func normalizeVideoSize(value string) string { value = strings.TrimSpace(value) if value == "" || value == "auto" { return "" } if strings.Contains(value, "x") { return value } if value == "9:16" || value == "2:3" || value == "3:4" { return "720x1280" } return "1280x720" } func normalizeVideoResolution(value string) string { value = strings.TrimSpace(value) if value == "" || value == "auto" || value == "medium" || value == "high" { return "720p" } if value == "low" { return "480p" } if strings.EqualFold(value, "4k") { return "2160p" } if strings.EqualFold(value, "2k") { return "1440p" } if strings.HasSuffix(value, "p") { return value } return value + "p" } func videoResolutionNameRequest(profile *VideoCapabilityConfig, value string) string { requested := strings.ToLower(strings.TrimSpace(value)) if requested == "" || requested == "auto" || requested == "default" || requested == "medium" || requested == "high" { return "" } if profile == nil { return "" } if len(profile.Resolutions) == 0 { return "" } candidates := []string{requested, strings.ToLower(normalizeVideoResolution(requested))} if requested == "4k" { candidates = append(candidates, "2160p") } if requested == "2160" || requested == "2160p" { candidates = append(candidates, "4k") } for _, supported := range profile.Resolutions { for _, candidate := range candidates { if strings.EqualFold(strings.TrimSpace(supported), candidate) { return strings.TrimSpace(supported) } } } return "" } func isAutomaticVideoResolution(value string) bool { switch strings.ToLower(strings.TrimSpace(value)) { case "", "auto", "default", "medium", "high": return true default: return false } } func normalizeSeedanceDuration(value string) int { if strings.TrimSpace(value) == "-1" { return -1 } seconds, err := strconv.Atoi(strings.TrimSpace(value)) if err != nil || seconds <= 0 { return 5 } return seconds } func normalizeSeedanceVideosDuration(value string) int { return normalizeSeedanceDuration(value) } func normalizeSeedanceRatio(value string) string { value = strings.TrimSpace(value) if value == "" || value == "auto" || value == "adaptive" { return "adaptive" } switch value { case "16:9", "9:16", "1:1", "4:3", "3:4", "21:9": return value default: return "adaptive" } } func normalizeSeedanceVideosRatio(value string) string { ratio := normalizeSeedanceRatio(value) if ratio == "adaptive" { return "16:9" } return ratio } func normalizeSeedanceResolution(value string, model string) string { resolution := strings.TrimSuffix(strings.TrimSpace(value), "p") if strings.EqualFold(resolution, "4k") { resolution = "2160" } switch resolution { case "480", "720", "1080", "2160": default: if value == "low" { resolution = "480" } else { resolution = "720" } } if strings.Contains(strings.ToLower(model), "fast") && (resolution == "1080" || resolution == "2160") { resolution = "720" } return resolution + "p" } func parseBool(value string, fallback bool) bool { switch strings.ToLower(strings.TrimSpace(value)) { case "true": return true case "false": return false default: return fallback } } func parseFloat(value string, fallback float64) float64 { number, err := strconv.ParseFloat(strings.TrimSpace(value), 64) if err != nil || number == 0 { return fallback } return number } func sleepContext(ctx context.Context, duration time.Duration) error { timer := time.NewTimer(duration) defer timer.Stop() select { case <-ctx.Done(): return ctx.Err() case <-timer.C: return nil } }