本文档是 Web Iframe 集成教程中的第二部分。教程由以下三篇文档组成:
1. 准备工作。
2. 后端集成(本篇)。
3. 前端集成。
请确保已经完前面内容,再开始本篇的学习。
学习目标
本篇介绍 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:9 或 9: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 | |
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:部署后端服务框架
Lighthouse:搭建 Node.js 开发环境。
SCF:快速部署 Koa 框架。
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;// 实际编码中,请填准备工作中创建的智能创作的平台 Idconst config = {platform: "test",secretId: "SecretId", // 用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140secretKey: "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_EDITcreateProjectReq.Category = 'VIDEO_EDIT';// 视频宽高比,导出视频的宽边与高边的比例,使用接口参数指定的视频导出宽高比。如何视频的宽边为480像素, 高边为720像素,则宽高比为6:9createProjectReq.AspectRatio = params.aspectRatio;// 平台,填写准备工作中创建的平台IdcreateProjectReq.Platform = config.platform;// 项目模式,固定为DefaultcreateProjectReq.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();// 平台,填写准备工作中创建的平台IdimportMediaToProjectReq.Platform = config.platform;// 项目IdimportMediaToProjectReq.ProjectId = projectId;// 媒体来源,这里使用云点播文件固定为'VOD',其它方式请参见文档importMediaToProjectReq.SourceType = 'VOD';// 云点播FileIdimportMediaToProjectReq.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 = {// 需要传入准备工作中获取的 SecretIdsecretId: 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}
注意
示例代码中没有对客户端请求的
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();// 平台,填写准备工作中创建的平台IdexportVideoEditProjectReq.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();// 平台,填写准备工作中创建的平台IddescribeTaskDetailReq.Platform = config.platform;// 项目Id,填参数传递到服务端的项目IddescribeTaskDetailReq.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) {// 导出的视频播放URLresult.FileUrl = response.VideoEditProjectOutput.URL;// 导出的视频的云点播FileIdresult.FileId = response.VideoEditProjectOutput.VodFileId;}resolve({Code: "Success",Message: "成功",Data: result});}});});}
注意
代码中没有对用户 ID 及任务 ID 进行校验,可能存在越权的问题,实际编码中请做好用户 ID 及任务 ID 权限的验证。