一、引言
最近網上會議很多,網上會議工具大多提供了錄播的功能,有些會議內容比較精彩,但中間穿插有些無用的內容,或者有些只有幾段精彩,大部分內容可以去除。這就需要對該錄播文件進行剪輯,取其精華留存,這樣可以節約後續重溫或者給其他人共享的時間。本文介紹的開發方法就是要實現這樣的一個工具。
二、背景知識介紹
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 即可。
注意:
百度雲上分享的《咖啡狗免費工具軟件共享空間》下的不同軟件安裝時必須解壓到不同目錄,如果解壓到同一目錄可能有衝突導致不能正常運行,
但解壓後遵循如下要求可以將其聚合到同一個目錄:
- 放置到同一目錄的不同軟件的版本必須相同,版本爲壓縮文件名中VX.X標註;
- 聚合拷貝時除拷貝執行文件外,還有resource目錄必須拷貝,如果resource目錄下有相同文件名可以覆蓋;
- 聚合拷貝exe文件和resource目錄及其下文件到其他已解壓工具目錄後,源目錄可以刪除。
廣告
老猿關於PyQt的付費專欄《使用PyQt開發圖形界面Python應用》只需要9.9元,本專欄《PyQt+moviepy音視頻剪輯實戰》文檔的同樣內容在付費專欄上也有相應內容,總體來說付費專欄介紹更詳細或案例更多。本節內容對應付費專欄的《PyQt+moviepy音視頻剪輯實戰2:實現一個剪裁視頻文件精華內容留存工具》。如果有興趣也願意支持老猿的讀者,歡迎購買付費專欄。