有奖捉虫:办公协同&微信生态&物联网文档专题 HOT
本文档是 Web Iframe 集成教程中的第二部分。教程由以下三篇文档组成:
2. 后端集成(本篇)。
请确保已经完前面内容,再开始本篇的学习。

学习目标

本篇介绍 Web Iframe 集成剪辑能力中的后端部分。主要内容是搭建业务后端服务,为业务前端提供如下三个接口:
创建项目:此接口返回 项目 ID 及打开项目时所需要的签名,同时导入待剪辑素材。
导出视频:此接口发起视频导出任务,返回 任务 ID
查看导出结果:此接口用于查询云端视频合成进度,成功后会同时返回视频 URL 及云点播 FileId。
回顾集成架构图,业务后端 的位置如红框所示。



三个接口在业务时序图中的位置如下图所示:



接口约定

本教程中约定,后端暴露的接口形式如下:
后端服务的域名为 api.example.com
以 HTTP GET 方式发起请求,通过 QueryString 传递业务参数。
example.com 域的 Cookie UserId 传递用户 ID(暂时不考虑鉴权)。

创建项目(CreateProject)

接口示例
https://api.example.com/CreateProject
?Name=视频编辑项目
&AspectRatio=16:9
&InitFileIdList=FileId1,FileId2,WatermarkFileID
参数说明
参数
类型
说明
Name
String
项目名称。
AspectRatio
String
项目宽高比,支持 16:99:16 两种。
InitFileIdList
String
项目初始素材,以英文逗号 “,” 分隔。
接口应答
{
"Code": "Success",
"Message": "成功",
"Data": {
"ProjectId": "cmepid_project_id",
"Signature": "signature"
}
}

导出视频(ExportVideo)

接口 URL
https://api.example.com/ExportVideo
?ProjectId=your_project_id
&Definition=10
&Name=视频名称
参数说明
参数
类型
说明
ProjectId
String
项目 ID,填创建项目时返回的项目 ID。
Definition
String
视频剪辑预设配置 ID,用于指定导出视频的参数,详情请参见 导出视频编辑项目 接口说明。
Name
String
导出后的视频名称。
接口应答
{
"Code": "Success",
"Message": "成功",
"Data": {
"TaskId": "125xxxxxxx09-CME-xxxxxx"
}
}

查看导出结果(GetTaskInfo)

接口示例
https://api.example.com/GetTaskInfo
?TaskId=taskId
参数说明
参数
类型
说明
TaskId
String
任务 ID,填发起导出任务后返回的任务 ID。
任务处理中的接口应答
{
"Code": "Success",
"Message": "成功",
"Data": {
"Status": "PROCESSING",
"Progress": 50
}
}
任务处理完成的接口应答
{
"Code": "Success",
"Message": "成功",
"Data":{
"Status": "SUCCESS",
"Progress": 100,
"FileUrl": "http://125xxxxxx1.vod2.myqcloud.com/b64e98acvodcqxxxxx/c20be67852858908xxxxxx/f0.mp4",
"FileId": "FileId"
}
}

步骤1:部署后端服务框架

本教程基于 node.js 以及 koa 框架进行开发(您也可以替换成您熟悉的编程语言与框架)。也可以使用腾讯云轻量应用服务器(Lighthouse)或无服务器云函数(SCF)快速部署,参见:
App 主框架代码如下:
const Koa = require('koa')
const Router = require('koa-router')

const app = new Koa()
const router = new Router()

// 示例请求路由代码,后续会将其用正式路由替换
router.get('/hello', (ctx, next) => {
ctx.body = 'world'
})

app.use(router.routes())
app.listen(80)
业务中涉及腾讯云服务端 API 调用。需要在路由代码之前增加如下代码,初始化腾讯云 API SDK:
注意
建议用户使用子账号密钥 + 环境变量的方式调用 SDK,提高 SDK 使用的安全性。为子账号授权时,请遵循 最小权限指引原则,防止泄漏目标存储桶或对象之外的资源。
如果您一定要使用永久密钥,建议遵循 最小权限指引原则 对永久密钥的权限范围进行限制。
const tencentcloud = require("tencentcloud-sdk-nodejs");
const CmeClient = tencentcloud.cme.v20191029.Client;

