1. 概述
1.1 背景
进入5G时代,越来越多的商家选择短视频平台做营销推广,将广告制作成短视频投放在多个KOL或营销号。
由于各大短视频平台有去重机制(防止视频盗用),内容相同的营销视频会被做屏蔽处理,这就要求投放在不同账号的视频,在内容、结构上要有差别。本文从案例入手,介绍如何使用智能媒体生产ICE(Intelligent Cloud Editing,以下简称ICE)将客户的基础素材剪辑出多个内容不同的营销短视频。
1.2 目标读者
有短视频批量混剪的商家,或批量混剪工具的开发者。
1.3 方案介绍
素材准备:提前针对待营销商品、商户、虚拟服务等准备多段图片/视频素材,并准备相关的营销文案、品牌Logo、背景音乐。
自定义脚本:对成片结构自定义脚本,如商户探店场景,脚本分组可设置为门头、环境、菜品、优惠介绍。
选择素材并提交合成:设计素材选择策略(本示例为随机选择),每次选出部分素材,为每段素材添加不同特效,将营销文案转为人声+字幕,并添加Logo、添加背景音乐进行合成。
重复3,合成多段成片视频。
由于每次选的素材不同,顺序不同,合成出来的视频内容均有较大差别。
效果演示:
本示例使用的视频素材:
多个成片效果:
智能媒体生产ICE还可以对视频进行截取、缩放,添加转场、调色等操作,能对视频做更多处理,达到更大的去重度。
客户设计好混剪策略后,也可以封装出前端页面,通过可视化界面来选择素材,并提交合成任务。以下为批量混剪工具示例,其中的参数设置参见文档最后的示例代码。
1.4 相关概念
时间线Timeline
和iMovie、Premiere一样,一次剪辑会包含多个轨道以及相应素材的位置、时长等信息,ICE使用Timeline来描述这些信息。用户组装好Timeline,调用提交剪辑合成作业接口,即可生产出视频了。
这是一个“多段视频拼接+Logo+文案转语音并生成字幕+背景音乐”的Timeline,Json格式,符合剪辑和开发同学的理解。
可以看到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媒资和ICE必须在相同region。
1.5 方案优势
云端剪辑
不同于市面上的C端工具,ICE可以直接剪辑云端的音视频文件,成片输出到云端。用户调用API即可进行剪辑,免去了安装工具的麻烦,且与用户的本机性能无关。
参数简单、灵活、易上手
ICE的Timeline结构符合大众对剪辑工程的理解,ICE还对很多场景做了Timeline简化,用户不用拼完整的Timeline参数,由ICE进行补全。除了简单的拼接、多轨合成外,ICE还可以对视频进行剪裁、变速、ASR、TTS等多种复杂操作,用户可以实现更复杂的剪辑场景。
高性能、低成本
智能媒体生产ICE依托于媒体处理服务MPS,可以从容应对高并发、大文件、高实时性的业务场景,ICE依据成片时长计费,用户即用即取,且能保证高性能和高可用性。
2. 方案实施
2.1 前提条件
开通ICE服务。
开通OSS服务或VOD服务。
2.2 接口说明
输入Timeline和成片地址,提交剪辑合成作业,返回剪辑作业JobId。
根据JobId获取剪辑合成作业信息,检查任务状态。
2.3 示例代码
Java
/**
Maven引入:
com.aliyun
aliyun-java-sdk-core
4.5.3
com.aliyun
ice20201109
1.1.5
com.alibaba
fastjson
1.2.68.noneautotype
*/
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 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 subUrls = videoUrlList.subList(0, videoCount);
produceSingleVideo(text, subUrls, bgMusic, fontSize, fontName, fontColor, logoUrl, logoX, logoY, width, height);
}
}
// 提交单个任务
public void produceSingleVideo(String text, List 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
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官方答疑群咨询交流。