PyQt+moviepy音視頻剪輯實(shí)戰(zhàn)1:多個(gè)音視頻合成順序播放或同屏播放的視頻文件實(shí)現(xiàn)詳解

一、引言

在《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>

跟老猿學(xué)Python、學(xué)5G!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容