在《moviepy音视频剪辑:音视频的加载和输出》、《moviepy音视频剪辑:多个视频合成一个视频》、《moviepy音视频剪辑:使用VideoFileClip、AudioFileClip和write_videofile、write_audiofile进行音视频的加载和输出》和《moviepy音视频剪辑:使用concatenate_videoclips和clips_array将多个视频合成一个顺序播放或同屏播放的视频》介绍了音视频文件加载和输出以及多视频合成一个视频的方法,本节将使用PyQt和moviepy结合开发一个音视频合成的GUI应用。html
以mainwindow为基础设计窗口主界面,包含一个菜单和对应工具条,用于选择要合成的文件、去除选中的文件、合成参数配置和执行合成操做等功能。python
本次对该界面的信号处理没有使用UI界面来定义信号和槽的关联,由于线条太多会很差修改,相关信号和槽的链接主要经过代码实现。web
根据选择的不一样合成类型,可选配置不一样的参数,也能够不配置,关于这些参数的说明请参考引言中提到的博文介绍。windows
老猿为准备开发的视频工具提供了一个统一的输出信息窗,moviepy自己的输出信息将所有被接管到该输出信息窗显示。界面设计如图:
缓存
关于输出信息截获请参考《在Python实现print标准输出sys.stdout、stderr重定向及捕获的简单办法》以及《PyQt(Python+Qt)学习随笔:print标准输出sys.stdout以及stderr重定向QTextBrowser等图形界面对象》。app
class mainWin(QtWidgets.QMainWindow,ui_mixClips.Ui_ui_mainWin): def __init__(self): super().__init__() self.setupUi(self) self.initValues() #完成初始化成员变量 self.initSignalAndSlots() #完成信号和槽的链接 self.initPublicFrame() #完成公共框架相关变量初始化
上面代码调用很简单,相关方法都好理解,只有initPublicFrame方法比较特殊,这是由于为了支持工具的开发只关注工具自己的功能,老猿单独开发了几个单独的模块用于全部工具都能使用,这些功能包括显示About窗口信息、截获标准输出、显示或关闭信息输出窗、信息输出窗与应用自己的QMainWindow对象关联(做为一个QDockWidget对象,关于QDockWidget请参考《第三十一章、containers容器类部件QDockWidget停靠窗功能介绍》或参考免费专栏《PyQt入门知识目录》相关章节的介绍)等功能,在此就不详细介绍了。框架
def validateAllInput(self,isOutputMessage=False): #效验全部文件是否都存在 ret = True fileList = self.videoFileListModel.stringList() if fileList: count = len(fileList) if count<2: self.actionProcessVideos.setEnabled(False) if isOutputMessage:print(f"输入视频文件数为{count},必须至少2个文件") ret = False else: for fileName in fileList: if len(fileName)==0:continue if not os.path.exists(fileName): if isOutputMessage:print(f"文件{fileName}不存在,请修订后再进行合成处理!") ret = False if ret: if not self.outputFileNameManuChanged: filePre = self.lastFileDir +"\\video_"+self.configW.composeType self.outputFileName = filePre + time.strftime("%Y%m%d%H%M%S", time.localtime()) + ".mp4" self.input_outputFile.setText(self.outputFileName) self.outputDir = self.lastFileDir else: ret = False if isOutputMessage:print(f"没有输入视频文件,必须至少2个文件") #print(self.videoFileListModel.stringList()) if not self.outputDir: ret = False if isOutputMessage:print("输出文件没有指定") elif not os.path.exists(self.outputDir): ret = False if isOutputMessage:print(f"输出文件对应目录:{self.outputDir} 不存在") #self.btn_processVideoFiles.setEnabled(ret) self.actionProcessVideos.setEnabled(ret) if ret: if isOutputMessage:print("全部输入数据检测正常!") if self.configW.composeType!='stack' and self.configW.transitionFileName and len(self.configW.transitionFileName): if not os.path.exists(self.configW.transitionFileName): if isOutputMessage:print(f"转场文件{self.configW.transitionFileName}不存在,请修订后再进行合成处理!") ret = False return ret
该方法在全部界面内容输入发送变化后触发,用于检测输入内容是否完整、合法,若是返回False,则视频合成操做不能进行。该方法带的参数用于控制是否输出检测到的异常信息,当各组件正在输入时不该输出以避免干扰,而最后要执行合成前会再校验一次,这次校验的异常则会输出。检测内容请见相关输出信息。ide
该方法包含了三种合成方式处理的完整代码,有点长。svg
def processFiles(self): print("\n\n合成处理开始......") if self.loadWin: self.loadWin.openCaptureWin() #打开输出信息窗口 if not self.validateAllInput(True):return #检测有异常则终止合成 tmpClip = [] #用于保存全部须要参与合成视频文件的剪辑对象 try: fileList = self.videoFileListModel.stringList() #取合成输入视频文件名列表 fileCount = len(fileList) for fileName in fileList: print(f"准备加载视频文件:{fileName} ") clip = mpe.VideoFileClip(fileName,verbose=True) print(f"加载视频文件:{fileName} 完成,时长为{clip.duration}秒,视频分辨率大小为:{clip.size} ") tmpClip.append(clip) print(f"视频文件:{fileName} 已经加载并缓存") transitionClip = None if self.configW.composeType != 'stack':#视频拼接可能须要转场文件 if self.configW.transitionFileName and len(self.configW.transitionFileName): print(f"准备加载转场文件:{self.configW.transitionFileName}") transitionClip = mpe.VideoFileClip(self.configW.transitionFileName) print(f"转场文件加载成功,时长为{transitionClip.duration}") print("进行内存视频合成...") padding = 0 if self.configW.composeType=='compose': #将全部输入剪辑所有统一分辨率方式合成则获取对应参数配置 method = 'compose' bgcolor = self.configW.bgColor padding = self.configW.input_padding.value() if padding==0.00: padding = 0 print("padding=", padding, 'bgcolor=', bgcolor, 'method=', method) destClip = mpe.concatenate_videoclips(tmpClip, method=method, padding=padding, bg_color=bgcolor,transition=transitionClip) #执行顺序拼接,统一分辨率 elif self.configW.composeType=='chain': #保持全部输入视频分辨率不变进行视频拼接则获取对应参数配置 padding = 0 bgcolor = None method = 'chain' print("padding=", padding, 'bgcolor=', bgcolor, 'method=', method) destClip = mpe.concatenate_videoclips(tmpClip, method=method, padding=padding, bg_color=bgcolor,transition=transitionClip)#执行顺序拼接 elif self.configW.composeType=='stack':#进行同屏播放合成则获取对应参数配置 bgcolor = self.configW.bgColor #下面代码用于设置屏幕上视频的行数和列数 if fileCount<=3: lines = 1 columns = fileCount elif fileCount<=10: lines = 2 columns = int((fileCount+1)/2) else: lines = 3 columns = int((fileCount+2)/3) print(f"视频将排列成{lines}行{columns}列") clipArrays = [] tmpClipArray = [] lines = column= 0 for clip in tmpClip:#按行列将视频排列 tmpClipArray.append(clip) column += 1 if column == columns: clipArrays.append(tmpClipArray) column = 0 tmpClipArray = [] destClip = mpe.clips_array(clipArrays) #进行同屏播放合成 print(f"内存视频合成完成,准备输出到文件:{self.outputFileName}.") destClip.write_videofile(self.outputFileName) print(f"输出到文件:{self.outputFileName} 成功!") except Exception as e: print(f"进行视频处理合成失败,请参考上面输出信息确认处理存在问题的文件,异常缘由:\n{e}") strinfo = str(e) if strinfo.find("codec can't decode"): print("该问题是因为视频文件解码致使的错误,请尝试将文件名或目录名改为纯ASCII字符集再尝试一下")
能够看到支持重复加入视频,本案例就是将《笑看风云》这个视频重复四次进行合成。若是是拼接就是四个接连播放,若是是同屏播放则一个界面上播放四个视频。工具
因为padding这个参数不能用于chain模式的拼接,所以为了展现效果,设置了padding参数为-1,表示先后两段视频有1秒的重叠。参数设置界面以下:
执行合成处理,下图为合成处理过程的一个截图:
合成处理挺快,但输出比较耗时间。
播放就是顺序播放,截图不能体现什么,但能够与同屏播放合成对比一下:
很差意思免费作广告了。
主界面和运行界面与拼接没有什么区别,参数配置界面以下:
合成后的视频截图:
使用《PyQt(Python+Qt)学习随笔:windows下使用pyinstaller将PyQt文件打包成exe可执行文件》介绍的方法进行打包。
老猿在win7上最终打包的可执行程序包已经上传到百度云,你们能够下载下来长期无偿使用。具体下载地址为百度网盘。
提取码:yh2d
选择该连接下的:视频合成工具.rar 便可。
老猿关于PyQt的付费专栏《使用PyQt开发图形界面Python应用》只须要9.9元,本专栏《PyQt+moviepy音视频剪辑实战》文档的一样内容在付费专栏上也有相应内容,整体来讲付费专栏介绍更详细或案例更多。本节内容对应付费专栏的《PyQt+moviepy音视频剪辑实战1:多视频合成顺序播放或同屏播放的视频文件》。若是有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。