本文详细介绍了通过API接入和使用播报视频合成服务的技术流程,包括模板准备、变量替换、视频合成及状态轮询等关键步骤。
操作流程
在视频创作工作台创建视频项目作为播报模板,并定义动态变量。
使用查询播报模板详情API获取模板信息和动态变量。
通过创建播报视频API动态设置变量值,提交视频合成。
通过查询视频API轮询视频合成状态,获取视频结果。
1 准备播报模板
使用API生成视频前,须前往视频创作控制台创建一个项目作为播报模板,具体操作教程见 数字人视频创作,并基于模板定义多个动态变量。创建各类变量的方式如下:
1.1 文本变量创建
在视频创作工作台左侧导航栏点击“内容”,选择文本输入,并通过{{}}作为占位符定义文本变量。

1.2 图片变量创建
须先上传一个贴图素材作为占位,表达图片的大小和位置,选中贴图元素,点击菜单中“API”完成图片变量定义。

2 获取播报模板信息和动态变量
通过列举播报模板API获取已创建的播报模板列表,从中获取模板ID。
使用查询播报模板详情API获取模板信息和待替换的变量。
示例响应如下:
{
"id": "BS1b2WNnRMu4ouRzT4clY9Jhg",
"name": "测试播报模板",
"variables": [
{
"name": "slide_script",
"type": "text"
},
{
"name": "slide_image",
"type": "image"
}
]
}模板ID也可以在视频创作工作台直接获取:

