video_proxy.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package controller
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/url"
  8. "time"
  9. "github.com/QuantumNous/new-api/constant"
  10. "github.com/QuantumNous/new-api/logger"
  11. "github.com/QuantumNous/new-api/model"
  12. "github.com/QuantumNous/new-api/service"
  13. "github.com/gin-gonic/gin"
  14. )
  15. // videoProxyError returns a standardized OpenAI-style error response.
  16. func videoProxyError(c *gin.Context, status int, errType, message string) {
  17. c.JSON(status, gin.H{
  18. "error": gin.H{
  19. "message": message,
  20. "type": errType,
  21. },
  22. })
  23. }
  24. func VideoProxy(c *gin.Context) {
  25. taskID := c.Param("task_id")
  26. if taskID == "" {
  27. videoProxyError(c, http.StatusBadRequest, "invalid_request_error", "task_id is required")
  28. return
  29. }
  30. task, exists, err := model.GetByOnlyTaskId(taskID)
  31. if err != nil {
  32. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to query task %s: %s", taskID, err.Error()))
  33. videoProxyError(c, http.StatusInternalServerError, "server_error", "Failed to query task")
  34. return
  35. }
  36. if !exists || task == nil {
  37. videoProxyError(c, http.StatusNotFound, "invalid_request_error", "Task not found")
  38. return
  39. }
  40. if task.Status != model.TaskStatusSuccess {
  41. videoProxyError(c, http.StatusBadRequest, "invalid_request_error",
  42. fmt.Sprintf("Task is not completed yet, current status: %s", task.Status))
  43. return
  44. }
  45. channel, err := model.CacheGetChannel(task.ChannelId)
  46. if err != nil {
  47. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to get channel for task %s: %s", taskID, err.Error()))
  48. videoProxyError(c, http.StatusInternalServerError, "server_error", "Failed to retrieve channel information")
  49. return
  50. }
  51. baseURL := channel.GetBaseURL()
  52. if baseURL == "" {
  53. baseURL = "https://api.openai.com"
  54. }
  55. var videoURL string
  56. proxy := channel.GetSetting().Proxy
  57. client, err := service.GetHttpClientWithProxy(proxy)
  58. if err != nil {
  59. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to create proxy client for task %s: %s", taskID, err.Error()))
  60. videoProxyError(c, http.StatusInternalServerError, "server_error", "Failed to create proxy client")
  61. return
  62. }
  63. ctx, cancel := context.WithTimeout(c.Request.Context(), 60*time.Second)
  64. defer cancel()
  65. req, err := http.NewRequestWithContext(ctx, http.MethodGet, "", nil)
  66. if err != nil {
  67. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to create request: %s", err.Error()))
  68. videoProxyError(c, http.StatusInternalServerError, "server_error", "Failed to create proxy request")
  69. return
  70. }
  71. switch channel.Type {
  72. case constant.ChannelTypeGemini:
  73. apiKey := task.PrivateData.Key
  74. if apiKey == "" {
  75. logger.LogError(c.Request.Context(), fmt.Sprintf("Missing stored API key for Gemini task %s", taskID))
  76. videoProxyError(c, http.StatusInternalServerError, "server_error", "API key not stored for task")
  77. return
  78. }
  79. videoURL, err = getGeminiVideoURL(channel, task, apiKey)
  80. if err != nil {
  81. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to resolve Gemini video URL for task %s: %s", taskID, err.Error()))
  82. videoProxyError(c, http.StatusBadGateway, "server_error", "Failed to resolve Gemini video URL")
  83. return
  84. }
  85. req.Header.Set("x-goog-api-key", apiKey)
  86. case constant.ChannelTypeOpenAI, constant.ChannelTypeSora:
  87. videoURL = fmt.Sprintf("%s/v1/videos/%s/content", baseURL, task.GetUpstreamTaskID())
  88. req.Header.Set("Authorization", "Bearer "+channel.Key)
  89. default:
  90. // Video URL is stored in PrivateData.ResultURL (fallback to FailReason for old data)
  91. videoURL = task.GetResultURL()
  92. }
  93. req.URL, err = url.Parse(videoURL)
  94. if err != nil {
  95. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to parse URL %s: %s", videoURL, err.Error()))
  96. videoProxyError(c, http.StatusInternalServerError, "server_error", "Failed to create proxy request")
  97. return
  98. }
  99. resp, err := client.Do(req)
  100. if err != nil {
  101. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to fetch video from %s: %s", videoURL, err.Error()))
  102. videoProxyError(c, http.StatusBadGateway, "server_error", "Failed to fetch video content")
  103. return
  104. }
  105. defer resp.Body.Close()
  106. if resp.StatusCode != http.StatusOK {
  107. logger.LogError(c.Request.Context(), fmt.Sprintf("Upstream returned status %d for %s", resp.StatusCode, videoURL))
  108. videoProxyError(c, http.StatusBadGateway, "server_error",
  109. fmt.Sprintf("Upstream service returned status %d", resp.StatusCode))
  110. return
  111. }
  112. for key, values := range resp.Header {
  113. for _, value := range values {
  114. c.Writer.Header().Add(key, value)
  115. }
  116. }
  117. c.Writer.Header().Set("Cache-Control", "public, max-age=86400")
  118. c.Writer.WriteHeader(resp.StatusCode)
  119. if _, err = io.Copy(c.Writer, resp.Body); err != nil {
  120. logger.LogError(c.Request.Context(), fmt.Sprintf("Failed to stream video content: %s", err.Error()))
  121. }
  122. }