云端短视频批量混剪解决方案

周桂鑫
  • 收获赞:172
  • 擅长领域:阿里云智能视频云技术专家,日常在线,专治各类疑难杂症。擅长领域:智能生产,云剪辑,数据开发,微服务,中间件技术

“批量生产”、“快速裂变”和“去重”是制作营销短视频的关键,基于有限数量的基础素材大规模生成指定数量的新视频,是营销短视频创作的常见思路。本篇介绍使用智能媒体生产ICE创作营销短视频,自由设定脚本顺序模拟手动剪辑,实现自动化批量制作高质量原创视频。

概述

背景

进入5G时代,越来越多的商家选择短视频平台做营销推广,将广告制作成短视频投放在多个KOL或营销号。

由于各大短视频平台有去重机制(防止视频盗用),内容相同的营销视频会被做屏蔽处理,这就要求投放在不同账号的视频,在内容、结构上要有差别。本文从案例入手,介绍如何使用智能媒体生产ICE(Intelligent Cloud Editing,以下简称ICE)将客户的基础素材剪辑出多个内容不同的营销短视频。

目标读者

有短视频批量混剪的商家,或批量混剪工具的开发者。

方案介绍批量混剪流程图

  1. 素材准备:提前针对待营销商品、商户、虚拟服务等准备多段图片/视频素材,并准备相关的营销文案、品牌Logo、背景音乐。

  2. 自定义脚本:对成片结构自定义脚本,如商户探店场景,脚本分组可设置为门头、环境、菜品、优惠介绍。

  3. 选择素材并提交合成:设计素材选择策略(本示例为随机选择),每次选出部分素材,为每段素材添加不同特效,将营销文案转为人声+字幕,并添加Logo、添加背景音乐进行合成。

  4. 重复3,合成多段成片视频。

由于每次选的素材不同,顺序不同,合成出来的视频内容均有较大差别。

效果演示:

本示例使用的视频素材:视频素材多个成片效果:

智能媒体生产ICE还可以对视频进行截取、缩放,添加转场、调色等操作,能对视频做更多处理,达到更大的去重度。

客户设计好混剪策略后,也可以封装出前端页面,通过可视化界面来选择素材,并提交合成任务。以下为批量混剪工具示例,其中的参数设置参见文档最后的示例代码。前端页面

相关概念

时间线Timeline

和iMovie、Premiere一样,一次剪辑会包含多个轨道以及相应素材的位置、时长等信息,ICE使用Timeline来描述这些信息。用户组装好Timeline,调用提交剪辑合成作业接口,即可生产出视频了。

这是一个“多段视频拼接+Logo+文案转语音并生成字幕+背景音乐”的Timeline,Json格式,符合剪辑和开发同学的理解。timeline示例可以看到Timeline里只需描述视频顺序即可,ICE在合成时会获取素材时长,自动推测视频在成片中的位置。

OSS地址

ICE可以直接剪辑对象存储OSS上的音视频文件,Timeline中引用文件的OSS地址即可。OSS地址格式:http://[bucket].oss-[region].aliyuncs.com/[object].mp4。地址中包含了Bucket、Region、Object等信息。

注意

使用的OSS文件和ICE必须在相同region。

视频点播VOD

阿里云视频点播(VOD)是集音视频上传、自动化转码处理、媒体资源管理、分发加速于一体的全链路音视频点播服务。ICE可以直接剪辑视频点播VOD上的音视频文件,Timeline中引用VOD的MediaId即可。

使用VOD MediaId的时间线示例:使用VOD媒资的时间线示例

注意

使用的VOD媒资和ICE必须在相同region。

方案优势

云端剪辑

不同于市面上的C端工具,ICE可以直接剪辑云端的音视频文件,成片输出到云端。用户调用API即可进行剪辑,免去了安装工具的麻烦,且与用户的本机性能无关。

参数简单、灵活、易上手

ICE的Timeline结构符合大众对剪辑工程的理解,ICE还对很多场景做了Timeline简化,用户不用拼完整的Timeline参数,由ICE进行补全。除了简单的拼接、多轨合成外,ICE还可以对视频进行剪裁、变速、ASR、TTS等多种复杂操作,用户可以实现更复杂的剪辑场景。

高性能、低成本

智能媒体生产ICE依托于媒体处理服务MPS,可以从容应对高并发、大文件、高实时性的业务场景,ICE依据成片时长计费,用户即用即取,且能保证高性能和高可用性。

方案实施

前提条件

  • 开通ICE服务。

  • 开通OSS服务或VOD服务。

接口说明

输入Timeline和成片地址,提交剪辑合成作业,返回剪辑作业JobId。