3 基于播报模板创建视频
(可选)若希望替换贴图元素,需通过创建播报贴图API获取贴图ID,用于后续变量替换。针对图片变量,可通过设置fit参数控制图片填充方式,默认为contain。填充方式示例如下:
contain:保持图像宽高比,完整显示在容器内,可能留白但不裁剪。
cover:保持图像宽高比,完全填满容器,可能裁剪但不留白。
fill:拉伸图像以完全填满容器,不留白也不裁剪,但会失真变形。
选择播报模板,完成变量信息填充,通过创建播报视频API提交视频生成,获取对应的视频ID。
示例请求如下:
{
"templateId": "BS1b2WNnRMu4ouRzT4clY9Jhg",
"variables": [
{
"name": "slide_script",
"type": "text",
"properties": {
"content": "待替换内容"
}
},
{
"name": "slide_image",
"type": "image",
"properties": {
"resourceId": "M1smBGOteOqnwA7y0-YJgCoQ",
"fit": "contain"
}
}
]
}4 视频状态轮询
根据上一步获取到的视频ID通过查询视频API轮询视频合成状态。由于视频合成需要一定的时间,所以该接口需要定时轮询调用,建议轮询间隔3s,轮询过于频繁可能会导致查询失败。查询视频状态直到状态显示为成功或者失败,状态为成功的时候可以获取到对应的视频下载URL,然后直接通过URL下载到对应的视频,针对失败的视频可以根据对应的失败原因进行修改重新提交。
播报视频合成完整调用示例代码
引入SDK
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>lingmou20250527</artifactId>
<!-- 请将 'the-latest-version' 替换为最新版本号:https://mvnrepository.com/artifact/com.aliyun/lingmou20250527 -->
<version>${the-latest-version:1.4.0}</version>
</dependency>示例代码
package com.alibaba.avatar.sample;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.lingmou20250527.Client;
import com.aliyun.lingmou20250527.models.*;
import com.aliyun.lingmou20250527.models.CreateBroadcastVideoFromTemplateRequest.CreateBroadcastVideoFromTemplateRequestVideoOptions;
import com.aliyun.lingmou20250527.models.GetUploadPolicyResponseBody.GetUploadPolicyResponseBodyData;
import com.aliyun.teaopenapi.models.Config;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
public class BroadcastVideoDemo {
private static final String ENDPOINT = "lingmou.cn-beijing.aliyuncs.com";
private static final String STICKER_TYPE = "INPUT_BROADCAST_STICKER";
public static void main(String[] args) throws Exception {
// 0. 初始化sdk client
Client client = BroadcastVideoDemo.getInstance();
// 1. 上传贴图素材并创建贴图素材记录,如图片存在内容安全问题会失败(Optional,替换贴图元素时需要)
File imageFile = new File("/path/to/sticker");
GetUploadPolicyResponseBodyData uploadPolicy = upload(client, STICKER_TYPE, imageFile);
String stickerId = createBroadcastSticker(client, uploadPolicy.getOssKey(), imageFile.getName());
// 2.1 获取播报模板列表(获取 templateId)
List<BroadcastTemplate> templates = listBroadcastTemplates(client);
// 2.2 查询播报模板详情(返回可替换的元素 variables)
BroadcastTemplate template = getBroadcastTemplate(client, templates.get(0).getId());
// 3. 基于模板合成视频,失败情况
// - 未开通商业化
// - 播报场景中存在不合规元素(文本、音色、音频、图像等)
String videoId = createVideoFromTemplate(client, template, stickerId);
// 4. 视频状态轮询
while (true) {
BroadcastVideo video = getBroadcastVideo(client, videoId);
String status = video.getStatus();
if ("SUCCESS".equals(status)) {
System.out.println("视频合成成功:" + JSON.toJSONString(video));
break;
} else if ("ERROR".equals(status)) {
System.out.println("视频合成失败:" + JSON.toJSONString(video));
break;
} else {
System.out.println("视频合成中...");
Thread.sleep(3000);
}
}
}
private static String createBroadcastSticker(Client client, String ossKey, String fileName) throws Exception {
CreateBroadcastStickerRequest request = new CreateBroadcastStickerRequest();
request.setOssKey(ossKey);
request.setFileName(fileName);
return client.createBroadcastSticker(request).getBody().getData().getId();
}
private static List<BroadcastTemplate> listBroadcastTemplates(Client client) throws Exception {
ListBroadcastTemplatesRequest request = new ListBroadcastTemplatesRequest();
request.setPage(1);
request.setSize(10);
List<BroadcastTemplate> templates = client.listBroadcastTemplates(request).getBody().getData();
System.out.println("播报模板列表:" + JSON.toJSONString(templates));
return templates;
}
private static BroadcastTemplate getBroadcastTemplate(Client client, String templateId) throws Exception {
GetBroadcastTemplateRequest request = new GetBroadcastTemplateRequest();
request.setTemplateId(templateId);
BroadcastTemplate template = client.getBroadcastTemplate(request).getBody().getData();
System.out.println("播报模板详情:" + JSON.toJSONString(template));
return template;
}
private static String createVideoFromTemplate(Client client, BroadcastTemplate template, String stickerId) throws Exception {
CreateBroadcastVideoFromTemplateRequest request = new CreateBroadcastVideoFromTemplateRequest();
// 设置播报模板id
request.setTemplateId(template.getId());
// 设置视频名称
request.setName("播报视频合成API测试");
// 设置视频选项
// - fps: 帧率,支持[15, 30],默认:30
// - resolution: 分辨率,支持["720p", "1080p"],默认:720p
// - watermark: 是否添加水印,默认:true
CreateBroadcastVideoFromTemplateRequestVideoOptions videoOptions =
new CreateBroadcastVideoFromTemplateRequestVideoOptions();
videoOptions.setFps(30);
videoOptions.setResolution("720p");
videoOptions.setWatermark(true);
request.setVideoOptions(videoOptions);
// 设置待替换的模板变量,支持替换图片、文本、数字人id、音色id
TemplateVariable imageVar = new TemplateVariable();
imageVar.setName("slide_image");
imageVar.setType("image");
imageVar.setProperties(new JSONObject()
.fluentPut("resourceId", stickerId)
.fluentPut("fit", "contain")
);
TemplateVariable textVar = new TemplateVariable();
textVar.setName("slide_script");
textVar.setType("text");
textVar.setProperties(new JSONObject()
.fluentPut("content", "待替换内容")
);
TemplateVariable avatarVar = new TemplateVariable();
avatarVar.setType("avatar");
avatarVar.setProperties(new JSONObject()
.fluentPut("resourceId", "M1NLv93fW2a3uPT_Rc993QUA")
);
TemplateVariable voiceVar = new TemplateVariable();
voiceVar.setType("voice");
voiceVar.setProperties(new JSONObject()
.fluentPut("resourceId", "M11E_ecrWkRozuIdlDnjzMAA")
);
request.setVariables(List.of(imageVar, textVar, avatarVar, voiceVar));
BroadcastVideo video = client.createBroadcastVideoFromTemplate(request).getBody().getData();
System.out.println("播报视频合成:" + JSON.toJSONString(video));
return video.getId();
}
private static BroadcastVideo getBroadcastVideo(Client client, String videoId) throws Exception {
ListBroadcastVideosByIdRequest request = new ListBroadcastVideosByIdRequest();
request.setVideoIds(List.of(videoId));
List<BroadcastVideo> videos = client.listBroadcastVideosById(request).getBody().getData();
return videos.get(0);
}
private static GetUploadPolicyResponseBodyData upload(Client client, String type, File file) throws Exception {
// 获取上传凭证
GetUploadPolicyRequest request = new GetUploadPolicyRequest();
request.setType(type);
GetUploadPolicyResponseBodyData uploadPolicy = client.getUploadPolicy(request).getBody().getData();
System.out.println("获取上传凭证:" + JSON.toJSONString(uploadPolicy));
// HttpClient上传文件
uploadFile(uploadPolicy, file);
return uploadPolicy;
}
private static void uploadFile(GetUploadPolicyResponseBodyData data, File file) throws IOException {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost uploadFile = new HttpPost("https://" + data.getOssPolicy().getHost());
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
ContentType contentType = ContentType.create("multipart/form-data", Consts.UTF_8);
// 添加OSS所需的表单字段
builder.addTextBody("key", data.getOssKey() + "/" + file.getName(), contentType);
builder.addTextBody("policy", data.getOssPolicy().getPolicy(), contentType);
builder.addTextBody("OSSAccessKeyId", data.getOssPolicy().getAccessId(), contentType);
builder.addTextBody("signature", data.getOssPolicy().getSignature(), contentType);
// 添加文件
builder.addBinaryBody("file", Files.newInputStream(file.toPath()), ContentType.IMAGE_PNG, file.getName());
HttpEntity multipart = builder.build();
uploadFile.setEntity(multipart);
try (CloseableHttpResponse response = httpClient.execute(uploadFile)) {
HttpEntity responseEntity = response.getEntity();
System.out.println("Upload result: " + response.getStatusLine());
if (responseEntity != null) {
String responseString = EntityUtils.toString(responseEntity);
System.out.println("Response content: " + responseString);
}
EntityUtils.consume(responseEntity);
}
}
}
private static Client getInstance() throws Exception {
String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
Config config = new Config();
// noinspection AklessInspection
config.setAccessKeyId(accessKeyId);
// noinspection AklessInspection
config.setAccessKeySecret(accessKeySecret);
config.setEndpoint(ENDPOINT);
return new Client(config);
}
}