一、即梦视频生成能力申请
进入即梦AI进行能力申请:账号登录-火山引擎

二、文档地址
官方文档地址:即梦AI-视频生成3.0 Pro-接口文档--即梦AI-火山引擎
java代码调用demo:
java
package cn.iocoder.yudao.module.iscs.utils;
import com.alibaba.fastjson.JSONObject;
import com.google.common.io.ByteStreams;
import com.volcengine.helper.Utils;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 火山引擎「即梦 AI - 视频生成 3.0 Pro」调用 Demo(原生 HTTP 签名版)。
* <p>
* 官方文档:https://www.volcengine.com/docs/85621/1777001
* <p>
* 模型标识(req_key):{@code jimeng_ti2v_v30_pro}
* <ul>
* <li>支持<strong>文生视频</strong>:仅传 prompt,由文字描述生成 1080P 视频</li>
* <li>支持<strong>图生视频(首帧)</strong>:首帧图片 + prompt,让静态图「动起来」</li>
* </ul>
* <p>
* 调用流程(异步,与文生图相同):
* <pre>
* 1. CVSync2AsyncSubmitTask --- 提交任务,返回 task_id
* 2. CVSync2AsyncGetResult --- 按 task_id 轮询,直到 status=done
* 3. 从 data.video_url 下载 MP4 并保存到本地
* </pre>
* <p>
* 注意:本类为本地调试 Demo,AK/SK 硬编码仅用于测试,生产环境请改用配置中心或环境变量。
*/
public class Sign {
// ======================== 认证与连接配置 ========================
/** 火山引擎 Access Key */
private static final String AK = "你的AK";
/** 火山引擎 Secret Key(Base64 编码,末尾通常带 ==) */
private static final String SK = "你的SK";
/** 视觉智能 API 固定 endpoint,不可使用 open.volcengineapi.com */
private static final String HOST = "visual.volcengineapi.com";
private static final String REGION = "cn-north-1";
private static final String SERVICE = "cv";
private static final String SCHEMA = "https";
private static final String PATH = "/";
private static final String API_VERSION = "2022-08-31";
// ======================== 模型与轮询配置 ========================
/**
* 能力标识(req_key)。
* 即梦视频生成 3.0 Pro 同时支持文生视频和图生视频,提交与查询时均需携带同一 req_key。
*/
private static final String REQ_KEY = "jimeng_ti2v_v30_pro";
/** 轮询间隔(毫秒);视频生成通常需 1~5 分钟,建议 5 秒查一次 */
private static final long POLL_INTERVAL_MS = 5_000;
/** 最大轮询次数,120 × 5s = 10 分钟,超时则放弃 */
private static final int MAX_POLL_TIMES = 120;
// ======================== 运行模式开关(改这里切换文生/图生) ========================
/**
* 当前演示模式。
* <ul>
* <li>{@link VideoMode#TEXT_TO_VIDEO} --- 文生视频,仅需 prompt</li>
* <li>{@link VideoMode#IMAGE_TO_VIDEO} --- 图生视频,需首帧图片 + prompt</li>
* </ul>
*/
private static final VideoMode CURRENT_MODE = VideoMode.TEXT_TO_VIDEO;
/** 生成视频保存目录(可按本机路径修改) */
private static final String OUTPUT_DIR = "C:\\Users\\HP\\Pictures\\Saved Pictures\\";
// ======================== 编码工具(签名用,无需修改) ========================
private static final BitSet URLENCODER = new BitSet(256);
private static final String CONST_ENCODE = "0123456789ABCDEF";
public static final Charset UTF_8 = StandardCharsets.UTF_8;
static {
int i;
for (i = 97; i <= 122; ++i) {
URLENCODER.set(i);
}
for (i = 65; i <= 90; ++i) {
URLENCODER.set(i);
}
for (i = 48; i <= 57; ++i) {
URLENCODER.set(i);
}
URLENCODER.set('-');
URLENCODER.set('_');
URLENCODER.set('.');
URLENCODER.set('~');
}
// ======================== 程序入口 ========================
public static void main(String[] args) throws Exception {
Sign sign = new Sign(REGION, SERVICE, SCHEMA, HOST, PATH, AK, SK);
// ---------- 1. 按模式构建提交请求体 ----------
JSONObject submitReq = buildSubmitRequest(CURRENT_MODE);
System.out.println("当前模式:" + CURRENT_MODE.label);
System.out.println("提交参数:" + submitReq.toJSONString());
// ---------- 2. 提交异步任务 ----------
// Action=CVSync2AsyncSubmitTask(注意:不是 CVProcess!CVProcess 为同步接口,视频生成不支持)
String submitRespBody = sign.doRequest("POST", new HashMap<>(),
submitReq.toJSONString().getBytes(StandardCharsets.UTF_8),
new Date(), "CVSync2AsyncSubmitTask", API_VERSION);
System.out.println("提交任务返回:" + submitRespBody);
JSONObject submitResp = JSONObject.parseObject(submitRespBody);
checkResponse(submitResp, "提交任务");
String taskId = submitResp.getJSONObject("data").getString("task_id");
if (StringUtils.isBlank(taskId)) {
System.out.println("task_id 为空,无法查询结果");
return;
}
System.out.println("task_id:" + taskId);
// ---------- 3. 轮询直到任务完成 ----------
JSONObject resultData = pollTaskResult(sign, taskId);
if (resultData == null) {
System.out.println("任务超时或失败,未获取到视频");
return;
}
// ---------- 4. 下载并保存视频 ----------
String videoUrl = resultData.getString("video_url");
if (StringUtils.isBlank(videoUrl)) {
System.out.println("status=done 但 video_url 为空,完整 data:" + resultData.toJSONString());
System.out.println("可能原因:生成内部异常、内容审核未通过,或需稍后重试查询");
return;
}
String outputPath = OUTPUT_DIR + System.currentTimeMillis() + ".mp4";
saveVideoFromUrl(videoUrl, outputPath);
System.out.println("视频保存成功:" + new File(outputPath).getAbsolutePath());
System.out.println("视频地址:" + videoUrl);
}
// ======================== 文生视频 / 图生视频 传参方案 ========================
/**
* 演示模式枚举。
*/
enum VideoMode {
/** 文生视频:仅文本提示词 */
TEXT_TO_VIDEO("文生视频"),
/** 图生视频(首帧):首帧图片 + 文本提示词 */
IMAGE_TO_VIDEO("图生视频-首帧");
final String label;
VideoMode(String label) {
this.label = label;
}
}
/**
* 按模式构建「提交任务」请求体。
*
* @param mode 文生视频 或 图生视频
* @return 可直接 POST 的 JSON 请求体
*/
private static JSONObject buildSubmitRequest(VideoMode mode) {
switch (mode) {
case TEXT_TO_VIDEO:
return buildTextToVideoRequest();
case IMAGE_TO_VIDEO:
return buildImageToVideoRequest();
default:
throw new IllegalArgumentException("未知模式: " + mode);
}
}
/**
* 【文生视频】传参方案
* <p>
* 仅需文字描述,由模型直接生成 1080P 视频。
* <p>
* 必填参数:
* <ul>
* <li>{@code req_key} --- 固定 {@code jimeng_ti2v_v30_pro}</li>
* <li>{@code prompt} --- 视频内容描述,支持中文;建议按镜头分段描述动作与场景</li>
* </ul>
* 可选参数:
* <ul>
* <li>{@code frames} --- 总帧数,{@code 121}≈5秒,{@code 241}≈10秒,默认 121</li>
* <li>{@code aspect_ratio} --- 宽高比:{@code 16:9}、{@code 4:3}、{@code 1:1}、{@code 3:4}、{@code 9:16}、{@code 21:9},默认 16:9</li>
* <li>{@code seed} --- 随机种子,{@code -1} 表示随机,固定正整数可复现结果</li>
* </ul>
* 不需要传的参数:
* <ul>
* <li>{@code image_urls}、{@code binary_data_base64} --- 文生视频不传图片</li>
* </ul>
* 请求示例:
* <pre>{@code
* {
* "req_key": "jimeng_ti2v_v30_pro",
* "prompt": "一只橘猫趴在键盘上打哈欠,温暖的台灯光线,镜头缓缓推进",
* "frames": 121,
* "aspect_ratio": "16:9",
* "seed": -1
* }
* }</pre>
*/
private static JSONObject buildTextToVideoRequest() {
JSONObject req = new JSONObject();
req.put("req_key", REQ_KEY);
req.put("prompt", "新疆风情浓郁的肖像视频,一位神秘女子留着齐腰波浪长发,脸上妆容现代精致,女子头转向侧面,随着微风轻拂,几缕发丝飘逸飞舞,女子侧脸对镜头微笑,深邃的背景色调烘托出温暖而静谧的氛围。高清写实风格,动作自然流畅,光影层次丰");
req.put("frames", 121); // 121 帧 ≈ 5 秒;241 帧 ≈ 10 秒
req.put("aspect_ratio", "16:9"); // 文生视频可指定画幅比例
req.put("seed", -1); // -1 = 每次随机;填正整数可固定随机种子
return req;
}
/**
* 【图生视频-首帧】传参方案
* <p>
* 以一张静态图片作为视频首帧,配合 prompt 描述「画面如何动起来」。
* <p>
* 必填参数:
* <ul>
* <li>{@code req_key} --- 固定 {@code jimeng_ti2v_v30_pro}</li>
* <li>{@code prompt} --- 描述动态效果,如表情变化、镜头运动、风吹发丝等</li>
* <li>首帧图片(二选一,不可同时传):
* <ul>
* <li>{@code image_urls} --- 公网可访问的图片 URL 数组,仅 1 张</li>
* <li>{@code binary_data_base64} --- 图片 Base64 编码数组,支持 JPEG/PNG,仅 1 张</li>
* </ul>
* </li>
* </ul>
* 可选参数:
* <ul>
* <li>{@code frames} --- 总帧数,{@code 121}≈5秒,{@code 241}≈10秒,默认 121</li>
* <li>{@code seed} --- 随机种子,{@code -1} 表示随机</li>
* </ul>
* 说明:
* <ul>
* <li>图生视频时画幅由首帧图片决定,一般<strong>无需传 aspect_ratio</strong></li>
* <li>图片须清晰、主体明确;URL 须公网可访问,火山服务端需能下载</li>
* <li>本地图片可先转 Base64 放入 {@code binary_data_base64},避免上传图床</li>
* </ul>
* 请求示例(image_urls 方式):
* <pre>{@code
* {
* "req_key": "jimeng_ti2v_v30_pro",
* "prompt": "女孩缓缓睁开眼睛,头发被风吹动,镜头缓缓拉出",
* "image_urls": ["https://example.com/first_frame.png"],
* "frames": 121,
* "seed": -1
* }
* }</pre>
* 请求示例(binary_data_base64 方式):
* <pre>{@code
* {
* "req_key": "jimeng_ti2v_v30_pro",
* "prompt": "女孩缓缓睁开眼睛,头发被风吹动",
* "binary_data_base64": ["<图片文件的Base64字符串>"],
* "frames": 121,
* "seed": -1
* }
* }</pre>
*/
private static JSONObject buildImageToVideoRequest() {
JSONObject req = new JSONObject();
req.put("req_key", REQ_KEY);
req.put("prompt", "女孩抱着狐狸,女孩睁开眼,温柔地看向镜头,狐狸友善地抱着,镜头缓缓拉出,女孩的头发被风吹动");
// 方式一(推荐调试):公网图片 URL,火山官方示例图
List<String> imageUrls = new ArrayList<>();
imageUrls.add("https://ark-project.tos-cn-beijing.volces.com/doc_image/i2v_foxrgirl.png");
req.put("image_urls", imageUrls);
// 方式二:本地图片转 Base64(与 image_urls 二选一,调试时取消下面注释并注释掉 image_urls)
// byte[] imageBytes = FileUtils.readFileToByteArray(new File("C:\\path\\to\\your\\image.png"));
// List<String> base64List = new ArrayList<>();
// base64List.add(Base64.getEncoder().encodeToString(imageBytes));
// req.put("binary_data_base64", base64List);
req.put("frames", 121);
req.put("seed", -1);
return req;
}
// ======================== 轮询与结果处理 ========================
/**
* 轮询查询任务结果。
* <p>
* 对应 API:Action=CVSync2AsyncGetResult, Version=2022-08-31
* <p>
* 查询请求体固定格式:
* <pre>{@code
* {
* "req_key": "jimeng_ti2v_v30_pro",
* "task_id": "<提交任务返回的 task_id>"
* }
* }</pre>
* 任务状态(data.status):
* <ul>
* <li>{@code in_queue} --- 排队中</li>
* <li>{@code generating} --- 生成中</li>
* <li>{@code done} --- 完成,读取 data.video_url</li>
* <li>{@code failed} --- 失败</li>
* </ul>
*/
private static JSONObject pollTaskResult(Sign sign, String taskId) throws Exception {
JSONObject queryReq = new JSONObject();
queryReq.put("req_key", REQ_KEY);
queryReq.put("task_id", taskId);
for (int i = 1; i <= MAX_POLL_TIMES; i++) {
Thread.sleep(POLL_INTERVAL_MS);
String queryRespBody = sign.doRequest("POST", new HashMap<>(),
queryReq.toJSONString().getBytes(StandardCharsets.UTF_8),
new Date(), "CVSync2AsyncGetResult", API_VERSION);
String logContent = queryRespBody.length() < 1000 ? queryRespBody : "响应过长,已省略";
System.out.println("第 " + i + " 次查询:" + logContent);
JSONObject queryResp = JSONObject.parseObject(queryRespBody);
if (queryResp == null) {
continue;
}
int code = queryResp.getIntValue("code");
if (code != 10000 && code != 0) {
throw new RuntimeException("查询失败: " + queryResp.getString("message"));
}
JSONObject data = queryResp.getJSONObject("data");
if (data == null) {
continue;
}
String status = data.getString("status");
if ("done".equalsIgnoreCase(status) || "success".equalsIgnoreCase(status)) {
return data;
}
if ("failed".equalsIgnoreCase(status) || "error".equalsIgnoreCase(status)) {
throw new RuntimeException("任务失败: " + data.toJSONString());
}
}
return null;
}
private static void checkResponse(JSONObject resp, String step) {
if (resp == null) {
throw new RuntimeException(step + "响应为空");
}
int code = resp.getIntValue("code");
if (code != 10000 && code != 0) {
throw new RuntimeException(step + "失败: " + resp.getString("message"));
}
if (resp.getJSONObject("data") == null) {
throw new RuntimeException(step + " data 字段为空");
}
}
private static void saveVideoFromUrl(String url, String path) throws Exception {
File file = new File(path);
FileUtils.forceMkdirParent(file);
FileUtils.copyURLToFile(new URL(url), file, 10_000, 120_000);
}
// ======================== HTTP 签名请求(火山 V4 签名) ========================
private final String region;
private final String service;
private final String schema;
private final String host;
private final String path;
private final String ak;
private final String sk;
public Sign(String region, String service, String schema, String host, String path, String ak, String sk) {
this.region = region;
this.service = service;
this.schema = schema;
this.host = host;
this.path = path;
this.ak = ak;
this.sk = sk;
}
/**
* 发送已签名的 HTTP 请求,返回响应体字符串。
*
* @param action 接口名,如 CVSync2AsyncSubmitTask、CVSync2AsyncGetResult
*/
public String doRequest(String method, Map<String, String> queryList, byte[] body,
Date date, String action, String version) throws Exception {
if (body == null) {
body = new byte[0];
}
String xContentSha256 = hashSHA256(body);
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String xDate = sdf.format(date);
String shortXDate = xDate.substring(0, 8);
String contentType = "application/json";
String signHeader = "host;x-date;x-content-sha256;content-type";
SortedMap<String, String> realQueryList = new TreeMap<>(queryList);
realQueryList.put("Action", action);
realQueryList.put("Version", version);
StringBuilder querySB = new StringBuilder();
for (String key : realQueryList.keySet()) {
querySB.append(signStringEncoder(key)).append("=").append(signStringEncoder(realQueryList.get(key))).append("&");
}
querySB.deleteCharAt(querySB.length() - 1);
String canonicalStringBuilder = method + "\n" + path + "\n" + querySB + "\n" +
"host:" + host + "\n" +
"x-date:" + xDate + "\n" +
"x-content-sha256:" + xContentSha256 + "\n" +
"content-type:" + contentType + "\n" +
"\n" +
signHeader + "\n" +
xContentSha256;
String hashcanonicalString = hashSHA256(canonicalStringBuilder.getBytes(StandardCharsets.UTF_8));
String credentialScope = shortXDate + "/" + region + "/" + service + "/request";
String signString = "HMAC-SHA256" + "\n" + xDate + "\n" + credentialScope + "\n" + hashcanonicalString;
byte[] signKey = genSigningSecretKeyV4(sk, shortXDate, region, service);
String signature = Hex.encodeHexString(Utils.hmacSHA256(signKey, signString));
URL url = new URL(schema + "://" + host + path + "?" + querySB);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
conn.setRequestProperty("Host", host);
conn.setRequestProperty("X-Date", xDate);
conn.setRequestProperty("X-Content-Sha256", xContentSha256);
conn.setRequestProperty("Content-Type", contentType);
conn.setRequestProperty("Authorization", "HMAC-SHA256" +
" Credential=" + ak + "/" + credentialScope +
", SignedHeaders=" + signHeader +
", Signature=" + signature);
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(body);
}
conn.connect();
int responseCode = conn.getResponseCode();
InputStream is = responseCode == 200 ? conn.getInputStream() : conn.getErrorStream();
String responseBody = new String(ByteStreams.toByteArray(is), StandardCharsets.UTF_8);
is.close();
System.out.println("HTTP " + responseCode + " | Action=" + action);
if (responseCode != 200) {
System.out.println("错误响应:" + responseBody);
}
return responseBody;
}
private String signStringEncoder(String source) {
if (source == null) {
return null;
}
StringBuilder buf = new StringBuilder(source.length());
ByteBuffer bb = UTF_8.encode(source);
while (bb.hasRemaining()) {
int b = bb.get() & 255;
if (URLENCODER.get(b)) {
buf.append((char) b);
} else if (b == 32) {
buf.append("%20");
} else {
buf.append("%");
char hex1 = CONST_ENCODE.charAt(b >> 4);
char hex2 = CONST_ENCODE.charAt(b & 15);
buf.append(hex1);
buf.append(hex2);
}
}
return buf.toString();
}
public static String hashSHA256(byte[] content) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return Hex.encodeHexString(md.digest(content));
}
public static byte[] hmacSHA256(byte[] key, String content) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(content.getBytes(StandardCharsets.UTF_8));
}
private byte[] genSigningSecretKeyV4(String secretKey, String date, String region, String service) throws Exception {
byte[] kDate = hmacSHA256(secretKey.getBytes(StandardCharsets.UTF_8), date);
byte[] kRegion = hmacSHA256(kDate, region);
byte[] kService = hmacSHA256(kRegion, service);
return hmacSHA256(kService, "request");
}
}
运行结果如下:

视频地址:
Saved Pictures1780987693143