根据JobId获取剪辑合成作业信息,检查任务状态。

示例代码

/**
Maven引入:

<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>aliyun-java-sdk-core</artifactId>
    <version>4.5.3</version>
</dependency>
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>ice20201109</artifactId>
    <version>1.1.5</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.68.noneautotype</version>
</dependency>
*/
package com.aliyun.ice.util;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.abs.common.util.MD5Util;
import com.aliyun.ice20201109.models.GetMediaProducingJobRequest;
import com.aliyun.ice20201109.models.GetMediaProducingJobResponse;
import com.aliyun.ice20201109.models.SubmitMediaProducingJobRequest;
import com.aliyun.ice20201109.models.SubmitMediaProducingJobResponse;
import com.aliyun.teaopenapi.models.Config;
import org.apache.logging.log4j.util.Strings;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class BatchProduceVideo {

    private String accessKeyId;
    private String accessKeySecret;
    private String bucket;
    private String region;
    private com.aliyun.ice20201109.Client iceClient;

    public void initClient() throws Exception {
        accessKeyId = "your_ak";
        accessKeySecret = "your_sk";
        bucket = "your-bucket";
        region = "cn-shanghai";
        iceClient = createIceClient();
    }

    public static void main(String[] args) throws Exception {
        BatchProduceVideo_release batchProduceVideo = new BatchProduceVideo_release();
        batchProduceVideo.initClient();
        batchProduceVideo.batchProduceVideo();
    }

    public void batchProduceVideo() throws Exception {

        // 文字素材
        String text = "测试文案";

        // 视频素材
        List<String> videoUrlList = new ArrayList();
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video1.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video2.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video3.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video4.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video5.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video6.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video7.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video8.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video9.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video10.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video11.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video12.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video13.mp4");
        videoUrlList.add("https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video14.mp4");

        // 背景音乐
        String bgMusic = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-music.mp3";

        // 字幕样式设置
        Integer fontSize = 60;
        String fontName = "WenQuanYi Zen Hei Mono";
        String fontColor = "#FFFFFF";

        // 视频尺寸
        Integer width = 1280;
        Integer height = 720;

        // logo
        String logoUrl = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-logo.png";
        Integer logoX = 20;
        Integer logoY = 20;

        // 输出成片个数
        Integer outputCount = 2;

        // 每个成片包含视频片段数
        Integer videoCount = 8;

        for (int i = 0; i < outputCount; i++) {
            Collections.shuffle(videoUrlList);
            List<String> subUrls = videoUrlList.subList(0, videoCount);
            produceSingleVideo(text, subUrls, bgMusic, fontSize, fontName, fontColor, logoUrl, logoX, logoY, width, height);
        }
    }

    // 提交单个任务
    public void produceSingleVideo(String text, List<String> videoUrls, String bgMusic, int fontSize, String fontName, String fontColor,
                                   String logoUrl, Integer logoX, Integer logoY, int width, int height) throws Exception {
        // 组装字幕轨
        if (fontSize <= 0) {
            fontSize = 32;
        }
        if (Strings.isBlank(fontName)) {
            fontName = "WenQuanYi Zen Hei Mono";
        }
        if (Strings.isBlank(fontColor)) {
            fontColor = "#000000";
        }

        // 组装视频轨
        JSONArray videoTrackClips = new JSONArray();
        for (String url : videoUrls) {
            JSONObject clip = new JSONObject();
            clip.put("MediaURL", url);
            videoTrackClips.add(clip);
        }

        // 组装音频轨
        String audioClip = "{\"AudioTrackClips\":[{\"Type\":\"AI_TTS\",\"Content\":\"" + text + "\",\"Voice\":\"sicheng\",\"Effects\":[{\"Type\":\"AI_ASR\",\"Font\":\"" + fontName + "\",\"Alignment\":\"BottomCenter\",\"Y\":0.1,\"FontSize\":" + fontSize + ",\"FontColor\":\"" + fontColor + "\",\"FontFace\":{\"Bold\":true,\"Italic\":false,\"Underline\":false}},{\"Type\":\"Volume\",\"Gain\":10}]}]}";
        String audioTracks;
        if (Strings.isBlank(bgMusic)) {
            audioTracks = "["+audioClip+"]";
        } else {
            // 两个音频轨,一个人声,一个音乐
            audioTracks = "["+audioClip+",{\"AudioTrackClips\":[{\"MediaURL\":\"" + bgMusic + "\"}]}]";
        }

        // 贴图clips
        String logoClip = "";
        if (Strings.isNotBlank(logoUrl)) {
            logoClip = "{\"ImageURL\":\"" + logoUrl + "\",\"X\":" + logoX + ",\"Y\":" + logoY + "}";
        }

        // 拼时间线
        String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":" + videoTrackClips.toJSONString() + "}]," +
                "\"AudioTracks\":" + audioTracks + "," +
                "\"ImageTracks\":[{\"ImageTrackClips\":[" + logoClip + "]}]}";
        System.out.println("timeline : " + timeline);

        // 提交合成任务
        SubmitMediaProducingJobRequest submitMediaProducingJobRequest = new SubmitMediaProducingJobRequest();
        submitMediaProducingJobRequest.setTimeline(timeline);
        String mediaURL = "https://" + bucket + ".oss-" + region + ".aliyuncs.com/" + MD5Util.getMd5String(String.valueOf(Math.random())) + ".mp4";
        submitMediaProducingJobRequest.setOutputMediaConfig("{\"MediaURL\":\"" + mediaURL + "\",\"Width\":" + width + ",\"Height\":" + height + "}");
        SubmitMediaProducingJobResponse submitMediaProducingJobResponse = iceClient.submitMediaProducingJob(submitMediaProducingJobRequest);
        System.out.println("job created, jobId : " + submitMediaProducingJobResponse.body.jobId + ", requestId : " + submitMediaProducingJobResponse.body.getRequestId() + ", mediaURL : " + mediaURL);
        // 等待合成任务完成
        while (true) {
            GetMediaProducingJobRequest getMediaProducingJobRequest = new GetMediaProducingJobRequest();
            getMediaProducingJobRequest.setJobId(submitMediaProducingJobResponse.body.jobId);
            GetMediaProducingJobResponse getMediaProducingJobResponse = iceClient.getMediaProducingJob(getMediaProducingJobRequest);
            System.out.println("GetMediaProducingJobResponse : " + JSONObject.toJSONString(getMediaProducingJobResponse.body));
            String status = getMediaProducingJobResponse.getBody().getMediaProducingJob().getStatus();
            if ("Success".equals(status) || "Failed".equals(status)) {
                break;
            }
            Thread.sleep(5000);
        }
        System.out.println("Produce succeed : " + mediaURL);
    }

    public com.aliyun.ice20201109.Client createIceClient() throws Exception {
        Config config = new Config()
                .setAccessKeyId(accessKeyId)
                .setAccessKeySecret(accessKeySecret);
        config.endpoint = "ice." + region + ".aliyuncs.com";
        return new com.aliyun.ice20201109.Client(config);
    }
}
<?php
/**
composer 引入:
{
    "require": {
        "alibabacloud/ice-20201109": "1.1.6",
        "aliyuncs/oss-sdk-php": "^2.4"
    }
}
*/


