豆包 AI 首尾帧视频生成:C# 实现教程
摘要:本文介绍如何利用豆包AI开放平台实现首尾帧视频生成。通过C#调用API,将首尾帧图片转为Base64格式后提交,异步生成过渡帧视频。关键步骤包括:1) 开通豆包API权限并获取密钥;2) 使用HttpClient发送签名请求;3) 轮询任务状态获取结果。提供完整代码示例,涵盖图片处理、签名生成和API调用,支持自定义视频时长、帧率和分辨率。该方法适用于快速创建两帧间的平滑过渡动画。(149字
·
要实现首尾帧模式的视频生成(即通过首帧、尾帧图片,由AI生成中间过渡帧并拼接为完整视频),核心是借助 豆包AI开放平台的视频生成API(支持图像到视频的生成),结合C#完成API调用、参数配置、结果处理。以下是完整实现方案:
一、前置准备
1. 开通豆包AI开放平台服务
- 注册并登录 豆包AI开放平台
- 创建应用,获取 API Key 和 Secret Key(在「应用管理」-「密钥管理」中查看)
- 开通「视频生成」相关接口权限(在「接口管理」中找到「图像生成视频」或「首尾帧视频生成」接口,申请开通)
2. 环境与依赖
- .NET Framework 4.8+ 或 .NET Core 3.1+
- 必要NuGet包(右键项目 → 管理NuGet程序包):
Newtonsoft.Json(JSON序列化/反序列化)System.Net.Http(HTTP请求)System.Drawing.Common(图片Base64编码,.NET Core需单独安装)
二、核心原理
首尾帧视频生成的核心流程:
- 将本地首帧、尾帧图片转为 Base64编码(API要求的图片传输格式)
- 调用豆包AI的「首尾帧视频生成API」,传入Base64、视频参数(时长、帧率等)
- API返回任务ID(视频生成是异步任务,需轮询查询结果)
- 轮询「任务查询API」,直到生成完成,获取视频URL
- 下载视频到本地或直接使用
三、完整C#实现代码
1. 配置类与工具类
先封装API配置、签名生成(豆包API需签名鉴权)、图片Base64转换等通用逻辑:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace DoubaoVideoGenerator
{
/// <summary>
/// 豆包AI API配置
/// </summary>
public class DoubaoConfig
{
// 替换为你的API Key和Secret Key(从豆包开放平台获取)
public string ApiKey { get; set; } = "your-api-key";
public string SecretKey { get; set; } = "your-secret-key";
// 首尾帧视频生成API地址(参考豆包官方文档,以最新地址为准)
public string CreateTaskUrl { get; set; } = "https://aquasearch.ai/api/v1/video/generate/frame-to-frame";
// 任务查询API地址
public string QueryTaskUrl { get; set; } = "https://aquasearch.ai/api/v1/video/task/query";
}
/// <summary>
/// 视频生成参数
/// </summary>
public class VideoGenerateParams
{
/// <summary>
/// 首帧图片Base64编码
/// </summary>
[JsonProperty("first_frame")]
public string FirstFrameBase64 { get; set; }
/// <summary>
/// 尾帧图片Base64编码
/// </summary>
[JsonProperty("last_frame")]
public string LastFrameBase64 { get; set; }
/// <summary>
/// 视频时长(秒),范围:3-60
/// </summary>
[JsonProperty("duration")]
public int Duration { get; set; } = 10;
/// <summary>
/// 帧率(fps),可选:15/24/30
/// </summary>
[JsonProperty("fps")]
public int Fps { get; set; } = 24;
/// <summary>
/// 视频分辨率,可选:720p(1280x720)/1080p(1920x1080)
/// </summary>
[JsonProperty("resolution")]
public string Resolution { get; set; } = "720p";
/// <summary>
/// 生成风格(可选,参考API文档)
/// </summary>
[JsonProperty("style")]
public string Style { get; set; } = "realistic";
}
/// <summary>
/// 豆包AI视频生成工具类
/// </summary>
public class DoubaoVideoGenerator
{
private readonly DoubaoConfig _config;
private readonly HttpClient _httpClient;
public DoubaoVideoGenerator(DoubaoConfig config)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_httpClient = new HttpClient();
_httpClient.Timeout = TimeSpan.FromMinutes(5); // 延长超时时间(视频生成耗时)
}
/// <summary>
/// 本地图片转为Base64编码(带格式前缀)
/// </summary>
/// <param name="imagePath">图片路径(支持JPG/PNG)</param>
/// <returns>Base64字符串</returns>
public string ImageToBase64(string imagePath)
{
if (!File.Exists(imagePath))
throw new FileNotFoundException("图片文件不存在", imagePath);
using (var image = Image.FromFile(imagePath))
using (var ms = new MemoryStream())
{
// 保留原图片格式
var extension = Path.GetExtension(imagePath).ToLower();
var format = extension switch
{
".jpg" or ".jpeg" => ImageFormat.Jpeg,
".png" => ImageFormat.Png,
_ => throw new NotSupportedException("仅支持JPG/PNG格式图片")
};
image.Save(ms, format);
byte[] imageBytes = ms.ToArray();
return $"data:image/{extension.TrimStart('.')};base64,{Convert.ToBase64String(imageBytes)}";
}
}
/// <summary>
/// 生成API请求签名(豆包API鉴权要求)
/// 签名规则:HMAC-SHA256(secretKey, timestamp + nonce + apiKey + 请求体JSON)
/// </summary>
private string GenerateSignature(string requestBody, out long timestamp, out string nonce)
{
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
nonce = Guid.NewGuid().ToString("N"); // 随机字符串
// 拼接签名原文
string signText = $"{timestamp}{nonce}{_config.ApiKey}{requestBody}";
// HMAC-SHA256加密
using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_config.SecretKey)))
{
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signText));
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
/// <summary>
/// 创建首尾帧视频生成任务
/// </summary>
/// <param name="params">生成参数</param>
/// <returns>任务ID</returns>
public async Task<string> CreateFrameToFrameTaskAsync(VideoGenerateParams @params)
{
if (@params == null)
throw new ArgumentNullException(nameof(@params));
// 序列化请求体
string requestBody = JsonConvert.SerializeObject(@params);
// 生成签名
string signature = GenerateSignature(requestBody, out long timestamp, out string nonce);
// 构造请求头
var headers = new Dictionary<string, string>
{
["Content-Type"] = "application/json",
["X-API-Key"] = _config.ApiKey,
["X-Timestamp"] = timestamp.ToString(),
["X-Nonce"] = nonce,
["X-Signature"] = signature
};
// 发送POST请求
var request = new HttpRequestMessage(HttpMethod.Post, _config.CreateTaskUrl)
{
Content = new StringContent(requestBody, Encoding.UTF8, "application/json")
};
foreach (var header in headers)
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode(); // 抛出HTTP错误
// 解析响应
string responseJson = await response.Content.ReadAsStringAsync();
dynamic result = JsonConvert.DeserializeObject(responseJson);
if (result.code != 0)
throw new Exception($"创建任务失败:{result.msg}");
return result.data.task_id; // 返回任务ID
}
/// <summary>
/// 轮询查询任务状态
/// </summary>
/// <param name="taskId">任务ID</param>
/// <param name="pollInterval">轮询间隔(默认5秒)</param>
/// <returns>生成的视频URL</returns>
public async Task<string> PollTaskStatusAsync(string taskId, int pollInterval = 5000)
{
if (string.IsNullOrEmpty(taskId))
throw new ArgumentException("任务ID不能为空");
while (true)
{
// 构造查询参数
var queryParams = new Dictionary<string, string>
{
["task_id"] = taskId
};
string queryString = string.Join("&", queryParams.Keys.Select(k => $"{k}={Uri.EscapeDataString(queryParams[k])}"));
string queryUrl = $"{_config.QueryTaskUrl}?{queryString}";
// 生成签名(查询请求体为空,requestBody传空字符串)
string signature = GenerateSignature("", out long timestamp, out string nonce);
// 构造请求头
var request = new HttpRequestMessage(HttpMethod.Get, queryUrl);
request.Headers.TryAddWithoutValidation("X-API-Key", _config.ApiKey);
request.Headers.TryAddWithoutValidation("X-Timestamp", timestamp.ToString());
request.Headers.TryAddWithoutValidation("X-Nonce", nonce);
request.Headers.TryAddWithoutValidation("X-Signature", signature);
// 发送查询请求
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseJson = await response.Content.ReadAsStringAsync();
dynamic result = JsonConvert.DeserializeObject(responseJson);
if (result.code != 0)
throw new Exception($"查询任务失败:{result.msg}");
// 解析任务状态(参考豆包API文档的状态码定义)
string status = result.data.status;
Console.WriteLine($"任务状态:{status}(任务ID:{taskId})");
switch (status)
{
case "SUCCESS": // 生成成功
return result.data.video_url; // 返回视频URL
case "FAILED": // 生成失败
throw new Exception($"任务失败:{result.data.fail_reason}");
case "PROCESSING": // 处理中,继续轮询
await Task.Delay(pollInterval);
break;
default:
throw new Exception($"未知任务状态:{status}");
}
}
}
/// <summary>
/// 下载视频到本地
/// </summary>
/// <param name="videoUrl">视频URL</param>
/// <param name="savePath">保存路径(含文件名,如:output.mp4)</param>
public async Task DownloadVideoAsync(string videoUrl, string savePath)
{
if (string.IsNullOrEmpty(videoUrl))
throw new ArgumentException("视频URL不能为空");
using (var response = await _httpClient.GetAsync(videoUrl, HttpCompletionOption.ResponseHeadersRead))
{
response.EnsureSuccessStatusCode();
using (var stream = await response.Content.ReadAsStreamAsync())
using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
await stream.CopyToAsync(fileStream);
Console.WriteLine($"视频已保存到:{savePath}");
}
}
}
}
}
2. 主程序调用示例
using System;
using System.Threading.Tasks;
namespace DoubaoVideoGenerator
{
class Program
{
static async Task Main(string[] args)
{
try
{
// 1. 配置豆包API密钥
var config = new DoubaoConfig
{
ApiKey = "替换为你的API Key",
SecretKey = "替换为你的Secret Key"
};
// 2. 初始化生成器
var generator = new DoubaoVideoGenerator(config);
// 3. 准备首尾帧图片(替换为你的本地图片路径)
string firstFramePath = @"C:\images\first_frame.jpg";
string lastFramePath = @"C:\images\last_frame.jpg";
// 4. 图片转Base64
Console.WriteLine("正在转换图片为Base64...");
string firstFrameBase64 = generator.ImageToBase64(firstFramePath);
string lastFrameBase64 = generator.ImageToBase64(lastFramePath);
// 5. 配置视频生成参数
var videoParams = new VideoGenerateParams
{
FirstFrameBase64 = firstFrameBase64,
LastFrameBase64 = lastFrameBase64,
Duration = 15, // 视频时长15秒
Fps = 30, // 帧率30fps
Resolution = "1080p", // 1080P分辨率
Style = "cinematic" // 电影风格(可选,参考API文档)
};
// 6. 创建视频生成任务
Console.WriteLine("正在创建视频生成任务...");
string taskId = await generator.CreateFrameToFrameTaskAsync(videoParams);
Console.WriteLine($"任务创建成功,任务ID:{taskId}");
// 7. 轮询任务状态,获取视频URL
Console.WriteLine("正在等待视频生成(可能需要1-5分钟)...");
string videoUrl = await generator.PollTaskStatusAsync(taskId);
Console.WriteLine($"视频生成成功,URL:{videoUrl}");
// 8. 下载视频到本地
string savePath = @"C:\output\frame_to_frame_video.mp4";
Console.WriteLine("正在下载视频...");
await generator.DownloadVideoAsync(videoUrl, savePath);
Console.WriteLine("所有操作完成!");
}
catch (Exception ex)
{
Console.WriteLine($"出错:{ex.Message}");
}
finally
{
Console.WriteLine("按任意键退出...");
Console.ReadKey();
}
}
}
}
四、关键说明
1. API地址与参数调整
- 代码中的API地址(
CreateTaskUrl、QueryTaskUrl)需参考 豆包官方最新文档(可能会更新),请登录开放平台查看「首尾帧视频生成」接口的正式地址。 - 视频参数(时长、帧率、分辨率、风格)需符合API限制(例如时长3-60秒),具体以官方文档为准。
2. 签名鉴权
豆包API的签名规则可能会调整,核心是:
- 签名原文 = 时间戳(毫秒级) + 随机字符串 + API Key + 请求体JSON
- 加密方式:HMAC-SHA256(密钥为Secret Key)
- 请务必按照官方文档的签名规则修改
GenerateSignature方法,否则会鉴权失败。
3. 异步任务处理
视频生成是异步任务(耗时1-5分钟),需通过轮询任务ID查询状态,不可直接等待响应(会超时)。代码中PollTaskStatusAsync方法已实现自动轮询。
4. 图片格式要求
- 支持JPG/PNG格式,建议首尾帧图片分辨率一致(避免生成视频拉伸)。
- 图片大小建议不超过5MB,过大可能导致API请求失败。
五、常见问题排查
-
鉴权失败(401错误):
- 检查API Key和Secret Key是否正确。
- 签名生成逻辑是否与官方文档一致(时间戳是否为毫秒级、参数顺序是否正确)。
-
图片转换失败:
- 确认图片路径正确,文件未被占用。
- 仅支持JPG/PNG格式,其他格式需先转换。
-
任务生成失败:
- 检查首尾帧图片是否符合分辨率要求(例如API要求最小640x480)。
- 视频时长、帧率是否超出API限制。
- 账户是否有足够的调用额度(豆包API可能有免费额度,超出后需付费)。
-
下载超时:
- 延长
HttpClient的超时时间(代码中已设为5分钟)。 - 直接复制视频URL到浏览器下载,排查网络问题。
- 延长
六、扩展功能
- 视频格式转换:生成的视频可能为MP4格式,若需其他格式(如AVI、MOV),可结合FFmpeg(C#通过
Process调用)进行转换。 - 进度显示:在轮询任务时,可根据API返回的进度百分比(若支持)显示生成进度。
- 批量生成:循环调用
CreateFrameToFrameTaskAsync,批量处理多组首尾帧。 - 错误重试:对API请求添加重试机制(使用
Polly库),提高稳定性。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐




所有评论(0)