[C#] Agnes AI 影片生成 API 教學:自動產生影片並下載 MP4

  • 43
  • 0

最近一直在測試各種可以產生圖片與影片的 AI API

前一篇測試了 Agnes AI 的圖片生成, 既然圖片可以直接用 API 產生,當然就會想看看影片是不是也可以

結果 Agnes AI 也有提供 agnes-video-v2.0 影片生成模型,而且可以直接透過 API 呼叫

Agnes AI 官方目前也將圖片與影片模型列為可透過 API 使用的多模態功能

1. 就按照慣例,提醒一下你必須要先去拿 key  https://platform.agnes-ai.com/settings/apiKeys


 

 

2.  影片生成跟圖片生成有一點不同,圖片通常呼叫 API 後,就可以直接取得圖片網址
影片需要比較久,所以會先建立一個任務,取得任務 ID,再持續查詢影片的生成進度,所以必須要一直查詢

大概流程就是:

送出影片生成要求

取得任務 ID

每隔幾秒查詢進度

影片生成完成

取得影片網址

下載 MP4 到本機

整個流程雖然比圖片生成多了一些步驟,但實際串接並不困難
 

3. 接下來就是程式碼的部分,需要注意的就是寫在程式裡面, 基本上你就丟給 AI ,補上你的 Key 
應該就是可以輕鬆寫出你要的效果..

internal class Program
   {
       static void Main(string[] args)
       {
           try
           {
               /*
                * Agnes AI 影片生成是非同步流程。
                *
                * 由同步 Main 進入點等待 MainAsync 執行完成。
                */
               MainAsync().GetAwaiter().GetResult();
           }
           catch (Exception ex)
           {
               Console.WriteLine("程式執行失敗:");
               Console.WriteLine(ex.Message);
           }

           Console.WriteLine();
           Console.WriteLine("按任意鍵結束...");
           Console.ReadKey();
       }

       private static async Task MainAsync()
       {
           /*
            * 直接帶入 Agnes AI API Key。
            *
            * 測試時可以直接寫在這裡,
            * 但正式專案不要將 API Key 提交到 GitHub。
            */
           const string apiKey = "YOUR_API_KEY";

           /*
            * Agnes AI 建立影片任務 API。
            */
           const string createEndpoint =
               "https://apihub.agnes-ai.com/v1/videos";

           if (string.IsNullOrWhiteSpace(apiKey) ||
               apiKey.Contains("帶入你的"))
           {
               Console.WriteLine("請先設定 Agnes AI API Key");
               return;
           }

           /*
            * 這個 HttpClient 只負責呼叫 Agnes AI API。
            *
            * 因為會加入 Authorization Header,
            * 不要使用它下載最後產生的影片。
            */
           using var apiHttpClient = new HttpClient
           {
               Timeout = TimeSpan.FromMinutes(5)
           };

           /*
            * Agnes AI 使用 Bearer Token 驗證。
            */
           apiHttpClient.DefaultRequestHeaders.Authorization =
               new AuthenticationHeaderValue("Bearer", apiKey);

           /*
            * 影片生成參數。
            *
            * model:
            * 使用 Agnes Video V2.0。
            *
            * prompt:
            * 建議描述主體、動作、鏡頭、場景與光線。
            *
            * width、height:
            * Agnes AI 可能會自動映射成最接近的預設尺寸。
            *
            * num_frames:
            * 必須符合 8n + 1。
            *
            * 例如:
            * 121、241、441。
            *
            * 121 個影格搭配 24 FPS,
            * 大約會產生 5 秒影片。
            */
           var request = new
           {
               model = "agnes-video-v2.0",

               prompt = """
                        A cinematic shot of a software engineer working
                        in a dark technology studio, typing C# code,
                        computer screens glowing softly,
                        slow camera movement,
                        realistic motion,
                        cinematic lighting
                        """,

               width = 1152,

               height = 768,

               num_frames = 121,

               frame_rate = 24
           };

           Console.WriteLine("開始建立影片生成任務...");

           /*
            * 建立影片生成任務。
            */
           using var createResponse =
               await apiHttpClient.PostAsJsonAsync(
                   createEndpoint,
                   request);

           var createResponseText =
               await createResponse.Content.ReadAsStringAsync();

           if (!createResponse.IsSuccessStatusCode)
           {
               Console.WriteLine(
                   $"建立任務失敗:{(int)createResponse.StatusCode} " +
                   $"{createResponse.StatusCode}");

               Console.WriteLine(createResponseText);
               return;
           }

           /*
            * 建立成功後,API 回傳的任務編號可能位於:
            *
            * id
            * task_id
            * video_id
            *
            * 目前實際回傳格式主要使用 id。
            */
           var createResult =
               JsonSerializer.Deserialize(
                   createResponseText,
                   new JsonSerializerOptions
                   {
                       PropertyNameCaseInsensitive = true
                   });

           var videoId =
               createResult?.Id ??
               createResult?.TaskId ??
               createResult?.VideoId;

           if (string.IsNullOrWhiteSpace(videoId))
           {
               Console.WriteLine("API 回應中找不到影片任務 ID");
               Console.WriteLine(createResponseText);
               return;
           }

           Console.WriteLine($"任務建立成功:{videoId}");

           /*
            * 影片生成需要一段時間。
            *
            * 每隔 10 秒查詢一次任務狀態,
            * 最多等待 30 分鐘。
            */
           var startedAt = DateTime.UtcNow;
           var maximumWaitTime = TimeSpan.FromMinutes(30);

           while (true)
           {
               if (DateTime.UtcNow - startedAt > maximumWaitTime)
               {
                   Console.WriteLine("等待影片生成逾時");
                   return;
               }

               await Task.Delay(TimeSpan.FromSeconds(10));

               /*
                * 使用影片任務 ID 查詢目前狀態。
                */
               var statusEndpoint =
                   "https://apihub.agnes-ai.com/agnesapi" +
                   $"?video_id={Uri.EscapeDataString(videoId)}";

               using var statusResponse =
                   await apiHttpClient.GetAsync(statusEndpoint);

               var statusResponseText =
                   await statusResponse.Content.ReadAsStringAsync();

               if (!statusResponse.IsSuccessStatusCode)
               {
                   Console.WriteLine(
                       $"查詢任務失敗:{(int)statusResponse.StatusCode} " +
                       $"{statusResponse.StatusCode}");

                   Console.WriteLine(statusResponseText);
                   return;
               }

               var statusResult =
                   JsonSerializer.Deserialize(
                       statusResponseText,
                       new JsonSerializerOptions
                       {
                           PropertyNameCaseInsensitive = true
                       });

               Console.WriteLine(
                   $"目前狀態:{statusResult?.Status}," +
                   $"進度:{statusResult?.Progress}%");

               /*
                * 任務失敗時停止輪詢。
                */
               if (string.Equals(
                   statusResult?.Status,
                   "failed",
                   StringComparison.OrdinalIgnoreCase))
               {
                   Console.WriteLine("影片生成失敗");

                   if (statusResult?.Error != null)
                   {
                       Console.WriteLine(
                           JsonSerializer.Serialize(
                               statusResult.Error));
                   }
                   else
                   {
                       Console.WriteLine(statusResponseText);
                   }

                   return;
               }

               /*
                * Agnes AI 完成後,
                * 影片下載網址直接放在最外層 url。
                *
                * 不是 metadata.url。
                */
               if (string.Equals(
                   statusResult?.Status,
                   "completed",
                   StringComparison.OrdinalIgnoreCase))
               {
                   var videoUrl = statusResult?.Url;

                   if (string.IsNullOrWhiteSpace(videoUrl))
                   {
                       Console.WriteLine("找不到影片下載網址");
                       Console.WriteLine(statusResponseText);
                       return;
                   }

                   Console.WriteLine();
                   Console.WriteLine("影片生成完成");
                   Console.WriteLine($"影片網址:{videoUrl}");

                   await DownloadVideoAsync(videoUrl);
                   return;
               }
           }
       }

       private static async Task DownloadVideoAsync(string videoUrl)
       {
           /*
            * 影片下載必須使用新的 HttpClient。
            *
            * 不要使用前面帶有 Agnes API Key 的 apiHttpClient,
            * 避免將 Authorization Header 傳到影片輸出網域,
            * 否則有可能出現 401 Unauthorized。
            */
           using var downloadHttpClient = new HttpClient
           {
               Timeout = TimeSpan.FromMinutes(15)
           };

           Console.WriteLine("開始下載影片...");

           using var response =
               await downloadHttpClient.GetAsync(
                   videoUrl,
                   HttpCompletionOption.ResponseHeadersRead);

           if (!response.IsSuccessStatusCode)
           {
               var errorContent =
                   await response.Content.ReadAsStringAsync();

               Console.WriteLine(
                   $"影片下載失敗:{(int)response.StatusCode} " +
                   $"{response.StatusCode}");

               Console.WriteLine(errorContent);
               return;
           }

           var videoBytes =
               await response.Content.ReadAsByteArrayAsync();

           if (videoBytes.Length == 0)
           {
               Console.WriteLine("影片下載失敗:回傳內容為空");
               return;
           }

           /*
            * 影片儲存在程式執行目錄。
            *
            * 檔名加入目前時間,
            * 避免重複執行時覆蓋原本的影片。
            */
           var outputPath = Path.Combine(
               AppContext.BaseDirectory,
               $"agnes-video-{DateTime.Now:yyyyMMdd-HHmmss}.mp4");

           await File.WriteAllBytesAsync(
               outputPath,
               videoBytes);

           Console.WriteLine();
           Console.WriteLine("影片下載完成");
           Console.WriteLine($"檔案位置:{outputPath}");
           Console.WriteLine($"檔案大小:{videoBytes.Length:N0} Bytes");
       }
   }

   /*
    * Agnes AI 影片任務回應格式。
    */
   public sealed class VideoResponse
   {
       /*
        * 目前建立任務時,
        * 任務 ID 主要會放在 id。
        */
       [JsonPropertyName("id")]
       public string? Id { get; set; }

       /*
        * 保留其他可能的欄位格式。
        */
       [JsonPropertyName("task_id")]
       public string? TaskId { get; set; }

       [JsonPropertyName("video_id")]
       public string? VideoId { get; set; }

       /*
        * 例如:
        *
        * in_progress
        * completed
        * failed
        */
       [JsonPropertyName("status")]
       public string? Status { get; set; }

       /*
        * 影片生成進度。
        */
       [JsonPropertyName("progress")]
       public int Progress { get; set; }

       /*
        * 影片秒數。
        */
       [JsonPropertyName("seconds")]
       public string? Seconds { get; set; }

       /*
        * 影片尺寸。
        *
        * 例如:
        * 1088x832
        */
       [JsonPropertyName("size")]
       public string? Size { get; set; }

       /*
        * 影片完成後,
        * 下載網址直接位於最外層 url。
        */
       [JsonPropertyName("url")]
       public string? Url { get; set; }

       /*
        * 任務失敗時的錯誤內容。
        */
       [JsonPropertyName("error")]
       public object? Error { get; set; }

       /*
        * Agnes AI 可能自動調整輸出尺寸,
        * 詳細資訊會放在 size_mapping。
        */
       [JsonPropertyName("size_mapping")]
       public VideoSizeMapping? SizeMapping { get; set; }
   }

   public sealed class VideoSizeMapping
   {
       [JsonPropertyName("adjusted")]
       public bool Adjusted { get; set; }

       [JsonPropertyName("requested_width")]
       public int RequestedWidth { get; set; }

       [JsonPropertyName("requested_height")]
       public int RequestedHeight { get; set; }

       [JsonPropertyName("width")]
       public int Width { get; set; }

       [JsonPropertyName("height")]
       public int Height { get; set; }

       [JsonPropertyName("ratio")]
       public string? Ratio { get; set; }

       [JsonPropertyName("resolution")]
       public string? Resolution { get; set; }

       [JsonPropertyName("message")]
       public string? Message { get; set; }
   }

4. 中間遇到一些小問題 ,這邊就筆記一下 

第一個是影片下載網址不是放在 metadata.url

實際回傳是直接放在 JSON 最外層 url 所以程式必須直接讀取 var videoUrl = statusResult?.Url;

第二個問題是下載影片時,不要直接使用原本帶有 Agnes AI API Key 的 HttpClient

因為影片網址位於另一個網域,如果連 Authorization Header 一起送過去,有可能會出現 401 Unauthorized

所以範例中另外建立了一個沒有 Bearer Token 的 HttpClient,專門負責下載影片

我原本傳入的影片尺寸是 1152 × 768 但 Agnes AI 最後自動轉成 1088 × 832

 

結論

這次實際測試 Agnes AI 的影片生成 API,整個流程比圖片生成多了一個任務查詢的步驟,但其實沒有想像中複雜。

拿到 API Key 之後,使用 C# 的 HttpClient 就可以建立影片任務、查詢進度,最後再把影片下載到本機。

現在串接這類 AI API,其實程式碼也不用自己慢慢從頭寫。

你可以直接把官方文件跟這篇案例丟給 AI,再換掉 API Key 與提示詞,基本上很快就可以做出自己的 AI 影片生成功能。

至少目前實際測試下來,文字生成影片可以正常完成,產生的 MP4 也能直接下載。

之後不管是拿來做文章影片、商品展示、短影音素材,或整合到自己的工具中,都有不少可以玩的地方。

 

這邊附上產出的影片

 

---

The bug existed in all possible states.
Until I ran the code.