使用pyside6实现ffmpeg操作的图形化

ffmpeg

ffmpeg是一个音视频处理工具。常用的功能有视频转图片截取视频片段

视频转图片
$ ffmpeg -i [input] -vf fps=[fps] -q:v [n]  [output]"
截取视频
$ ffmpeg -ss [start] -i [input] -t [duration] -c copy [output]
$ ffmpeg -ss [start] -i [input] -to [end] -c copy [output]

其中,-i 表示输入;-vf 表示每1秒输出几帧;-q:v 选择图片质量,其中n是1-31的数字,1表示最高质量

附一张有无添加-q:v 1参数的对比图:
在这里插入图片描述

pyside6

当批量操作视频,且需要频繁更改传入参数时,使用可视化界面可以提升效率。
pyside6是Qt6的python版本下的API库,其内置的designer可以使用拖拽的方式生成页面代码。
下面记录实现的过程,下图是视频抽帧、裁剪的界面
在这里插入图片描述

conda create -n pyside6 python=3.8
conda activate pyside6
pip install pyside6 -y

打开D:\ProgramFiles\Anaconda\envs\pyside6\Lib\site-packages\PySide6\designer.exe,进行拖拽式界面设计,如下图:
在这里插入图片描述
这里使用 Label、LineEdit、comboBox、pushButton四个组件,进行简单布局后,保存为 ui.ui文件。
在这里插入图片描述

打开命令行工具,编译ui.ui得到python文件ui.py

PySide6-uic ui.ui -o ui.py

关于快速熟悉Pyside6设计操作的可以参考视频:

https://www.bilibili.com/video/BV1Wa41127fk/?spm_id_from=333.788

生成的界面代码:ui.py

# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'ui.ui'
##
## Created by: Qt User Interface Compiler version 6.2.4
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
    QMetaObject, QObject, QPoint, QRect,
    QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
    QFont, QFontDatabase, QGradient, QIcon,
    QImage, QKeySequence, QLinearGradient, QPainter,
    QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QComboBox, QHBoxLayout, QLabel,
    QLineEdit, QMainWindow, QMenuBar, QPushButton,
    QSizePolicy, QSpacerItem, QStatusBar, QVBoxLayout,
    QWidget)