// 实际编码中,请填准备工作中创建的智能创作的平台 Id
const config = {
platform: "test",
secretId: "SecretId", // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
secretKey: "SecretKey", // 用户的 SecretKey,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
}

const clientConfig = {
credential: {
// 需要传入准备工作中获取的 SecretId,SecretKey,此处还需注意密钥对的保密
secretId: config.secretId,
secretKey: config.secretKey,
},
region: "",
profile: {
httpProfile: {
// 智能创作 API 接口调用域名,固定
endpoint: "cme.tencentcloudapi.com",
},
}
};


const client = new CmeClient(clientConfig);
部署服务,并访问 http://api.example.com/hello,接口将返回文本 world

步骤2:开发创建项目接口(CreateProject)

主要流程如下:
1. 解析请求,从 Cookie 中获取 UseId。解析请求获得项目名称、项目初始媒体列表等请求参数。
2. 调用智能创作 创建项目 接口创建在线剪辑项目,返回项目 ID。
3. 调用智能创作 在项目中导入媒体 接口,导入媒体素材到项目中。
4. 根据 客户端访问签名 算法生成前端打开项目时所需要的签名。
5. 组装前端应答。
在 App 的路由部分增加如下代码,来支持 CreateProject 路径的路由:
const querystring = require("querystring")
const crypto = require('crypto')

router.get('/CreateProject', async (ctx, next) => {
// 1. 解析请求参数
const userId = ctx.cookies.get('UserId');
const name = ctx.query.Name;
console.log(ctx.query);
const aspectRatio = ctx.query.AspectRatio;
const fileIdList = ctx.query.InitFileIdList.split(',');

// 2. 调用智能创作服务端API创建项目
const createProjectResult = await doCreateProject({
userId,
name,
aspectRatio,
});
if (createProjectResult.Code !== 'Success') {
ctx.body = {
Code: "InternalError",
Message: '服务器内部错误。',
};
return;
}

const projectId = createProjectResult.Data.ProjectId;

// 3. 导入媒体到项目,目前导入媒体到项目失败没有返回失败,业务可根据实际情况重新导入或者直接返回失败并删除已经创建项目,暂时不考虑失败的场景,请业务自行处理
await doImportMediasToProject(projectId, fileIdList);

// 4. 生成签名
const signature = genOpenProjectSign(userId, projectId);

// 5. 应答
ctx.body = {
Code: "Success",
Message: "成功",
Data: {
ProjectId: projectId,
Signature: signature,
}
};
})

// 根据请求参数,组装到智能创作创建项目的参数
function buildCreateProjectParams(params) {
const createProjectReq = new CmeModel.CreateProjectRequest();
// 项目名称
createProjectReq.Name = params.name;
// 项目类别,固定为VIDEO_EDIT
createProjectReq.Category = 'VIDEO_EDIT';
// 视频宽高比,导出视频的宽边与高边的比例,使用接口参数指定的视频导出宽高比。如何视频的宽边为480像素, 高边为720像素,则宽高比为6:9
createProjectReq.AspectRatio = params.aspectRatio;
// 平台,填写准备工作中创建的平台Id
createProjectReq.Platform = config.platform;
// 项目模式,固定为Default
createProjectReq.Mode = 'Default';

// 项目归属,目前仅支持归属个人的项目
const owner = new CmeModel.Entity();
// 用户 Id,使用当前调用的用户Id,用于区分权限。可通过参数指定或者后台指定
owner.Id = params.userId;
// 项目归属类型,目前仅支持个人项目
owner.Type = "PERSON";
createProjectReq.Owner = owner;

return createProjectReq;
}

// 到智能创作创建项目
function doCreateProject(params) {
return new Promise((resolve, reject) => {
client.CreateProject(buildCreateProjectParams(params), (err, response) => {
if (err) {
// 调用失败,打印日志
console.error(err);
reject({
Code: "InternalError",
Message: '系统错误。'
});
} else {
// 成功
console.debug('response', response);
const resp = {
Code: "Success",
Message: "成功",
Data: {
ProjectId: response.ProjectId,
}
};
resolve(resp);
}
});
});
}

