智能生产制作提供专业在线的普通模板剪辑能力,针对视频制作中重复性内容和定制美化需求,您可以通过自定义模板和素材替换,实现批量化视频生产。通过阅读本文,您可以了解如何接入普通模板剪辑Web SDK。

接入说明

普通模板是基于视频剪辑工程创建的,因此基于普通模板的视频剪辑是集成在视频剪辑Web SDK中。如果您需要接入普通模板剪辑,只需要接入视频剪辑Web SDK,并在初始化中传入参数mode。关于接入及初始化视频剪辑Web SDK,请参见接入视频剪辑Web SDK

window.AliyunVideoEditor.init({
  mode: 'template'
  ......
});

config属性说明

由于普通模板是基于视频剪辑工程创建的,因此接入视频剪辑Web SDK中的config属性也同样适用于普通模板剪辑,详情请参见config属性说明。除此之外,基于普通模板剪辑的config属性中还额外增加了以下参数:

参数 类型 必填 描述 引入版本
updateTemplate (data: {coverUrl: string; duration: number; timeline: Timeline; isAuto: boolean}) => Promise<{projectId: string}>; 编辑普通模板界面右上角保存模版按钮对应的参数,作用为保存模版的时间线,参数依次为:工程的封面图地址、时长(单位:秒)、Timeline数据和是否自动保存(当前每分钟自动保存1次)。返回的Promise对象需要resolve项目ID。 3.7.0

init()示例代码

注意 Web SDK只负责界面交互,不会发起请求,您需要通过Web SDK调用请求逻辑。请求本身应该先发送给您自己的服务端,您自己的服务端再根据AccessKey信息(AccessKey ID和AccessKey Secret)转发给相关的阿里云OpenAPI。
// 注意,WebSDK 本身并不提供 request 这个方法,这里仅作为示例,您可以使用您喜欢的网络请求库,如 axios 等

