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:实现一个剪裁视频文件精华内容留存工具》。如果有兴趣也愿意支持老猿的读者,欢迎购买付费专栏。

<a href="https://blog.csdn.net/LaoYuanPython/article/details/98245036"><img src="https://img-blog.csdnimg.cn/20190426190559122.png" ><img src="https://img-blog.csdnimg.cn/20200422115441574.png" ></a>

跟老猿学Python、学5G!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345