一、即梦视频生成能力申请进入即梦AI进行能力申请账号登录-火山引擎二、文档地址官方文档地址即梦AI-视频生成3.0 Pro-接口文档--即梦AI-火山引擎java代码调用demopackage 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 轮询直到 statusdone * 3. 从 data.video_url 下载 MP4 并保存到本地 * /pre * p * 注意本类为本地调试 DemoAK/SK 硬编码仅用于测试生产环境请改用配置中心或环境变量。 */ public class Sign { // 认证与连接配置 /** 火山引擎 Access Key */ private static final String AK 你的AK; /** 火山引擎 Secret KeyBase64 编码末尾通常带 */ 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. 提交异步任务 ---------- // ActionCVSync2AsyncSubmitTask注意不是 CVProcessCVProcess 为同步接口视频生成不支持 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(statusdone 但 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火山官方示例图 ListString 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)); // ListString 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 * 对应 APIActionCVSync2AsyncGetResult, Version2022-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, MapString, 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(yyyyMMddTHHmmssZ); 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; SortedMapString, 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
使用java实现即梦文生视频、图生视频,火山引擎「即梦 AI - 视频生成 3.0 Pro」调用 Demo(原生 HTTP 签名版)。
一、即梦视频生成能力申请进入即梦AI进行能力申请账号登录-火山引擎二、文档地址官方文档地址即梦AI-视频生成3.0 Pro-接口文档--即梦AI-火山引擎java代码调用demopackage 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 轮询直到 statusdone * 3. 从 data.video_url 下载 MP4 并保存到本地 * /pre * p * 注意本类为本地调试 DemoAK/SK 硬编码仅用于测试生产环境请改用配置中心或环境变量。 */ public class Sign { // 认证与连接配置 /** 火山引擎 Access Key */ private static final String AK 你的AK; /** 火山引擎 Secret KeyBase64 编码末尾通常带 */ 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. 提交异步任务 ---------- // ActionCVSync2AsyncSubmitTask注意不是 CVProcessCVProcess 为同步接口视频生成不支持 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(statusdone 但 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火山官方示例图 ListString 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)); // ListString 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 * 对应 APIActionCVSync2AsyncGetResult, Version2022-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, MapString, 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(yyyyMMddTHHmmssZ); 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; SortedMapString, 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