window.AliyunVideoEditor.init({
    container: document.getElementById('aliyun-video-editor'),
    locale: 'zh-CN',
    mode: 'template', // 开启普通模版编辑模式
    getEditingProjectMaterials: () => {
      if (templateId) {
        // templateId 由调用方自己保存

        // 相关接口文档 https://help.aliyun.com/document_detail/277452.html
        return request('GetTemplate', {
          TemplateId: templateId,
          RelatedMediaidFlag: 1 // 加上这个参数,会返回相应的用于绑定媒资的字段
        })
          .then(res => {
            const { RelatedMediaids } = res.data.Template;
            const MediaIds = Object.values(JSON.parse(RelatedMediaids)).reduce(
              (acc, cur) => acc.concat(cur),
              []
            );

            // 批量获取媒资ID对应的资源
            // 用 GetMediaInfo 逐个媒资单个查询 https://help.aliyun.com/document_detail/197842.htm
            return request('BatchGetMediaInfos', {
              MediaIds,
              AdditionType: 'FileInfo'
            });
          })
          .then(res => {
            return transMediaList(res.data.MediaInfos); // 需要做一些数据变换,具体参考后文
          });
      }
      return Promise.resolve([]);
    },
    searchMedia: mediaType => {
      // mediaType 为用户当前所在的素材 tab,可能为 video | audio | image,您可以根据这个参数对应地展示同类型的可添加素材
      return new Promise(resolve => {
        // 调用方需要自己实现展示媒资、选择媒资添加的界面,这里的 callDialog 只是一种示例,WebSDK 本身并不提供
        // 关于展示媒资,请参考:https://help.aliyun.com/document_detail/197964.html
        callDialog({
          onSubmit: async materials => {
            // 这里的 materials 是 ListMediaBasicInfos 接口获取的媒资列表,并经过 transMediaList 转换

            // 相关接口文档 https://help.aliyun.com/document_detail/277452.html
            const res = await request('GetTemplate', {
              TemplateId: templateId, // templateId 由调用方自己保存
              RelatedMediaidFlag: 1 // 加上这个参数,会返回相应的用于绑定媒资的字段
            });

            const { Template } = res.data;
            const MediaIdsMap = JSON.parse(Template.RelatedMediaids); // 解析出当前已绑定的媒资

            // 将选中的媒资进行添加
            materials.forEach(({ mediaType: type, mediaId }) => {
              if (!MediaIdsMap[type]) {
                MediaIdsMap[type] = [];
              }

              if (!MediaIdsMap[type].includes(mediaId)) {
                MediaIdsMap[type].push(mediaId);
              }
            });

            // 保存更新的媒资
            // 相关接口文档 https://help.aliyun.com/document_detail/340673.html
            await request('UpdateTemplate', {
              TemplateId: templateId, // templateId 由调用方自己保存
              RelatedMediaids: JSON.stringify(MediaIdsMap)
            });

            resolve(materials);
          }
        });
      });
    },
    deleteEditingProjectMaterials: async (mediaId, mediaType) => {
      // 相关接口文档 https://help.aliyun.com/document_detail/277452.html
      const res = await request('GetTemplate', {
        TemplateId: templateId, // templateId 由调用方自己保存
        RelatedMediaidFlag: 1 // 加上这个参数,会返回相应的用于绑定媒资的字段
      });

      const { Template } = res.data;
      const MediaIdsMap = JSON.parse(Template.RelatedMediaids); // 解析出当前已绑定的媒资

      // 剔除要删除的媒资
      if (MediaIdsMap[mediaType] && MediaIdsMap[mediaType].includes(mediaId)) {
        MediaIdsMap[mediaType].splice(MediaIdsMap[mediaType].indexOf(mediaId), 1);

        // 保存更新的媒资
        // 相关接口文档 https://help.aliyun.com/document_detail/340673.html
        await request('UpdateTemplate', {
          TemplateId: templateId, // templateId 由调用方自己保存
          RelatedMediaids: JSON.stringify(MediaIdsMap)
        });

        return true;
      }

      return false;
    },
    getEditingProject: async () => {
      // 相关接口文档 https://help.aliyun.com/document_detail/277452.html
      const res = await request('GetTemplate', {
        TemplateId: templateId // templateId 由调用方自己保存
      });

      const config = res.data.Template.Config;
      return {
        timeline: config ? JSON.parse(Config) : undefined
      };
    },
    updateTemplate: async ({ coverUrl, timeline, isAuto }) => {
      const updateParams = {
        TemplateId: templateId, // templateId 由调用方自己保存
        CoverURL: coverUrl,
        Config: JSON.stringify(timeline)
      };

      // 保存更新
      // 相关接口文档 https://help.aliyun.com/document_detail/340673.html
      await request('UpdateTemplate', updateParams);
    }

    // updateEditingProject 模版模式下不会使用到该参数,不需要传入
    // produceEditingProjectVideo 模版模式下不会使用到该参数,不需要传入
  });

  /**
   * 将服务端的素材信息转换成 WebSDK 需要的格式
   */
  function transMediaList(data) {
    if (!data) return [];

    if (Array.isArray(data)) {
      return data.map(item => {
        const basicInfo = item.MediaBasicInfo;
        const fileBasicInfo = item.FileInfoList[0].FileBasicInfo;
        const mediaId = basicInfo.MediaId;
        const result = {
          mediaId
        };
        const mediaType = basicInfo.MediaType;
        result.mediaType = mediaType;

        if (mediaType === 'video') {
          result.video = {
            title: fileBasicInfo.FileName,
            duration: Number(fileBasicInfo.Duration),
            // 源视频的宽高、码率等数据,用于推荐合成数据,不传入或是0时无推荐数据
            width: Number(fileBasicInfo.Width) || 0,
            height: Number(fileBasicInfo.Height) || 0,
            bitrate: Number(fileBasicInfo.Bitrate) || 0,
            coverUrl: basicInfo.CoverURL
          };
          const spriteImages = basicInfo.SpriteImages;
          if (spriteImages) {
            try {
              const spriteArr = JSON.parse(spriteImages);
              const sprite = spriteArr[0];
              const config = JSON.parse(sprite.Config);
              result.video.spriteConfig = {
                num: config.Num,
                lines: config.SpriteSnapshotConfig?.Lines,
                cols: config.SpriteSnapshotConfig?.Columns,
                cellWidth: config.SpriteSnapshotConfig?.CellWidth,
                cellHeight: config.SpriteSnapshotConfig?.CellHeight
              };
              result.video.sprites = sprite.SnapshotUrlList;
            } catch (e) {
              console.log(e);
            }
          }
        } else if (mediaType === 'audio') {
          result.audio = {
            title: fileBasicInfo.FileName,
            duration: Number(fileBasicInfo.Duration),
            coverURL: '' // 您可以给音频文件一个默认的封面图
          };
        } else if (mediaType === 'image') {
          result.image = {
            title: fileBasicInfo.FileName,
            coverUrl: fileBasicInfo.FileUrl,
            // 图片的宽高等数据,用于推荐合成数据,不传入或是0时无推荐数据
            width: Number(fileBasicInfo.Width) || 0,
            height: Number(fileBasicInfo.Height) || 0
          };
        }

        return result;
      });
    } else {
      return [data];
    }
  }