require_once '../vendor/autoload.php';

use AlibabaCloud\SDK\ICE\V20201109\ICE;
use AlibabaCloud\SDK\ICE\V20201109\Models;
use AlibabaCloud\SDK\ICE\V20201109\Models\SubmitMediaProducingJobRequest;
use AlibabaCloud\SDK\ICE\V20201109\Models\GetMediaProducingJobRequest;

use Darabonba\OpenApi\Models\Config;

class BatchProduceVideo {
    var $iceClient;
    var $bucket;
    var $region;

    function createClient() {
        $this->bucket = "your-bucket";
        $this->region = "cn-shanghai";

        $accessKeyId = "your_ak";
        $accessKeySecret = "your_sk";

        // ice
        $iceConfig = new Config();
        $iceConfig->accessKeyId = $accessKeyId;
        $iceConfig->accessKeySecret = $accessKeySecret;
        $iceConfig->regionId = "cn-shanghai";
        $iceConfig->endpoint = "ice.cn-shanghai.aliyuncs.com";
        $this->iceClient = new ICE($iceConfig);
    }

    function run() {
        echo "Start produce...\n";
        // 初始化client
        $this->createClient();

        // 文字素材
        $text = "测试文案";
        
        // 视频素材
        $videoUrls = array();
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video1.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video2.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video3.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video4.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video5.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video6.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video7.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video8.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video9.mp4";
        $videoUrls[] = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-video10.mp4";

        // 背景音乐
        $bgMusic = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/test_media/your-music.mp3";

        // 字幕样式设置
        $fontSize = 60;
        $fontName = "WenQuanYi Zen Hei Mono";
        $fontColor = "#FFFFFF";

        // logo
        $logoUrl = "https://your-bucket.oss-cn-shanghai.aliyuncs.com/your-logo.png";
        $logoX = 20;
        $logoY = 20;

        // 视频尺寸
        $width = 1920;
        $height = 1080;

        // 输出成片个数
        $outputCount = 2;

        // 每个成片包含视频片段数
        $videoCount = 8;

        for ($i = 0; $i < $outputCount; $i++) { 
            shuffle($videoUrls);
            $subUrls = array_slice($videoUrls, 0, $videoCount);
            $this->produceSingleVideo($text, $subUrls, $bgMusic, $fontSize, $fontName, $fontColor, $logoUrl, $logoX, $logoY, $width, $height);
        }
    }