// 根据请求参数,组装到智能创作导入媒体到项目中的请求参数
function buildImportMediaToProjectParams(projectId, vodFileId) {
const importMediaToProjectReq = new CmeModel.ImportMediaToProjectRequest();
// 平台,填写准备工作中创建的平台Id
importMediaToProjectReq.Platform = config.platform;
// 项目Id
importMediaToProjectReq.ProjectId = projectId;
// 媒体来源,这里使用云点播文件固定为'VOD',其它方式请参见文档
importMediaToProjectReq.SourceType = 'VOD';
// 云点播FileId
importMediaToProjectReq.VodFileId = vodFileId;
// 预处理参数,如里视频不能在浏览器直接播放(非h264 编辑、格式非mp4/m3u8/webm)需要添加此参数
importMediaToProjectReq.PreProcessDefinition = 10;

return importMediaToProjectReq;
}

// 将媒体导入到智能创作
function doImportMediasToProject(projectId, initFileIdList) {
for (let i = 0; i < initFileIdList.length; ++i) {
// 这里示例中单个文件导出失败没有返回错误,业务可根据实际情况做处理
const vodFileId = initFileIdList[i];
doImportOneMediaToProject(projectId, vodFileId);
}
}

// 将单个点播文件导入到智能创作
function doImportOneMediaToProject(projectId, vodFileId) {
return new Promise((resolve, reject) => {
client.ImportMediaToProject(buildImportMediaToProjectParams(projectId, vodFileId), (err, response) => {
if (err) {
// 请求失败,打印日志
console.error(err, response);
reject({
Code: "InternalError",
Message: '系统错误。'
});
} else {
// 请求正常返回,打印response对象
console.log(response.to_json_string());
resolve({
Code: "Success",
Message: "成功"
});
}
});
});
}

genOpenProjectSign(userId, projectId) {
const current = parseInt((new Date()).getTime() / 1000);
const expired = current + 86400; // 签名有效期:1天,正常有效期从签发到使用时有效即可,建议下发较短有效期的签名

const args = {
// 需要传入准备工作中获取的 SecretId
secretId: config.secretId,
currentTimeStamp: current,
expireTime: expired,
random: Math.round(Math.random() * Math.pow(2, 32)),
platform: config.platform,
projectId,
userId,
action: 'OpenProject',
};

return genSha1Sign(args);
}

function genSha1Sign(args) {
const original = querystring.stringify(args);
const originalBuffer = Buffer.from(original, 'utf8');

const hmac = crypto.createHmac('sha1', config.secretKey);
const hmacBuffer = hmac.update(originalBuffer).digest();

const signature = Buffer.concat([hmacBuffer, originalBuffer]).toString('base64');

return signature
}
注意
为实现项目的用户级隔离(即避免用户 A 创建的项目被用户 B 访问),在调用智能创作 创建项目 接口时,务必明确项目的所有者。具体参见 账号
示例代码中没有对客户端请求的 InitFileIdList 做校验,可能导致用户访问到 VOD 平台中任意一个视频。实际业务中,为避免不必要的访问越权,需要校验 FileId 归属。
创建项目后导入媒体,也可以根据实际情况在创建项目完成后分批导入。

步骤3:开发导出视频编辑项目接口(ExportVideo)

主要流程如下:
1. 从 Cookie 中获取 UseId。解析请求获得项目 ID、剪辑预设配置、导出视频名称等请求参数。
2. 调用智能创作 导出视频编辑项目 接口,发起导出视频任务,返回任务 ID。
3. 组装前端应答。
ExportVideo 路由实现 :
router.get('/ExportVideo', async (ctx, next) => {
// 1. 解析请求参数
const userId = ctx.cookies.get('UserId');
const name = ctx.query.Name;
const projectId = ctx.query.ProjectId;
const definition = ctx.query.Definition;

// 2. 调用智能创作服务端 API 发起导出任务
const resp = await doExportVideoEditProject({
userId,
name,
projectId,
definition,
});

// 4. 应答
ctx.body = resp;
})


