PyQt+moviepy音視頻剪輯實戰2:一個剪裁視頻文件精華內容留存工具的實現

一、引言

最近網上會議很多,網上會議工具大多提供了錄播的功能,有些會議內容比較精彩,但中間穿插有些無用的內容,或者有些只有幾段精彩,大部分內容可以去除。這就需要對該錄播文件進行剪輯,取其精華留存,這樣可以節約後續重溫或者給其他人共享的時間。本文介紹的開發方法就是要實現這樣的一個工具。

二、背景知識介紹

2.1、視頻的讀取和輸出保存

本部分知識請參考《moviepy音視頻剪輯:音視頻的加載和輸出》或專欄《PyQt+moviepy音視頻剪輯實戰》相關文章即可。

2.2、視頻的截取

視頻的截取使用subclip方法,該方法爲clip類的方法,moviepy中clip類是所有剪輯的基類。

語法如下: subclip(self, t_start=0, t_end=None)

2.3、視頻的拼接

本節的案例是從同一個視頻取幾段順序拼接合成,這些段的分辨率相同,因此可以用保持分辨率拼接和統一分辨率拼接都可以,相關知識請參考《moviepy音視頻剪輯:多個視頻合成一個視頻》或專欄《PyQt+moviepy音視頻剪輯實戰》相關文章即可。

三、圖形界面設計

本程序除了主界面之外的部分都是複用《PyQt+moviepy音視頻剪輯實戰1:多個音視頻合成順序播放或同屏播放的視頻文件實現詳解》、《PyQt+moviepy音視頻剪輯實戰1:多視頻合成順序播放或同屏播放的視頻文件》的公用框架。

主界面如下:
在這裏插入圖片描述
該界面的不同部分留了過多的空間,這是爲了要在底部動態構建一個停靠窗部件用於顯示輸出信息使用。

四、代碼實現

4.1、主界面類及構造方法

