大家好,我是灿灿, 专注于AI智能体应用,今天拆解一个情感类混剪视频案例。
话不多说,先看视频效果。整体上来看,效果还是不错的。
思路拆解:混剪顾名思义就是把同类型不同的视频片段组合拼接,那我们可以直接找相同等时间的视频片段,然后再编写代码调整视频片段顺序。视频片段个数根据文案音频时间除以单个视频片段时间来算。
步骤拆解:
- 输入情感视频文案
这里我们使用选择器节点和变量聚合节点做个兼容处理,文案可手动输入也可用大模型生成。
- 并行处理视频文案和音频生成
看到这里,有些小伙伴可能会有疑问,视频的时间线是以音频时间线为准,但是文案的时间线就可能和音频时间线对不齐,是的,所以我们需要使用一个字幕音频对齐插件来使得他们对齐。
我们先看字幕音频对齐插件所需要的参数,第一个是api_token,速推的api_key,第二个是audio_url, 音频链接,第三个就是text文案,使用\n符号进行拼接。
音频链接使用音频合成就行,文本就先使用文本处理节点根据逗号进行文案的分割,组成数组,再使用\n符号进行拼接即可。
这里要注意的是,生成音频的时候,需要再获取音频时长,因为视频的组合是需要用到这个的。
- 视频片段组合
前面的准备工作做好了,下面来到了最关键的环节,视频片段组合,代码的话我已经贴出来了,接下来我讲下思路。这里的每个视频片段是3s,使用音频总时长整除3并向下取整就能得到需要的视频片段数量,如果音频总时长不是3的倍数,视频片段就需要加1。然后再随机从一组视频链接里提取算出的视频片段个数。
代码的话有编程基础的同学可以自己写,没有的话就描述清楚让ai写。
import random
import math
async def main(args: Args) -> Output:
params = args.params
#每个视频片段的时长
clip_duration = 3
total_duration = math.ceil(params['duration'])
# 计算需要的视频片段数量
num = total_duration // clip_duration
# 如果总时长不能被片段时长整除,需要额外处理
if total_duration % clip_duration != 0:
num += 1
video_urls = get_random_videos("",num)
# 构建输出对象
ret: Output = {
"video_urls": video_urls,
}
return ret
def get_random_videos(style,num):
#"情感","夜景","行人"
video_urls = [
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/46c2177de7307de206fe4ac7ffc43fa72510af28.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/8fd9fcf4947b37b8b4d42dab6a061756d5bc56b1.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/a2e38c05895ccb9d8e8116b77af8277691097663.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/6a5e35c8b095bee0a57a9911d5f8b2d5ec8b686d.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/bd993229ac40d88b607f9e1a4b4e83857915a06f.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/93bbbd5dd0d0f2cd61d22c0877966d38b1df7a2b.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/86a4e6507f036a8dc7c56581888f68d087abdc4a.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/5c84153c21b81c42eb5a1879a3bcce5045398e2b.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/b102f211c5a9b7617a567d6b7d57c9bb3a5dc1ed.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/8da435f114353b1b2e53d9554d05eadd88f88480.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/32fb3cdbaae92d3652a6d4061d27ef4c49d81c59.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/ee085c6785fa4fc01bcb9317bf8cda63ecbf26e2.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/4795c5903cb746c1e950ec9e3c6b590c5a57d8d5.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/b7cbd06a5011199b2d074685a5901c5b417eac71.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/0cbc467c456ca33b3af48526add4535219e1cf9b.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/c51b33546bdf8e09390b5085c12c6cc64050f2b7.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/0d782370cbb0c5e879d6106d71db55d4abb51b70.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/76a4a8bb37c7eb761582e612070eaf6ddf21811e.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/b542654b9c151fa515c0fca36304d3b35b9127c3.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/3d970d2d1df6a302c26df44f27525fd7e4c038c9.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/50a82576bb72a92d88424b4b7ed5f3ddb1a6b567.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/e0b93bf86ed284f06ba2b1ac799f443b0249db7f.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/f0e3a4cd3de0f8e341c503d4212df6274f377b8b.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/ae9fcc18e8bba64a73820a3e8c5ec32cb0e5d9a8.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/be0921849286abfa4ac0865ab1b458d7944e0b80.mp4",
"https://befun-static.oss-cn-shenzhen.aliyuncs.com/clip/material/538e40294e80875f08f3a7e63cbc778879c3b5b3.mp4",]
# 随机抽取所需的视频片段
return random.sample(video_urls, num)
- 字幕、音频、视频数据处理
字幕、音频、视频都获取到了,接下来就主要是处理它们的时间线,保证时间线对齐。
这里我讲下代码具体是做了什么,首先根据获取的视频数据,循环设置视频片段为3s的时间线,最后一段特殊一点,因为最后一段可能除不尽,最后一段的结束时间和时长都是音频的结束时间。然后就是背景音乐和音频时间线就是以音频的开始时间和结束时间为准。再就是字幕是根据字幕与音频插件对齐生成的时间线来生成,字幕标题就设置了开头的两秒。最后就是数据输出了。
import json
async def main(args: Args) -> Output:
params = args.params
bgm = params['bgm']
texts = params['texts']
#视频
video_start = 0
video_end = 0
videos = []
length = len(params['vedio_urls'])
for item in params['vedio_urls']:
video_end = video_start + 3*1000000
videos.append({
"video_url": item,
"duration": 3*1000000,
"start": video_start,
"end": video_end,
"width":576,
"height":1024,
"transition": "叠化",
"transition_duration": 1000000
})
if video_end > video_start:
video_start = video_end
videos[length - 1]['end'] = params['duration']*1000000
videos[length - 1]['duration'] = params['duration']*1000000
# 背景音乐
audioBgm = [{
"audio_url": bgm,
"start": 0,
"end": params['duration']*1000000
}]
# audioBgm.append()
#配音、字幕
start = 0
end = 0
captions = []
audios = []
audios.append({
"audio_url": params['audioUrl'],
"duration": params['duration']*1000000,
"start": 0,
"end": params['duration']*1000000
})
timelines = params['timelines']
for idx,item in enumerate(timelines):
start = item['start']
end = item['end']
text = texts[idx]
captions.append({
'text': text,
'start': start,
'end': end,
"in_animation":"渐显",
"out_animation":"渐隐"
})
captionsTitle = [{
'text': params['title'],
'start': 0,
'end': 2000000,
"in_animation":"",
"out_animation":"渐隐"
}]
# 构建输出对象
ret: Output = {
"captions": json.dumps(captions),
"audios": json.dumps(audios),
"videos": json.dumps(videos),
"audioBgm": json.dumps(audioBgm),
"captionsTitle": json.dumps(captionsTitle)
}
return ret
- 把字幕、音频、视频数据添加到剪映草稿
前面把数据都处理好了,剩下的就是把数据添加到剪映草稿了。由于是常规操作,我就不贴具体的节点了,不太熟练的同学可以后台问我。
好了,今天的案例拆解就到这里了。
有收获的小伙伴可以点个一键三连支持一下
想要工作流源码的小伙伴请后台留言“混剪” ↓
