You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

186 lines
4.4 KiB

  1. package helper
  2. import (
  3. "fmt"
  4. "math"
  5. "strconv"
  6. "strings"
  7. "github.com/QuantumNous/new-api/common"
  8. )
  9. func init() {
  10. registerDerivedPricingFunc("megapixels", derivedMegapixels)
  11. registerDerivedPricingFunc("aspect_ratio", derivedAspectRatio)
  12. registerDerivedPricingFunc("input_mode", derivedInputMode)
  13. registerDerivedPricingFunc("video_input", derivedVideoInput)
  14. }
  15. func derivedMegapixels(req, resp map[string]any) (any, error) {
  16. resolution, ok, _ := getNestedValue(req, "resolution")
  17. if !ok || !hasNonEmptyValue(resolution) {
  18. resolution, ok, _ = getNestedValue(req, "options.size")
  19. }
  20. if !ok || !hasNonEmptyValue(resolution) {
  21. return nil, nil
  22. }
  23. width, height, err := parseResolution(fmt.Sprintf("%v", resolution))
  24. if err != nil {
  25. return nil, err
  26. }
  27. return float64(width*height) / 1_000_000, nil
  28. }
  29. func derivedAspectRatio(req, resp map[string]any) (any, error) {
  30. resolution, ok, _ := getNestedValue(req, "resolution")
  31. if !ok || !hasNonEmptyValue(resolution) {
  32. resolution, ok, _ = getNestedValue(req, "options.size")
  33. }
  34. if !ok || !hasNonEmptyValue(resolution) {
  35. return nil, nil
  36. }
  37. width, height, err := parseResolution(fmt.Sprintf("%v", resolution))
  38. if err != nil {
  39. return nil, err
  40. }
  41. if height == 0 {
  42. return nil, fmt.Errorf("resolution height must be greater than zero")
  43. }
  44. gcd := intGCD(width, height)
  45. return fmt.Sprintf("%d:%d", width/gcd, height/gcd), nil
  46. }
  47. func derivedInputMode(req, resp map[string]any) (any, error) {
  48. if hasAnyNonEmpty(req, "video", "video_url", "reference_video", "first_frame_video") {
  49. return "v2v", nil
  50. }
  51. if hasAnyNonEmpty(req, "image", "image_url", "image[]", "images", "first_frame_image", "last_frame_image") {
  52. return "i2v", nil
  53. }
  54. return "t2v", nil
  55. }
  56. func derivedVideoInput(req, resp map[string]any) (any, error) {
  57. if hasAnyNonEmpty(req, "video", "video_url", "reference_video", "first_frame_video") {
  58. return true, nil
  59. }
  60. metadata, ok, _ := getNestedValue(req, "metadata")
  61. if !ok || !hasNonEmptyValue(metadata) {
  62. return false, nil
  63. }
  64. metadataMap, err := normalizeMetadataMap(metadata)
  65. if err != nil {
  66. return nil, err
  67. }
  68. return metadataContainsVideoInput(metadataMap), nil
  69. }
  70. func normalizeMetadataMap(raw any) (map[string]any, error) {
  71. switch value := raw.(type) {
  72. case map[string]any:
  73. return value, nil
  74. case string:
  75. if strings.TrimSpace(value) == "" {
  76. return nil, nil
  77. }
  78. metadata := map[string]any{}
  79. if err := common.Unmarshal([]byte(value), &metadata); err != nil {
  80. return nil, err
  81. }
  82. return metadata, nil
  83. default:
  84. return nil, nil
  85. }
  86. }
  87. func metadataContainsVideoInput(metadata map[string]any) bool {
  88. if metadata == nil {
  89. return false
  90. }
  91. content, ok := metadata["content"].([]any)
  92. if !ok {
  93. return false
  94. }
  95. for _, item := range content {
  96. m, ok := item.(map[string]any)
  97. if !ok {
  98. continue
  99. }
  100. if typ, _ := m["type"].(string); typ == "video_url" {
  101. return true
  102. }
  103. if value, ok := m["video_url"]; ok && hasNonEmptyValue(value) {
  104. return true
  105. }
  106. }
  107. return false
  108. }
  109. func hasNonEmptyValue(v any) bool {
  110. switch value := v.(type) {
  111. case nil:
  112. return false
  113. case string:
  114. return strings.TrimSpace(value) != ""
  115. case []any:
  116. return len(value) > 0
  117. case []string:
  118. return len(value) > 0
  119. case map[string]any:
  120. return len(value) > 0
  121. default:
  122. return true
  123. }
  124. }
  125. func hasAnyNonEmpty(req map[string]any, keys ...string) bool {
  126. for _, key := range keys {
  127. value, ok, _ := getNestedValue(req, key)
  128. if ok && hasNonEmptyValue(value) {
  129. return true
  130. }
  131. }
  132. return false
  133. }
  134. func parseResolution(raw string) (int, int, error) {
  135. normalized := strings.ToLower(strings.TrimSpace(raw))
  136. if strings.HasSuffix(normalized, "p") {
  137. height, err := strconv.Atoi(strings.TrimSuffix(normalized, "p"))
  138. if err != nil || height <= 0 {
  139. return 0, 0, fmt.Errorf("invalid resolution %q", raw)
  140. }
  141. width := int(math.Round(float64(height) * 16 / 9))
  142. return width, height, nil
  143. }
  144. parts := strings.FieldsFunc(normalized, func(r rune) bool {
  145. return r == 'x' || r == '*' || r == '×'
  146. })
  147. if len(parts) != 2 {
  148. return 0, 0, fmt.Errorf("invalid resolution %q", raw)
  149. }
  150. width, err := strconv.Atoi(strings.TrimSpace(parts[0]))
  151. if err != nil || width <= 0 {
  152. return 0, 0, fmt.Errorf("invalid resolution width %q", raw)
  153. }
  154. height, err := strconv.Atoi(strings.TrimSpace(parts[1]))
  155. if err != nil || height <= 0 {
  156. return 0, 0, fmt.Errorf("invalid resolution height %q", raw)
  157. }
  158. return width, height, nil
  159. }
  160. func intGCD(a, b int) int {
  161. for b != 0 {
  162. a, b = b, a%b
  163. }
  164. if a < 0 {
  165. return -a
  166. }
  167. if a == 0 {
  168. return 1
  169. }
  170. return a
  171. }