Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

263 linhas
8.6 KiB

  1. package maas_seedance_sdk
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "net/http"
  7. "os"
  8. "strings"
  9. "maas_seedance_sdk_1.0.0_go/volcengine_aicc_sdk"
  10. "maas_seedance_sdk_1.0.0_go/volcengine_aicc_sdk/common/log"
  11. )
  12. const (
  13. mappingQueryPath = "/mapping/query"
  14. )
  15. // MaasSeedanceClient is the top-level client for MaaS Seedance video generation.
  16. type MaasSeedanceClient struct {
  17. volcClient *volcengine_aicc_sdk.SeedanceClient
  18. maasModel string // the resolved endpoint returned by /mapping/query, used as model in request body
  19. serviceVersion string // service version, passed as service-version header
  20. enableVideoEncrypt bool
  21. logger *log.Logger
  22. }
  23. // NewMaasSeedanceClient creates a new MaasSeedanceClient.
  24. //
  25. // maasBaseURL: the MaaS gateway base URL
  26. // maasAPIKey: the API key for authentication
  27. // maasModel: the virtual model name (resolved via /mapping/query)
  28. // enableVideoEncrypt: whether to enable video file encryption
  29. func NewMaasSeedanceClient(maasBaseURL, maasAPIKey, maasModel string, enableVideoEncrypt bool, opts ...MaasSeedanceClientOption) (*MaasSeedanceClient, error) {
  30. client := &MaasSeedanceClient{
  31. enableVideoEncrypt: enableVideoEncrypt,
  32. logger: log.NewLogger(),
  33. }
  34. for _, opt := range opts {
  35. opt(client)
  36. }
  37. // Resolve actual endpoint: the endpoint from /mapping/query is used as the model field in the request body
  38. resolvedModel := client.resolveActualModel(maasBaseURL, maasAPIKey, maasModel)
  39. client.maasModel = resolvedModel
  40. seedanceOpts := []volcengine_aicc_sdk.SeedanceClientOption{
  41. volcengine_aicc_sdk.WithSeedanceBaseURL(maasBaseURL),
  42. volcengine_aicc_sdk.WithSeedanceAPIKey(maasAPIKey),
  43. volcengine_aicc_sdk.WithSeedanceEndpoint(resolvedModel),
  44. volcengine_aicc_sdk.WithSeedanceIsSecure(true),
  45. volcengine_aicc_sdk.WithSeedanceIsEnableVideoEncrypt(enableVideoEncrypt),
  46. volcengine_aicc_sdk.WithSeedanceTimeout(120.0),
  47. volcengine_aicc_sdk.WithSeedanceVideoFileStorageLocation("TOS"),
  48. }
  49. client.volcClient = volcengine_aicc_sdk.NewSeedanceClient(seedanceOpts...)
  50. client.logger.Info("[MaasSeedanceClient] initialized, baseUrl: %s, originalModel: %s, actualModel: %s, enableVideoEncrypt: %v, serviceVersion: %s",
  51. maasBaseURL, maasModel, resolvedModel, enableVideoEncrypt, client.serviceVersion)
  52. return client, nil
  53. }
  54. func (c *MaasSeedanceClient) resolveActualModel(baseURL, apiKey, model string) string {
  55. cleanBaseURL := strings.TrimRight(baseURL, "/")
  56. mappingURL := cleanBaseURL + mappingQueryPath
  57. c.logger.Info("[MaasSeedanceClient] Querying model mapping: url=%s, model=%s", mappingURL, model)
  58. requestBody := map[string]interface{}{
  59. "model": model,
  60. }
  61. jsonBody, err := json.Marshal(requestBody)
  62. if err != nil {
  63. c.logger.Warning("[MaasSeedanceClient] Failed to marshal mapping query request: %v", err)
  64. return model
  65. }
  66. httpClient := &http.Client{}
  67. req, err := http.NewRequest("POST", mappingURL, bytes.NewBuffer(jsonBody))
  68. if err != nil {
  69. c.logger.Warning("[MaasSeedanceClient] Failed to create mapping query request: %v", err)
  70. return model
  71. }
  72. req.Header.Set("Content-Type", "application/json")
  73. req.Header.Set("Accept", "application/json")
  74. req.Header.Set("Authorization", "Bearer "+apiKey)
  75. if c.serviceVersion != "" {
  76. req.Header.Set("service-version", c.serviceVersion)
  77. }
  78. resp, err := httpClient.Do(req)
  79. if err != nil {
  80. c.logger.Warning("[MaasSeedanceClient] Failed to query model mapping, using original model: %s. Error: %v", model, err)
  81. return model
  82. }
  83. defer resp.Body.Close()
  84. var result map[string]interface{}
  85. if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
  86. c.logger.Warning("[MaasSeedanceClient] Failed to decode mapping response, using original model: %s", model)
  87. return model
  88. }
  89. c.logger.Info("[MaasSeedanceClient] Mapping query response: status=%d, body=%v", resp.StatusCode, result)
  90. if resp.StatusCode == 200 {
  91. if endpoint, ok := result["endpoint"].(string); ok && endpoint != "" {
  92. c.logger.Info("[MaasSeedanceClient] Resolved model mapping: %s -> %s", model, endpoint)
  93. return endpoint
  94. }
  95. c.logger.Warning("[MaasSeedanceClient] No endpoint found in mapping response for model: %s, using original model", model)
  96. } else {
  97. c.logger.Warning("[MaasSeedanceClient] Mapping query failed with status: %d, using original model: %s", resp.StatusCode, model)
  98. }
  99. return model
  100. }
  101. // SetVideoFileEncryptKey sets or generates RSA key pair for video file encryption.
  102. func (c *MaasSeedanceClient) SetVideoFileEncryptKey(publicKeyPath, privateKeyPath string) error {
  103. if !c.enableVideoEncrypt {
  104. c.logger.Warning("[MaasSeedanceClient] Current mode is plaintext, no need to set video encrypt key")
  105. return nil
  106. }
  107. if _, err := os.Stat(publicKeyPath); os.IsNotExist(err) {
  108. if _, err := os.Stat(privateKeyPath); os.IsNotExist(err) {
  109. c.logger.Info("[MaasSeedanceClient] Generating RSA key pair...")
  110. if err := c.volcClient.GenerateVideoFileEncryptKey(publicKeyPath, privateKeyPath); err != nil {
  111. return fmt.Errorf("failed to generate RSA key pair: %w", err)
  112. }
  113. c.logger.Info("[MaasSeedanceClient] RSA key pair generated successfully")
  114. }
  115. } else {
  116. c.logger.Info("[MaasSeedanceClient] Loading existing RSA key pair from: %s", publicKeyPath)
  117. }
  118. if err := c.volcClient.SetVideoFileEncryptKey(publicKeyPath, privateKeyPath); err != nil {
  119. return fmt.Errorf("failed to set video file encrypt key: %w", err)
  120. }
  121. return nil
  122. }
  123. // CreateVideoGenerationTask creates a video generation task.
  124. func (c *MaasSeedanceClient) CreateVideoGenerationTask(data map[string]interface{}) (string, error) {
  125. data["model"] = c.maasModel
  126. c.logger.Info("[MaasSeedanceClient] Request body after adding model: %v", data)
  127. headers := make(map[string]string)
  128. if c.hasVideoInput(data) {
  129. headers["Input-Has-Video"] = "true"
  130. }
  131. if c.serviceVersion != "" {
  132. headers["service-version"] = c.serviceVersion
  133. }
  134. c.logger.Info("[MaasSeedanceClient] Creating video generation task, Input-Has-Video: %v, service-version: %s",
  135. headers["Input-Has-Video"] != "", c.serviceVersion)
  136. taskID, err := c.volcClient.CreateVideoGenerationTask(data, headers)
  137. if err != nil {
  138. return "", err
  139. }
  140. c.logger.Info("[MaasSeedanceClient] Task created successfully, taskId: %s", taskID)
  141. return taskID, nil
  142. }
  143. // QueryVideoGenerationTask queries the status of a video generation task.
  144. func (c *MaasSeedanceClient) QueryVideoGenerationTask(taskID string) (map[string]interface{}, error) {
  145. c.logger.Info("[MaasSeedanceClient] Querying video generation task: %s", taskID)
  146. result, err := c.volcClient.QueryVideoGenerationTask(taskID)
  147. if err != nil {
  148. return nil, err
  149. }
  150. if result != nil {
  151. if status, ok := result["status"].(string); ok {
  152. c.logger.Info("[MaasSeedanceClient] Task status: %s", status)
  153. }
  154. }
  155. return result, nil
  156. }
  157. // QueryVideoGenerationTaskList queries the list of video generation tasks.
  158. func (c *MaasSeedanceClient) QueryVideoGenerationTaskList(pageNum, pageSize int, status string, taskIDs []string, startTime, endTime string) (map[string]interface{}, error) {
  159. c.logger.Info("[MaasSeedanceClient] Querying video generation task list - pageNum: %d, pageSize: %d, status: %s",
  160. pageNum, pageSize, status)
  161. opts := volcengine_aicc_sdk.QueryVideoGenerationTaskListOptions{
  162. PageNum: pageNum,
  163. PageSize: pageSize,
  164. Status: status,
  165. TaskIDs: taskIDs,
  166. }
  167. return c.volcClient.QueryVideoGenerationTaskList(opts)
  168. }
  169. // DownloadVideo downloads the video file for a completed task.
  170. func (c *MaasSeedanceClient) DownloadVideo(taskID, localFilePath string) (bool, error) {
  171. c.logger.Info("[MaasSeedanceClient] Downloading video: taskId=%s, localPath=%s", taskID, localFilePath)
  172. success, err := c.volcClient.VideoFileDownload(taskID, localFilePath)
  173. if err != nil {
  174. return false, err
  175. }
  176. if success {
  177. c.logger.Info("[MaasSeedanceClient] Video downloaded successfully to: %s", localFilePath)
  178. } else {
  179. c.logger.Warning("[MaasSeedanceClient] Video download failed for taskId: %s", taskID)
  180. }
  181. return success, nil
  182. }
  183. // DeleteVideoGenerationTask deletes a video generation task.
  184. func (c *MaasSeedanceClient) DeleteVideoGenerationTask(taskID string) (bool, error) {
  185. c.logger.Info("[MaasSeedanceClient] Deleting video generation task: %s", taskID)
  186. result, err := c.volcClient.DeleteVideoGenerationTask(taskID)
  187. if err != nil {
  188. return false, err
  189. }
  190. return result != nil, nil
  191. }
  192. func (c *MaasSeedanceClient) hasVideoInput(data map[string]interface{}) bool {
  193. content, ok := data["content"]
  194. if !ok {
  195. return false
  196. }
  197. contentList, ok := content.([]interface{})
  198. if !ok {
  199. return false
  200. }
  201. for _, item := range contentList {
  202. part, ok := item.(map[string]interface{})
  203. if !ok {
  204. continue
  205. }
  206. if partType, ok := part["type"].(string); ok && partType == "video_url" {
  207. return true
  208. }
  209. }
  210. return false
  211. }