pydub+moviepy+opencv+ffmpeg按帧数进行视频剪辑

Arya Lv3

⏸️前言

公司的流程中经常会用到EDL (Edit Desicion List剪辑决策表)来确定每个镜头时间。因为要分别把不同的镜头发给不同的动画师,所以需要一个能够将每一集的分镜根据edl的时间剪辑出不同镜头的插件。用python进行视频处理,有两个现成的库可以用,一是moviepy,第二个是opencv。其中moviepy可以精确到秒对视频进行剪辑,而opencv可以精确到帧对视频进行剪辑。本想着用opencv就可以简单处理了。没想到流程中要求剪辑的视频需要带声音!!!半路杀出个程咬金。因为opencv按帧数剪辑视频的原理是将视频拆分成每一帧,然后再把每一帧重新合成为一个视频,所以导致剪辑出来的视频没有声音,而且这个过程会很慢。而其他的音频处理库,例如pydub也只能精确到毫秒,因为帧是视频的时间概念,转换到音频的时间概念必须要四舍五入,所以总会有那么一两帧与edl有出入。
在互联网上搜寻了一天无果后,我想出了一种视频剪辑全家福(pydub+moviepy+opencv+ffmpeg)套餐,可能不是最优解,我相信肯定有更好的方法,只是我没有找到,如果你知道的话,可以下方评论我。话不多说,下面来介绍下我的方法。

💡思路

我的方法思路有点繁复,其中的关键是ffmpeg,使用它可以剪辑视频的零到任何一帧,但是无法自定义起始帧(或者是我没查到文档中正确的方法)。所以我只能将精确到帧的视频(没有声音)用opencv剪辑出来,然后合成上精确到毫秒的声音(moviepy转换+pydub剪辑音频)。然后再用ffmpeg剪辑一次,保证视频的帧数正确,而多出的声音则会被剪掉。

💻具体做法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import cv2
from pydub import AudioSegment
from moviepy.editor import *
def clip_video(self):
audio = AudioFileClip(self.video_file_path) #moviepy提取视频中的音频
audio.write_audiofile(self.folder_path+"/audio.wav") #写入音频文件
audio = AudioSegment.from_file(self.folder_path+"/audio.wav") #pydub
edl_path = self.edl_file_path
title = self.detect_edl_title(edl_path) #提取edl的Title,并进行处理
title_list=title.split("_")
self.data=self.extract_data() #提取edl镜头名称及时间信息
self.show = []
self.edl_infomation=[]
#计算每个镜头的帧数以及起止时间
for clip_name, timecode_in, timecode_out in self.data:
hours_in, minutes_in, seconds_in, frames_in = map(int, timecode_in.split(":"))
hours_out, minutes_out, seconds_out, frames_out = map(int, timecode_out.split(":"))
#audio time clip
start_time = (hours_in * 3600 + minutes_in * 60 + seconds_in) * 1000 + frames_in * (1000 / 24)
end_time = (hours_out * 3600 + minutes_out * 60 + seconds_out) * 1000 + frames_out * (1000 / 24)
#video frames clip
edl_frames = (3600 * (hours_out - hours_in) + 60 * (minutes_out - minutes_in) + (
seconds_out - seconds_in)) * 24 + (frames_out - frames_in)
clip_name = title_list[0] + "_" + clip_name
self.edl_infomation.append([clip_name, edl_frames,start_time,end_time])



video_path = self.video_file_path
video_path = video_path.replace("\\", "/").replace('"', '').replace("'", "").strip()
video_capture = cv2.VideoCapture(video_path) #opencv
fps = video_capture.get(cv2.CAP_PROP_FPS) #得到视频的帧速率
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH) #得到视频宽度
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT) #得到视频高度
frames = video_capture.get(cv2.CAP_PROP_FRAME_COUNT) #得到视频总帧数
size = (int(width),int(height))
fourcc = cv2.VideoWriter_fourcc('H', '2', '6', '4') #视频是H264编码,也就是mov格式
start_frame=0
for item in self.edl_infomation:
video_capture = cv2.VideoCapture(video_path)
output_dir=self.folder_path.replace("\\","/")
out_video_pth = output_dir+"/"+item[0]+"_temp_.mov" #临时文件1的地址,帧数准确但无声的视频
video_write = cv2.VideoWriter(out_video_pth, fourcc, fps, size, True)
end_frame = start_frame+int(item[1])
pos_frame = start_frame
while (pos_frame <= end_frame):
video_capture.set(cv2.CAP_PROP_POS_FRAMES, pos_frame)
isSuccess, frame = video_capture.read()
video_write.write(frame) #把每一帧写入文件中
pos_frame = pos_frame + 1
video_capture.release() #释放内存
video_write.release()
index=self.edl_infomation.index(item)
out_video_pth = out_video_pth.replace("\\", "/").replace('"', '').replace("'", "").strip()
video=VideoFileClip(out_video_pth) #moviepy
video_time=video.duration
# 提取视频的音频
#audio segment file
output_audiofile=output_dir+"/"+item[0]+".wav"
audio_segment = audio[item[2]:item[3]]
audio_segment.export(output_audiofile, format="wav")
#合成音视频
audiofile=AudioFileClip(output_audiofile)
video_out=video.set_audio(audiofile) #合成视频和音频,生成的视频帧数是音频的帧数,不太准确
out_name=out_video_pth.replace(".mov",".mp4")
output_path=output_dir+"/"+item[0]+".mov"
video_out.write_videofile(out_name)
ff="ffmpeg.exe" #这个exe要去ffmpeg官网下载
cmd01=ff+" -i "+out_name+" -ss 0 -vframes "+str(end_frame-start_frame)+" -y "+output_path
os.system(cmd01) #用cmd运行ffmpeg,对合成的视频再次进行剪辑,保证帧数准确
#清理之前的临时文件
os.remove(out_video_pth)
os.remove(out_name)
os.remove(output_audiofile)
start_frame=end_frame
if end_frame == frames:
break
os.remove(self.folder_path+"/audio.wav") #删除之前提取出来的音频

📖遇到的问题

保证帧数准确还要有声音是一个难题。其次的难题就是格式问题,因为动画师习惯用mov格式的视频,所以要用ffmpeg对格式进行转换,而用opencv时需要去查询编码格式,才能生成正确格式的视频。而moviepy无法生成mov格式的视频。因为这个项目的另一个部分是按照edl剪辑音频(不是我写的),还是会有反馈音频的帧数不准的问题,如果还想让音频帧数准确的话,可以生成视频后再提取一遍音频吧😳…但是运行的时间嘛….就当然没有那么快了

  • 标题: pydub+moviepy+opencv+ffmpeg按帧数进行视频剪辑
  • 作者: Arya
  • 创建于 : 2023-08-09 22:09:52
  • 更新于 : 2024-01-05 15:53:10
  • 链接: https://aryagala0.github.io/2023/08/09/python项目/根据edl剪辑镜头工具(时间精确到帧)/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论
此页目录
pydub+moviepy+opencv+ffmpeg按帧数进行视频剪辑