    // 提交单个任务
    function produceSingleVideo($text, $videoUrls, $bgMusic, $fontSize, $fontName, $fontColor, $logoUrl, $logoX, $logoY, $width, $height) {
        $bucket = $this->bucket;
        $region = $this->region;

        // 组装字幕轨
        if ($fontSize <= 0) {
            $fontSize = 32;
        }
        if ($fontName == "") {
            $fontName = "WenQuanYi Zen Hei Mono";
        }
        if ($fontColor == "") {
            $fontColor = "#000000";
        }

        // 组装视频轨
        $videoTrackClips = array();
        foreach ($videoUrls as $url) {
            $videoTrackClips[] = array('MediaURL' => $url);
        }
        var_dump(json_encode($videoTrackClips));

        // 组装音频轨
        $audioClip = "{\"AudioTrackClips\":[{\"Type\":\"AI_TTS\",\"Content\":\"" . $text . "\",\"Voice\":\"sicheng\",\"Effects\":[{\"Type\":\"AI_ASR\",\"Font\":\"" . $fontName . "\",\"Alignment\":\"BottomCenter\",\"Y\":0.1,\"FontSize\":" . $fontSize . ",\"FontColor\":\"" . $fontColor . "\",\"FontFace\":{\"Bold\":true,\"Italic\":false,\"Underline\":false}},{\"Type\":\"Volume\",\"Gain\":10}]}]}";
        $audioTracks;
        if ($bgMusic == "") {
            $audioTracks = "[" . $audioClip . "]";
        } else {
            // 两个音频轨,一个人声,一个音乐
            $audioTracks = "[" . $audioClip . ",{\"AudioTrackClips\":[{\"MediaURL\":\"" . $bgMusic . "\"}]}]";
        }
        var_dump($audioTracks);

        // 贴图clips
        $logoClip = "";
        if ($logoUrl != "") {
            $logoClip = "{\"ImageURL\":\"" . $logoUrl . "\",\"X\":" . $logoX . ",\"Y\":" . $logoY . "}";
        }
        var_dump($logoClip);

        // 拼时间线
        $timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":" . json_encode($videoTrackClips) . "}]," .
                "\"AudioTracks\":" . $audioTracks . "," .
                "\"ImageTracks\":[{\"ImageTrackClips\":[" . $logoClip . "]}]}";
        echo $timeline."\n";

        // 提交合成任务
        $submitMediaProducingJobRequest = new SubmitMediaProducingJobRequest();
        $submitMediaProducingJobRequest->timeline = $timeline;

        $mediaURL = "https://" . $bucket . ".oss-" . $region . ".aliyuncs.com/" . md5(rand()) . ".mp4";
        $submitMediaProducingJobRequest->outputMediaConfig = "{\"MediaURL\":\"" . $mediaURL . "\",\"Width\":" . $width . ",\"Height\":" . $height . "}";
        $submitMediaProducingJobResponse = $this->iceClient->submitMediaProducingJob($submitMediaProducingJobRequest);
        $jobId = $submitMediaProducingJobResponse->body->jobId;
        $resuestId = $submitMediaProducingJobResponse->body->requestId;
        echo "jobId : ". $jobId . ", resuestId : " . $resuestId . ", mediaURL : " . $mediaURL . "\n";

        // 等待合成任务完成
        while (true) {
            $getMediaProducingJobRequest = new GetMediaProducingJobRequest();
            $getMediaProducingJobRequest->jobId = $jobId;
            $getMediaProducingJobResponse = $this->iceClient->getMediaProducingJob($getMediaProducingJobRequest);
            $status = $getMediaProducingJobResponse->body->mediaProducingJob->status;
            echo "job Status : " . $status . "\n";
            if ("Success" == $status) {
                break;
            }
            sleep(5);
        }
        echo "job finished !";
    }
}

try {
    $batchProduceVideo = new BatchProduceVideo();
    $batchProduceVideo->run();
} catch (Error $e) {
    var_dump($e);
}

欢迎访问官方文档查看更多示例:

欢迎加入智能媒体生产ICE官方答疑群咨询交流。

答疑群二维码