// 根据请求参数,组装到智能创作发起导出视频的参数
function buildExportVideoEditProjectParams(params) {
const exportVideoEditProjectReq = new CmeModel.ExportVideoEditProjectRequest();
// 平台,填写准备工作中创建的平台Id
exportVideoEditProjectReq.Platform = config.platform;
// 导出模板 Id,具体请参见接口文档。此处填接口传递给服务端的参数
exportVideoEditProjectReq.Definition = 10;
exportVideoEditProjectReq.ProjectId = params.projectId;
// 导出目标,此处为导出视频到云点播为例,固定填VOD。可以根据官网API文档选择导出到智能创作媒资
exportVideoEditProjectReq.ExportDestination = 'VOD';
const vodExportInfo = new CmeModel.VODExportInfo();
// 导出的视频名称,填接口传递给服务端的参数
if (params.name) {
vodExportInfo.Name = params.name;
} else {
vodExportInfo.Name = 'test';
}
exportVideoEditProjectReq.VODExportInfo = vodExportInfo;
exportVideoEditProjectReq.Operator = params.userId;

return exportVideoEditProjectReq;
}

// 到智能创作发起导出任务
function doExportVideoEditProject(params) {
return new Promise((resolve, reject) => {
client.ExportVideoEditProject(buildExportVideoEditProjectParams(params), (err, response) => {
if (err) {
// 请求异常,打印错误信息
console.error(err);
reject({
Code: "InternalError",
Message: '系统错误。'
});
} else {
// 请求正常返回,打印response对象
console.log(response.to_json_string());
resolve({
Code: "Success",
Message: "成功",
Data: {
TaskId: response.TaskId,
}
});
}
});
});
}

注意
代码中没有对用户 ID 及项目 ID 进行校验,可能存在越权的问题,实际编码中请做好用户 ID 及项目 ID 权限的验证。

步骤4:开发查看导出任务结果接口(GetTaskInfo)

整个接口的主要流程如下:
1. 解析请求,从 Cookie 中获取 UseId。解析请求获得任务 ID 参数。
2. 调用智能创作 获取任务详情 接口,获取任务结果,如果任务完成,返回导出的视频 URL 及点播 FileId。
3. 组装前端应答。
GetTaskInfo 路由实现:
router.get('/GetTaskInfo', async (ctx, next) => {
// 1. 解析请求参数
const userId = ctx.cookies.get('UserId');
const taskId = ctx.query.TaskId;

// 2. 调用智能创作服务端 API 获取导出任务结果
const resp = await doGetTaskInfo({ userId, taskId });

// 3. 应答
ctx.body = resp;
})

// 根据请求参数,组装到智能创作获取导出结果的参数
function buildGetTaskInfoParams(params) {
const describeTaskDetailReq = new CmeModel.DescribeTaskDetailRequest();
// 平台,填写准备工作中创建的平台Id
describeTaskDetailReq.Platform = config.platform;
// 项目Id,填参数传递到服务端的项目Id
describeTaskDetailReq.TaskId = params.taskId;
// 操作者,可通过操作者来鉴权
describeTaskDetailReq.Operator = params.userId;

return describeTaskDetailReq;
}

// 到智能创作获取任务信息
function doGetTaskInfo(params) {
return new Promise((resolve, reject) => {
client.DescribeTaskDetail(buildGetTaskInfoParams(params), (err, response) => {
if (err) {
// 调用失败,打日志
console.log(err);
reject({
Code: "InternalError",
Message: '系统错误。'
});
} else {
console.log(response.to_json_string());

const result = {
Status: response.Status, // 任务状态,只状态为 'SUCCESS' 才算成功
Progress: response.Progress,
ErrorCode: response.ErrCode,
ErrMsg: response.ErrMsg,
};
if (response.VideoEditProjectOutput && response.VideoEditProjectOutput.URL) {
// 导出的视频播放URL
result.FileUrl = response.VideoEditProjectOutput.URL;
// 导出的视频的云点播FileId
result.FileId = response.VideoEditProjectOutput.VodFileId;
}
resolve({
Code: "Success",
Message: "成功",
Data: result
});
}
});
});
}
注意
代码中没有对用户 ID 及任务 ID 进行校验,可能存在越权的问题,实际编码中请做好用户 ID 及任务 ID 权限的验证。

后续步骤

您已完成业务后端的开发,请进入下一章 前端集成