fix: prefer generated video metadata for billing

This commit is contained in:
2026-05-20 00:11:28 +08:00
parent 69d23efb57
commit 11a2c13e4a
4 changed files with 318 additions and 14 deletions
+79 -14
View File
@@ -82,19 +82,23 @@ func (s *Service) billings(ctx context.Context, user *auth.User, kind string, bo
resource = "video"
unit = "5s_video"
baseKey = "videoBase"
duration := requestDurationSeconds(body)
duration, durationSource := billingDurationSeconds(body, response)
audioEnabled, audioSource := billingAudioEnabled(body, response)
durationUnits := math.Max(1, math.Ceil(duration/5))
amount := float64(count) *
durationUnits *
resourcePrice(config, resource, baseKey, "basePrice") *
resourceWeight(config, resource, "resolutionWeights", firstNonEmptyString(stringFromMap(body, "resolution"), stringFromMap(body, "size"))) *
resourceWeight(config, resource, "audioWeights", boolWeightKey(boolishValue(body["audio"]))) *
resourceWeight(config, resource, "audioWeights", boolWeightKey(audioEnabled)) *
resourceWeight(config, resource, "referenceVideoWeights", boolWeightKey(requestHasReferenceVideo(body))) *
resourceWeight(config, resource, "voiceSpecifiedWeights", boolWeightKey(requestHasVoiceID(body))) *
resourceWeight(config, resource, "voiceSpecifiedWeights", boolWeightKey(requestHasVoiceID(body, audioEnabled))) *
discount
return []any{billingLineWithDetails(candidate, resource, unit, count*int(durationUnits), roundPrice(amount), discount, simulated, map[string]any{
"count": count,
"audio": audioEnabled,
"audioSource": audioSource,
"durationSeconds": duration,
"durationSource": durationSource,
"durationUnit": "5s",
"durationUnitCount": durationUnits,
})}
@@ -345,6 +349,54 @@ func requestDurationSeconds(body map[string]any) float64 {
return 5
}
func billingDurationSeconds(body map[string]any, response clients.Response) (float64, string) {
if duration, ok := generatedVideoDurationSeconds(response.Result); ok {
return duration, "generated_video"
}
return requestDurationSeconds(body), "preprocessed_request"
}
func generatedVideoDurationSeconds(result map[string]any) (float64, bool) {
data, _ := result["data"].([]any)
for _, raw := range data {
item, _ := raw.(map[string]any)
if len(item) == 0 {
continue
}
duration := floatFromAny(item["duration"])
if duration <= 0 {
continue
}
rounded := math.Round(duration)
if rounded <= 0 {
rounded = 1
}
return rounded, true
}
return 0, false
}
func billingAudioEnabled(body map[string]any, response clients.Response) (bool, string) {
if value, ok := generatedVideoHasAudio(response.Result); ok {
return value, "generated_video"
}
return boolishValue(body["audio"]), "preprocessed_request"
}
func generatedVideoHasAudio(result map[string]any) (bool, bool) {
data, _ := result["data"].([]any)
for _, raw := range data {
item, _ := raw.(map[string]any)
if len(item) == 0 {
continue
}
if value, ok := boolishOptional(firstPresentValue(item, "has_audio", "hasAudio")); ok {
return value, true
}
}
return false, false
}
func requestHasReferenceVideo(body map[string]any) bool {
if hasNonEmptyArray(body["video_list"]) || hasNonEmptyArray(body["videoList"]) {
return true
@@ -367,8 +419,8 @@ func requestHasReferenceVideo(body map[string]any) bool {
return false
}
func requestHasVoiceID(body map[string]any) bool {
return boolishValue(body["audio"]) && firstNonEmptyStringValue(body, "voice_id", "voiceId") != ""
func requestHasVoiceID(body map[string]any, audioEnabled bool) bool {
return audioEnabled && firstNonEmptyStringValue(body, "voice_id", "voiceId") != ""
}
func boolWeightKey(value bool) string {
@@ -379,25 +431,38 @@ func boolWeightKey(value bool) string {
}
func boolishValue(value any) bool {
result, _ := boolishOptional(value)
return result
}
func boolishOptional(value any) (bool, bool) {
switch typed := value.(type) {
case bool:
return typed
return typed, true
case string:
switch strings.ToLower(strings.TrimSpace(typed)) {
case "true", "1", "yes", "on":
return true
default:
return false
return true, true
case "false", "0", "no", "off":
return false, true
}
case int:
return typed != 0
return typed != 0, true
case int64:
return typed != 0
return typed != 0, true
case float64:
return typed != 0
default:
return false
return typed != 0, true
}
return false, false
}
func firstPresentValue(record map[string]any, keys ...string) any {
for _, key := range keys {
if value, ok := record[key]; ok {
return value
}
}
return nil
}
func hasNonEmptyArray(value any) bool {