from QLineEdit_Override import MyQLine

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(1116, 913)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.comboBox_fps = QComboBox(self.centralwidget)
        self.comboBox_fps.addItem("")
        self.comboBox_fps.addItem("")
        self.comboBox_fps.addItem("")
        self.comboBox_fps.addItem("")
        self.comboBox_fps.addItem("")
        self.comboBox_fps.addItem("")
        self.comboBox_fps.setObjectName(u"comboBox_fps")
        self.comboBox_fps.setGeometry(QRect(270, 250, 91, 31))
        self.comboBox_output = QComboBox(self.centralwidget)
        self.comboBox_output.addItem("")
        self.comboBox_output.addItem("")
        self.comboBox_output.setObjectName(u"comboBox_output")
        self.comboBox_output.setGeometry(QRect(270, 360, 171, 31))
        self.layoutWidget = QWidget(self.centralwidget)
        self.layoutWidget.setObjectName(u"layoutWidget")
        self.layoutWidget.setGeometry(QRect(190, 120, 71, 301))
        self.verticalLayout = QVBoxLayout(self.layoutWidget)
        self.verticalLayout.setObjectName(u"verticalLayout")
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.label = QLabel(self.layoutWidget)
        self.label.setObjectName(u"label")

        self.verticalLayout.addWidget(self.label)

        self.label_2 = QLabel(self.layoutWidget)
        self.label_2.setObjectName(u"label_2")

        self.verticalLayout.addWidget(self.label_2)

        self.label_3 = QLabel(self.layoutWidget)
        self.label_3.setObjectName(u"label_3")

        self.verticalLayout.addWidget(self.label_3)

        self.lineEdit_file_path = MyQLine(self.centralwidget)
        self.lineEdit_file_path.setObjectName(u"lineEdit_file_path")
        self.lineEdit_file_path.setGeometry(QRect(270, 130, 301, 61))
        self.widget = QWidget(self.centralwidget)
        self.widget.setObjectName(u"widget")
        self.widget.setGeometry(QRect(630, 210, 61, 221))
        self.verticalLayout_2 = QVBoxLayout(self.widget)
        self.verticalLayout_2.setObjectName(u"verticalLayout_2")
        self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
        self.label_4 = QLabel(self.widget)
        self.label_4.setObjectName(u"label_4")

        self.verticalLayout_2.addWidget(self.label_4)

        self.label_5 = QLabel(self.widget)
        self.label_5.setObjectName(u"label_5")

        self.verticalLayout_2.addWidget(self.label_5)

        self.widget1 = QWidget(self.centralwidget)
        self.widget1.setObjectName(u"widget1")
        self.widget1.setGeometry(QRect(720, 170, 171, 301))
        self.verticalLayout_3 = QVBoxLayout(self.widget1)
        self.verticalLayout_3.setObjectName(u"verticalLayout_3")
        self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
        self.lineEdit_start = QLineEdit(self.widget1)
        self.lineEdit_start.setObjectName(u"lineEdit_start")

        self.verticalLayout_3.addWidget(self.lineEdit_start)

        self.lineEdit_duration = QLineEdit(self.widget1)
        self.lineEdit_duration.setObjectName(u"lineEdit_duration")

        self.verticalLayout_3.addWidget(self.lineEdit_duration)

        self.widget2 = QWidget(self.centralwidget)
        self.widget2.setObjectName(u"widget2")
        self.widget2.setGeometry(QRect(270, 510, 521, 61))
        self.horizontalLayout = QHBoxLayout(self.widget2)
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.pushButton_extract_frame = QPushButton(self.widget2)
        self.pushButton_extract_frame.setObjectName(u"pushButton_extract_frame")
        self.pushButton_extract_frame.setLayoutDirection(Qt.RightToLeft)

        self.horizontalLayout.addWidget(self.pushButton_extract_frame)

        self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)

        self.horizontalLayout.addItem(self.horizontalSpacer)

        self.pushButton_video_clip = QPushButton(self.widget2)
        self.pushButton_video_clip.setObjectName(u"pushButton_video_clip")
        self.pushButton_video_clip.setLayoutDirection(Qt.RightToLeft)

        self.horizontalLayout.addWidget(self.pushButton_video_clip)

        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QMenuBar(MainWindow)
        self.menubar.setObjectName(u"menubar")
        self.menubar.setGeometry(QRect(0, 0, 1116, 22))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QStatusBar(MainWindow)
        self.statusbar.setObjectName(u"statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)

        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
        self.comboBox_fps.setItemText(0, QCoreApplication.translate("MainWindow", u"2", None))
        self.comboBox_fps.setItemText(1, QCoreApplication.translate("MainWindow", u"1.5", None))
        self.comboBox_fps.setItemText(2, QCoreApplication.translate("MainWindow", u"1", None))
        self.comboBox_fps.setItemText(3, QCoreApplication.translate("MainWindow", u"1/2", None))
        self.comboBox_fps.setItemText(4, QCoreApplication.translate("MainWindow", u"1/3", None))
        self.comboBox_fps.setItemText(5, QCoreApplication.translate("MainWindow", u"1/5", None))

        self.comboBox_output.setItemText(0, QCoreApplication.translate("MainWindow", u"Images", None))
        self.comboBox_output.setItemText(1, QCoreApplication.translate("MainWindow", u"\u540c\u540d\u6587\u4ef6\u5939", None))

        self.label.setText(QCoreApplication.translate("MainWindow", u"\u89c6\u9891\u6587\u4ef6", None))
        self.label_2.setText(QCoreApplication.translate("MainWindow", u"fps", None))
        self.label_3.setText(QCoreApplication.translate("MainWindow", u"\u4fdd\u5b58\u8def\u5f84", None))
        self.lineEdit_file_path.setText("")
        self.lineEdit_file_path.setPlaceholderText(QCoreApplication.translate("MainWindow", u"\u6d4f\u89c8\u6216\u62d6\u62fd.mp4\u6587\u4ef6\u5230\u8fd9\u91cc", None))
        self.label_4.setText(QCoreApplication.translate("MainWindow", u"\u5f00\u59cb\u65f6\u95f4", None))
        self.label_5.setText(QCoreApplication.translate("MainWindow", u"\u6301\u7eed\u65f6\u95f4", None))
        self.lineEdit_start.setText(QCoreApplication.translate("MainWindow", u"0", None))
        self.lineEdit_duration.setText(QCoreApplication.translate("MainWindow", u"0", None))
        self.pushButton_extract_frame.setText(QCoreApplication.translate("MainWindow", u"\u62bd\u5e27", None))
        self.pushButton_video_clip.setText(QCoreApplication.translate("MainWindow", u"\u88c1\u526a", None))
    # retranslateUi


接着,编写代码,获取组件的数值或者给组件绑定动作

主函数:main.py

from PySide6.QtWidgets import QApplication, QMainWindow
from ui import Ui_MainWindow
from ffmpeg import extract_videos_frame, extract_video_frame, video_clip
import os
# PySide6-uic ui.ui -o ui.py


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()  # UI类的实例化
        self.ui.setupUi(self)

        self.band()
        self.fps = 0  # 帧率
        self.input_path = ""        # 视频路径
        self.output_path = ""  # 保存路径

        self.start = 0          # 开始时间
        self.duration = 0       # 持续时间

    def band(self):
        # seLf.ui.___ACTTON___.triggered.connect(___FUNCTION___)
        # seLf.ui.___BUTTON___.clicked.connect(___FUNCTION___)
        # seLf.Ui.___COMBO_B0X___.currentIndexChanged.connect(___FUNCTION___)
        # seLf.ui.___SPIN_B0X___.valueChanged.connect(___FUNCTION___)
        # 自定义信号.属性名.connect(___FUNCTION___)
        self.ui.pushButton_extract_frame.clicked.connect(self.extract_frame)
        self.ui.pushButton_video_clip.clicked.connect(self.video_clip)

    def extract_frame(self):
        self.fps = self.ui.comboBox_fps.currentText()  # 获取fps
        from fractions import Fraction  # str转float
        self.fps = float(Fraction(self.fps))
        self.input_path = self.ui.lineEdit_file_path.text()
        self.output_path = self.ui.comboBox_output.currentText()  # 获取图片存储路径

        print('Input Path: ', self.input_path)
        print('Output path: ', self.output_path)
        print('FPS: ', self.fps)

        if os.path.isdir(self.input_path):
            extract_videos_frame(self.input_path, self.fps, self.output_path)
        else:
            extract_videos_frame(self.input_path, self.fps, self.output_path)

    def video_clip(self):
        self.input_path = self.ui.lineEdit_file_path.text()
        self.start = int(self.ui.lineEdit_start.text())
        self.duration = int(self.ui.lineEdit_duration.text())

        print('Input Path: ', self.input_path)
        print('Start time: %d s' % self.start)
        print('Duration : %d s' % self.duration)
        if not os.path.isdir(self.input_path):
            video_clip(self.input_path, self.start, self.duration)


if __name__ == '__main__':
    app = QApplication([])    # 启动一个应用
    window = MainWindow()     # 实例化主窗口
    window.show()             # 展示主窗口
    app.exec()                # 避免程序执行到这一行后直接退出

视频处理代码:ffmpeg.py

import shutil
import hashlib
import os


def get_md5(url):
    if isinstance(url, str):
        url = url.encode("utf-8")
    md = hashlib.md5()
    md.update(url)
    return md.hexdigest()


def extract_video_frame(video_path, fps, output_path):
    # 创建存储图片的文件夹
    video_name = os.path.split(video_path)[1].split('.')[0].replace(' ', "")
    print(video_name)
    if output_path == "Images":
        output_path = os.path.join(os.path.dirname(video_path), "Images")
    else:
        output_path = os.path.join(os.path.dirname(video_path), video_name)
        if os.path.exists(output_path):
            shutil.rmtree(output_path)
    if not os.path.exists(output_path):
        os.makedirs(output_path)

    # ffmpeg把视频转图片
    hash_name = get_md5(video_path)
    image_name = hash_name + "-" + video_name
    commend = "ffmpeg -i \"%s\" -vf fps=%f -q:v 1  %s\%s-%%4d.jpg" % \
              (video_path, fps, output_path, image_name)
    # 执行cmd命令
    os.system(commend)
    print("Commend: ", commend)


def extract_videos_frame(videos_dir, fps, output_path):
    # 对该目录下所有文件抽帧
    videos_path = []
    for root, dirs, files in os.walk(videos_dir):
        videos_path.extend([os.path.join(root, name) for name in files if name[-4:] in ["webm", ".mp4", ".MP4"]])
    print(videos_path)
    for video_path in videos_path:
        extract_video_frame(video_path, fps, output_path)


def video_clip(video_path, start, duration):
    new_video_name = os.path.split(video_path)[1][0:-4]+'_cliped'+os.path.split(video_path)[1][-4:]
    save_path = os.path.join(os.path.split(video_path)[0], new_video_name)
    commend = 'ffmpeg -i %s -ss %d -t %d -c copy %s' % (video_path, start, duration, save_path)
    os.system(commend)


QLineEditEX.py

其中,拖拽文件获取文件路径,网络大都是pyside2版的教程。

https://www.2bboy.com/archives/173.html

该博客评论区下的“喵喵”,提供了pyside6下实现“拖拽文件获取路径”的解决方案。具体做法是:右击QLineEdit标签“提升为”,如下图进行设置。接着创建文件QLineEditEX.py ,继承QLineEdit并重写拖拽功能。
在这里插入图片描述

from PySide6.QtWidgets import QLineEdit

class MyQLine(QLineEdit):
    """实现文件拖放功能"""
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        data = event.mimeData()
        urls = data.urls()
        if (urls and urls[0].scheme() == 'file'):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        data = event.mimeData()
        urls = data.urls()
        if urls and urls[0].scheme() == 'file':
            event.acceptProposedAction()

    def dropEvent(self, event):
        data = event.mimeData()
        urls = data.urls()
        if urls and urls[0].scheme() == 'file':
            # for some reason, this doubles up the intro slash
            filepath = str(urls[0].path())[1:]
            self.setText(filepath)

结果

可以拖拽视频文件实现路径的获取,处理单个视频。也可以直接输入文件目录,处理文件下所有视频文件。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值