class mainWin(QtWidgets.QMainWindow,ui_multiSegmentClip.Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.initValues()
        self.initSignalAndSlots()
        self.initPublicFrame()

4.2、槽和信號連接方法initSignalAndSlots

    def initSignalAndSlots(self):
        self.btn_choiceSrc.clicked.connect(self.chooseFile)

        self.videoFile.textChanged['QString'].connect(self.fileNameInputed)
        self.btn_choiceDest.clicked.connect(self.chooseFile)
        self.startPos.editingFinished.connect(self.getDestFName)
        self.endPos.editingFinished.connect(self.getDestFName)
        self.actionmergeClips.triggered.connect(self.convert)
        self.actionridClips.triggered.connect(self.convert)

4.3、視頻文件、輸出文件手工輸入或選擇方法

 def fileNameInputed(self,fname=None): #源視頻文件手工輸入編輯完成後調用本方法
        ret = False
        if not fname or fname==True:fname = self.videoFile.text()
        if self.srcDir:
            dir = QtCore.QDir(self.srcDir)
            ret = dir.exists(fname)
        self.actionmergeClips.setEnabled(ret)
        self.actionridClips.setEnabled(ret)
        self.getDestFName()

    def chooseFile(self): #點擊輸出文件選擇或視頻文件選擇調用本槽方法用於選擇文件
        btnName = self.sender().objectName()

        if btnName == 'btn_choiceSrc':

            if self.srcDir:fname = self.srcDir
            else:fname = ""
            fileName = self.fileDialog.getOpenFileName(self, "選擇視頻文件",fname, "video Files (*.mp4)")# *.wmv *.rm *.avi *.flv *.webm *.wav *rmvb )")
            if fileName[0]=='':return
            fileName = QtCore.QDir.toNativeSeparators(fileName[0])
            self.videoFile.setText(fileName)
            self.fileNameInputed(fileName)
        else:
            if self.destDir: fname = self.destDir
            else:  fname = r""
            fileName = self.fileDialog.getSaveFileName(self, "選擇要保存的視頻存儲文件",fname,"video Files (*.mp4)")# *.wmv *.rm *.avi *.flv *.webm *.wav *rmvb)")
            if fileName[0] == '': return
            fileName = QtCore.QDir.toNativeSeparators(fileName[0])
            self.saveFile.setText(fileName)
            destDir = fileName.rsplit('\\',1)[0]
            self.destDir = destDir
            print(self.destDir)

    def getDestFName(self): #根據視頻文件和視頻剪輯時間段設置自動生成一個輸出文件名
        srcFile = self.videoFile.text()
        if not srcFile:return
        file_pre, file_type = srcFile.split('.')
        if not file_type: return

        ##計算文件名長度是否小於255
        lenPre = len(file_pre)
        segStart = self.startPos.text().strip(" \r\n")
        segEnd = self.endPos.text().strip(" \r\n")
        lenSegStart = len(segStart)
        lenSegEnd = len(segEnd)
        if (lenPre + lenSegStart + lenSegEnd) > 240:
            lenSeg = (240 - lenPre) / 2
            segStart = segStart[0:lenSeg]
            segEnd = segEnd[0:lenSeg]
        else:
            segStart = segStart[0:]
            segEnd = segEnd[0:]
        self.videoFName = file_pre +'_'+segStart+ '_' + segEnd + '.'+file_type

        self.videoFName = self.videoFName.replace(',','_')
        destDir = file_pre.rsplit('\\', 1)[0]
        self.srcDir = destDir
        self.destDir = destDir
        self.saveFile.setText(self.videoFName)

4.4、視頻拼接處理方法

    def convertByMoviepy(self,srcFile,destFile,isMergeClip)://執行視頻拼接處理
        paths = destFile.rsplit('\\',1)
        if len(paths)==2:
            path = paths[0]
            fname = paths[1]
        else:
            fname = destFile
            path = ''
        if isMergeClip:
            fname = 'merge_'+fname
        else:fname = 'rid_'+fname
        if path=='':destFile = fname
        else:destFile = path+'\\'+fname
        print("執行視頻提取開始,源文件:",srcFile,' --> 目標文件:',destFile)
        start = time.clock()
        print(start)
        try:
            validClipDistance = self.validateSlipDistance(isMergeClip)
            if not validClipDistance:return

            videoFile = mpe.VideoFileClip(srcFile)
            print(f"視頻總長:{videoFile.duration}秒")
            self.destClip = None
            destClip = None

            for dist in validClipDistance:
                destClip = self.mergeClip(videoFile,dist)
            print("開始寫目標文件.")
            destClip.write_videofile(destFile)
            print("目標文件生成完成")
            videoFile.close()
            destClip.close()
            print("執行視頻提取成功,保存在文件:", destFile)
        except Exception as e:
            info = f"視頻文件無法讀取,可能是因爲格式不支持:{e}"
            print(info)
            print("任務無法執行!")
        finally:
            print("處理耗時(秒):",time.clock()-start)

    def mergeClip(self,clip,distance):#從clip取distance指定的視頻段合併到輸出剪輯 self.destClip
        start, end = distance
        try:
            duration = int(clip.duration)

            if end>duration:end = duration
            if start>duration:start = duration
            if not end:end = duration
            subClip = clip.subclip(start,end)
            if self.destClip:
                self.destClip = mpe.concatenate_videoclips([self.destClip, subClip])
            else: self.destClip = subClip
        except Exception as e:
            print(f"合併片段:{start}--{end}失敗,原因:{e}")
            return None
        else:
            print(f"合併片段:{start}--{end}成功")
            return self.destClip

五、運行界面

5.1、初始主界面

在這裏插入圖片描述
主界面上可以選擇視頻源文件、設定視頻段,不過視頻段的設置比較簡陋,所有開始位置用英文逗號分隔放在“視頻段開始位置”後面的編輯框中,結束位置放在“視頻段結束位置”,兩者數字和逗號都是純ASCII半角字符,且二者的數字和逗號個數相等,且必須從低到高排列、除最後一個外結束位置必須大於開始位置,如果結束位置爲0,則表示到視頻最後。

5.2、進行視頻裁剪的運行過程界面

在這裏插入圖片描述
這是從F:\video\順流逆流.mp4取0-3秒和20-25秒兩段視頻合併成一個視頻輸出。如果是指定視頻段輸出,輸出文件名以merge開頭,否則以rid開頭。

六、打包成windows執行文件

使用《PyQt(Python+Qt)學習隨筆:windows下使用pyinstaller將PyQt文件打包成exe可執行文件》介紹的方法進行打包。

老猿前不久用該工具實現了對一個長達150多分賬的視頻會議錄播視頻的23處精華內容進行了剪裁合併,最終生成文件爲43分鐘。不過在處理前要觀看視頻確認需要留存內容。

在win7、win10上可運行的可執行程序包已經上傳到百度雲,大家可以下載下來長期免費使用。具體下載地址爲百度網盤。

鏈接:https://pan.baidu.com/s/1UNaA2UqQBoxx-v8rCIPDhA

提取碼:yh2d

選擇該鏈接下的:視頻剪裁工具1.0.rar 即可。

注意:

百度雲上分享的《咖啡狗免費工具軟件共享空間》下的不同軟件安裝時必須解壓到不同目錄,如果解壓到同一目錄可能有衝突導致不能正常運行,
但解壓後遵循如下要求可以將其聚合到同一個目錄:

  1. 放置到同一目錄的不同軟件的版本必須相同,版本爲壓縮文件名中VX.X標註;
  2. 聚合拷貝時除拷貝執行文件外,還有resource目錄必須拷貝,如果resource目錄下有相同文件名可以覆蓋;
  3. 聚合拷貝exe文件和resource目錄及其下文件到其他已解壓工具目錄後,源目錄可以刪除。

廣告

老猿關於PyQt的付費專欄《使用PyQt開發圖形界面Python應用》只需要9.9元,本專欄《PyQt+moviepy音視頻剪輯實戰》文檔的同樣內容在付費專欄上也有相應內容,總體來說付費專欄介紹更詳細或案例更多。本節內容對應付費專欄的《PyQt+moviepy音視頻剪輯實戰2:實現一個剪裁視頻文件精華內容留存工具》。如果有興趣也願意支持老猿的讀者,歡迎購買付費專欄。

跟老猿學Python、學5G!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章