- 專欄:Python基礎(chǔ)教程目錄
- 專欄:使用PyQt開發(fā)圖形界面Python應(yīng)用
- 專欄:PyQt+moviepy音視頻剪輯實(shí)戰(zhàn)
- 專欄:PyQt入門學(xué)習(xí)
- 老猿Python博文目錄
- 老猿學(xué)5G博文目錄
一、引言
在《moviepy音視頻剪輯:音視頻的加載和輸出》、《moviepy音視頻剪輯:多個(gè)視頻合成一個(gè)視頻》、《moviepy音視頻剪輯:使用VideoFileClip、AudioFileClip和write_videofile、write_audiofile進(jìn)行音視頻的加載和輸出》和《moviepy音視頻剪輯:使用concatenate_videoclips和clips_array將多個(gè)視頻合成一個(gè)順序播放或同屏播放的視頻》介紹了音視頻文件加載和輸出以及多視頻合成一個(gè)視頻的方法,本節(jié)將使用PyQt和moviepy結(jié)合開發(fā)一個(gè)音視頻合成的GUI應(yīng)用。
二、功能及界面設(shè)計(jì)
2.1、主界面
以mainwindow為基礎(chǔ)設(shè)計(jì)窗口主界面,包含一個(gè)菜單和對應(yīng)工具條,用于選擇要合成的文件、去除選中的文件、合成參數(shù)配置和執(zhí)行合成操作等功能。
本次對該界面的信號處理沒有使用UI界面來定義信號和槽的關(guān)聯(lián),因?yàn)榫€條太多會(huì)不好修改,相關(guān)信號和槽的連接主要通過代碼實(shí)現(xiàn)。
2.2、參數(shù)配置界面
根據(jù)選擇的不同合成類型,可選配置不同的參數(shù),也可以不配置,關(guān)于這些參數(shù)的說明請參考引言中提到的博文介紹。
2.3、輸出信息窗
老猿為準(zhǔn)備開發(fā)的視頻工具提供了一個(gè)統(tǒng)一的輸出信息窗,moviepy本身的輸出信息將全部被接管到該輸出信息窗顯示。界面設(shè)計(jì)如圖:
關(guān)于輸出信息截獲請參考《在Python實(shí)現(xiàn)print標(biāo)準(zhǔn)輸出sys.stdout、stderr重定向及捕獲的簡單辦法》以及《PyQt(Python+Qt)學(xué)習(xí)隨筆:print標(biāo)準(zhǔn)輸出sys.stdout以及stderr重定向QTextBrowser等圖形界面對象》。
三、代碼實(shí)現(xiàn)
3.1、主界面構(gòu)造方法
class mainWin(QtWidgets.QMainWindow,ui_mixClips.Ui_ui_mainWin):
def __init__(self):
super().__init__()
self.setupUi(self)
self.initValues() #完成初始化成員變量
self.initSignalAndSlots() #完成信號和槽的連接
self.initPublicFrame() #完成公共框架相關(guān)變量初始化
上面代碼調(diào)用很簡單,相關(guān)方法都好理解,只有initPublicFrame方法比較特殊,這是因?yàn)闉榱酥С止ぞ叩拈_發(fā)只關(guān)注工具本身的功能,老猿單獨(dú)開發(fā)了幾個(gè)單獨(dú)的模塊用于所有工具都能使用,這些功能包括顯示About窗口信息、截獲標(biāo)準(zhǔn)輸出、顯示或關(guān)閉信息輸出窗、信息輸出窗與應(yīng)用本身的QMainWindow對象關(guān)聯(lián)(作為一個(gè)QDockWidget對象,關(guān)于QDockWidget請參考《第三十一章、containers容器類部件QDockWidget??看肮δ芙榻B》或參考免費(fèi)專欄《PyQt入門知識目錄》相關(guān)章節(jié)的介紹)等功能,在此就不詳細(xì)介紹了。
3.2、界面輸入內(nèi)容校驗(yàn)方法
def validateAllInput(self,isOutputMessage=False):
#效驗(yàn)所有文件是否都存在
ret = True
fileList = self.videoFileListModel.stringList()
if fileList:
count = len(fileList)
if count<2:
self.actionProcessVideos.setEnabled(False)
if isOutputMessage:print(f"輸入視頻文件數(shù)為{count},必須至少2個(gè)文件")
ret = False
else:
for fileName in fileList:
if len(fileName)==0:continue
if not os.path.exists(fileName):
if isOutputMessage:print(f"文件{fileName}不存在,請修訂后再進(jìn)行合成處理!")
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個(gè)文件")
#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"輸出文件對應(yīng)目錄:{self.outputDir} 不存在")
#self.btn_processVideoFiles.setEnabled(ret)
self.actionProcessVideos.setEnabled(ret)
if ret:
if isOutputMessage:print("所有輸入數(shù)據(jù)檢測正常!")
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"轉(zhuǎn)場文件{self.configW.transitionFileName}不存在,請修訂后再進(jìn)行合成處理!")
ret = False
return ret
該方法在所有界面內(nèi)容輸入發(fā)送變化后觸發(fā),用于檢測輸入內(nèi)容是否完整、合法,如果返回False,則視頻合成操作不能進(jìn)行。該方法帶的參數(shù)用于控制是否輸出檢測到的異常信息,當(dāng)各組件正在輸入時(shí)不應(yīng)輸出以免干擾,而最后要執(zhí)行合成前會(huì)再校驗(yàn)一次,此次校驗(yàn)的異常則會(huì)輸出。檢測內(nèi)容請見相關(guān)輸出信息。
3.3、合成處理方法
該方法包含了三種合成方式處理的完整代碼,有點(diǎn)長。
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"準(zhǔn)備加載視頻文件:{fileName} ")
clip = mpe.VideoFileClip(fileName,verbose=True)
print(f"加載視頻文件:{fileName} 完成,時(shí)長為{clip.duration}秒,視頻分辨率大小為:{clip.size} ")
tmpClip.append(clip)
print(f"視頻文件:{fileName} 已經(jīng)加載并緩存")
transitionClip = None
if self.configW.composeType != 'stack':#視頻拼接可能需要轉(zhuǎn)場文件
if self.configW.transitionFileName and len(self.configW.transitionFileName):
print(f"準(zhǔn)備加載轉(zhuǎn)場文件:{self.configW.transitionFileName}")
transitionClip = mpe.VideoFileClip(self.configW.transitionFileName)
print(f"轉(zhuǎn)場文件加載成功,時(shí)長為{transitionClip.duration}")
print("進(jìn)行內(nèi)存視頻合成...")
padding = 0
if self.configW.composeType=='compose': #將所有輸入剪輯全部統(tǒng)一分辨率方式合成則獲取對應(yīng)參數(shù)配置
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) #執(zhí)行順序拼接,統(tǒng)一分辨率
elif self.configW.composeType=='chain': #保持所有輸入視頻分辨率不變進(jìn)行視頻拼接則獲取對應(yīng)參數(shù)配置
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)#執(zhí)行順序拼接
elif self.configW.composeType=='stack':#進(jìn)行同屏播放合成則獲取對應(yīng)參數(shù)配置
bgcolor = self.configW.bgColor
#下面代碼用于設(shè)置屏幕上視頻的行數(shù)和列數(shù)
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) #進(jìn)行同屏播放合成
print(f"內(nèi)存視頻合成完成,準(zhǔn)備輸出到文件:{self.outputFileName}.")
destClip.write_videofile(self.outputFileName)
print(f"輸出到文件:{self.outputFileName} 成功!")
except Exception as e:
print(f"進(jìn)行視頻處理合成失敗,請參考上面輸出信息確認(rèn)處理存在問題的文件,異常原因:\n{e}")
strinfo = str(e)
if strinfo.find("codec can't decode"):
print("該問題是由于視頻文件解碼導(dǎo)致的錯(cuò)誤,請嘗試將文件名或目錄名改成純ASCII字符集再嘗試一下")
四、運(yùn)行界面截圖
4.1、加入合成文件后的主界面
可以看到支持重復(fù)加入視頻,本案例就是將《笑看風(fēng)云》這個(gè)視頻重復(fù)四次進(jìn)行合成。如果是拼接就是四個(gè)接連播放,如果是同屏播放則一個(gè)界面上播放四個(gè)視頻。
4.2、設(shè)置為統(tǒng)一分辨率拼接合成
由于padding這個(gè)參數(shù)不能用于chain模式的拼接,因此為了展示效果,設(shè)置了padding參數(shù)為-1,表示前后兩段視頻有1秒的重疊。參數(shù)設(shè)置界面如下:
執(zhí)行合成處理,下圖為合成處理過程的一個(gè)截圖:
合成處理挺快,但輸出比較耗時(shí)間。
播放就是順序播放,截圖不能體現(xiàn)什么,但可以與同屏播放合成對比一下:
不好意思免費(fèi)做廣告了。
4.3、設(shè)置為同屏播放方式合成
主界面和運(yùn)行界面與拼接沒有什么區(qū)別,參數(shù)配置界面如下:
合成后的視頻截圖:
五、打包成exe
使用《PyQt(Python+Qt)學(xué)習(xí)隨筆:windows下使用pyinstaller將PyQt文件打包成exe可執(zhí)行文件》介紹的方法進(jìn)行打包。
老猿在win7上最終打包的可執(zhí)行程序包已經(jīng)上傳到百度云,大家可以下載下來長期免費(fèi)使用。具體下載地址為百度網(wǎng)盤。
鏈接:https://pan.baidu.com/s/1UNaA2UqQBoxx-v8rCIPDhA
提取碼:yh2d
選擇該鏈接下的:視頻合成工具.rar 即可。
廣告
老猿關(guān)于PyQt的付費(fèi)專欄《使用PyQt開發(fā)圖形界面Python應(yīng)用》只需要9.9元,本專欄《PyQt+moviepy音視頻剪輯實(shí)戰(zhàn)》文檔的同樣內(nèi)容在付費(fèi)專欄上也有相應(yīng)內(nèi)容,總體來說付費(fèi)專欄介紹更詳細(xì)或案例更多。本節(jié)內(nèi)容對應(yīng)付費(fèi)專欄的《PyQt+moviepy音視頻剪輯實(shí)戰(zhàn)1:多視頻合成順序播放或同屏播放的視頻文件》。如果有興趣也愿意支持老猿的讀者,歡迎購買付費(fèi)專欄。
<a ><img src="https://img-blog.csdnimg.cn/20190426190559122.png" ><img src="https://img-blog.csdnimg.cn/20200422115441574.png" ></a>