pyqt5 制作视频剪辑软件,切割视频

该软件用于切割视频,手动选取视频片段的起始帧和结束帧并保存为json文件。gui界面如下:包含快进、快退、暂停等功能,

代码如下:

# coding=UTF-8
"""
theme: pyqt5实现动作起始帧和结束帧的定位,将定位到的帧数保存json文件
time:  2024-6-27
author: cong
"""
import json
import re
import sys
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

# 使用 QMediaPlayer 可以进行视频文件解码,视频播放必须将视频帧在某个界面组件上显示,
# 有 QVideoWidget 和 QGraphicsVideoItem 两种视频显示组件,也可以从这两个类继承,自定义视频显示组件。
# QMediaPlayer 也可以结合 QMediaPlaylist 实现视频文件列表播放。


class VideoWin(QWidget):
    save_flag = 0
    save_start_count = 1
    save_end_count = 2


    def __init__(self):
        super(VideoWin, self).__init__()
        self.setWindowTitle("MediaPlayer")
        # 播放画面
        self.player = QMediaPlayer()
        self.video_widget = QVideoWidget(self)  # 定义视频显示的widget,界面组件
        self.video_widget.setFixedSize(1280,720)
        self.player.setVideoOutput(self.video_widget)  # 视频播放输出的widget,就是上面定义的

        # 当前播放的进度,显示调整视频进度条
        self.label_time = QLabel()
        self.timeSlider = QSlider()
        self.timeSlider.setOrientation(Qt.Horizontal)
        self.timeSlider.setValue(0)
        self.timeSlider.setMinimum(0)
        self.player.positionChanged.connect(self.get_time)
        self.timeSlider.sliderPressed.connect(self.player.pause)
        self.timeSlider.sliderMoved.connect(self.change_time)
        self.timeSlider.sliderReleased.connect(self.player.play)

        # 打开视频
        self.open_button = QPushButton('打开')
        self.open_button.clicked.connect(self.open_file)
        # 快进
        self.right_button = QPushButton('快进')
        self.right_button.clicked.connect(self.up_time)
        # play
        self.play_button = QPushButton('播放')
        self.play_button.clicked.connect(self.player.play)
        # pause
        self.mid_button = QPushButton('暂停')
        self.mid_button.clicked.connect(self.player.pause)
        # 快退
        self.left_button = QPushButton('快退')
        self.left_button.clicked.connect(self.down_time)
        # 保存开始时间
        self.start_button = QPushButton('保存动作开始时间')
        self.start_button.clicked.connect(self.save_start_time)
        # 保存结束时间
        self.end_button = QPushButton('保存动作结束时间')
        self.end_button.clicked.connect(self.save_end_time)
        # 所有时间选定,最终保存按钮
        self.done_button = QPushButton('完成并保存')
        self.done_button.setFixedSize(100,40)
        self.done_button.clicked.connect(self.save_json)




        # 视频路径 entry 布局
        self.path_entry = QLineEdit()

        # 创建一个网格布局
        grid_layout = QGridLayout()
        self.entry_names = [f'entry_{i + 1}' for i in range(80)]
        for i in range(80):

            if (i+1) % 2 == 1:
                label = QLabel(f"start_frame_d{int((i+1)// 2 + 1)}:")
            else:
                label = QLabel(f"end_frame_d{int((i+1) / 2)}:")
            self.entry_names[i] = QLineEdit(self)
            grid_layout.addWidget(label, i // 2, (i % 2) * 2)
            grid_layout.addWidget(self.entry_names[i], i // 2, (i % 2) * 2 + 1)
        # 上述按钮布局
        button_layout = QHBoxLayout()
        button_layout.addWidget(self.open_button)
        button_layout.addWidget(self.right_button)
        button_layout.addWidget(self.play_button)
        button_layout.addWidget(self.mid_button)
        button_layout.addWidget(self.left_button)
        button_layout.addWidget(self.start_button)
        button_layout.addWidget(self.end_button)

        # 左侧布局
        left_layout = QVBoxLayout()
        left_layout.addWidget(self.video_widget)
        left_layout.addWidget(self.label_time, alignment=Qt.AlignRight)

        left_layout.addWidget(self.timeSlider)

        left_layout.addLayout(button_layout)

        left_layout.addSpacing(100)

        left_layout.addWidget(QLabel("视频路径:"))
        left_layout.addWidget(self.path_entry)

        # 中间布局
        middle_layout = QVBoxLayout()
        middle_layout.addLayout(grid_layout)
        # 右侧布局
        right_layout = QVBoxLayout()
        right_layout.addWidget(self.done_button)
        # 总布局
        all_layout = QHBoxLayout()
        all_layout.addLayout(left_layout)
        all_layout.addLayout(middle_layout)
        all_layout.addLayout(right_layout)
        self.setLayout(all_layout)
        self.showMaximized()

    # 打开视频
    def open_file(self):
        a = QFileDialog.getOpenFileUrl()[0]
        self.video_path = a.toString()
        self.player.setMedia(QMediaContent(a))  # 选取视频文件
        msg = QMessageBox.information(self, '提示', "已经打开视频文件")
        self.path_entry.setText(self.video_path)


    # 调节播放进度
    def change_time(self, num):
        self.player.setPosition(num)

    # 快进
    def up_time(self):
        # print(self.player.duration())
        # num = self.player.position() + int(self.player.duration() / 20)
        num = self.player.position() + 200
        self.player.setPosition(num)

    # 快退
    def down_time(self):
        # num = self.player.position() - int(self.player.duration() / 20)
        num = self.player.position() - 200
        self.player.setPosition(num)

    # 获取进度条进度
    def get_time(self, num):
        self.timeSlider.setMaximum(self.player.duration())
        self.timeSlider.setValue(num)
        frame_count = int(num / 1000 * 30)
        # d = QDateTime.fromMSecsSinceEpoch(num).toString("mm:ss")
        # print(d)
        all = self.player.duration()
        total_count = int(all / 1000 * 30)
        # all_d = QDateTime.fromMSecsSinceEpoch(all).toString("mm:ss")
        self.label_time.setText(str(frame_count) + '/' + str(total_count))


    def closeEvent(self, event):  # 关闭前需要self.player.pause()操作,否则报错
        self.player.pause()
        reply = QMessageBox.question(self, '提示',
                                     "是否退出",
                                     QMessageBox.Yes | QMessageBox.No,
                                     QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def save_start_time(self):

        if self.save_flag == 0:
            self.save_flag = 1
            start_time = self.player.position()
            start_frame = int(start_time / 1000 * 30)
            self.entry_names[self.save_start_count-1].setText(str(start_frame))
            self.save_start_count += 2
            QMessageBox.information(self, "保存成功", f"已保存当前时间点:第{start_frame}帧 ")

        else:
            QMessageBox.information(self, "保存失败", f"请先保存动作结束时间 ")

    def save_end_time(self):
        if self.save_flag == 1:
            self.save_flag = 0
            end_time = self.player.position()
            end_frame = int(end_time / 1000 * 30)
            self.entry_names[self.save_end_count-1].setText(str(end_frame))
            self.save_end_count += 2
            QMessageBox.information(self, "保存成功", f"已保存当前时间点:第{end_frame}帧 ")
        else:
            QMessageBox.information(self, "保存失败", f"请先保存动作开始时间 ")

    def save_json(self):
        result = {}
        single_part = {}

        video_path = self.video_path
        print('当前保存结果来源于视频文件', video_path)
        result['video_path'] = video_path
        result['split_result'] = []
        # video_path: 'file:///D:/SplitVideo/dmh2.avi'
        file_path = re.split('/', video_path)[-1] + '.json'
        for i in range(len(self.entry_names)):
            if self.entry_names[i].text() != '':
                if (i + 1) % 2 == 1:
                    label_key = f"start_frame_d{int((i + 1) // 2 + 1)}:"
                else:
                    label_key = f"end_frame_d{int((i + 1) / 2)}:"
                single_part[label_key]= int(self.entry_names[i].text())
                # print(self.single_part)
        result['split_result'].append(single_part)

        with open(file_path, 'w') as f:
            json.dump(result, f)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.aboutToQuit.connect(app.deleteLater)
    win = VideoWin()
    win.show()
    sys.exit(app.exec())


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/755015.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python-docx 设置水印字体

本文目录 前言一、水印的XML在哪里1、Word内置水印设置2、自定义XML部件3、Header or Footer二、确认位置三、水印表前解释1、水印XML源代码2、水印结构解析3、关于style的详解三、修改水印样式前言 本文我们来完成一个有趣的玩意儿:在Python中通过操作Word文档的XML来设置整…

Maven - 在没有网络的情况下强制使用本地jar包

文章目录 问题解决思路解决办法删除 _remote.repositories 文件代码手动操作步骤验证 问题 非互联网环境,无法从中央仓库or镜像里拉取jar包。 服务器上搭建了一套Nexus私服。 Nexus私服故障,无法连接。 工程里新增了一个Jar的依赖, 本地仓…

如何利用React和Python构建强大的网络爬虫应用

如何利用React和Python构建强大的网络爬虫应用 引言: 网络爬虫是一种自动化程序,用于通过互联网抓取网页数据。随着互联网的不断发展和数据的爆炸式增长,网络爬虫越来越受欢迎。本文将介绍如何利用React和Python这两种流行的技术&#xff0c…

成功解决ES高亮内容引起的字段显示不一致问题

在处理搜索引擎(如Elasticsearch)结果时,常见需求之一是对用户搜索的关键词进行高亮显示,这有助于用户快速识别搜索结果为何与其查询相关。但在实际应用中,如果处理不当,直接使用高亮片段可能会导致原始数据…

SVN 的忽略(Ignore)和递归(Recursively)以及忽略部分

SVN中忽略大家经常用到,但总是似懂非懂,下面就详细展开说明一下忽略如何设置。 两个忽略 通常设置忽略都是文件夹和里面的文件都忽略。 设置忽略我们通常只需要鼠标右键点击忽略就可以了,如图: 第一个忽略用的最多,…

AI问答-供应链管理:中的长鞭效应(Bullwhip Effect)/ 供应链中需求信息变异放大现象

供应链管理中的长鞭效应(Bullwhip Effect)是一个经济学上的术语,它描述了供应链中需求信息变异放大的现象。以下是关于长鞭效应的详细解释: 一、定义 长鞭效应,也被称为“需求变异加速放大原理”或“牛鞭效应”&…

乐鑫 Matter 技术体验日|快速落地 Matter 产品,引领智能家居生态新发展

随着 Matter 协议的推广和普及,智能家居行业正迎来新的发展机遇,众多厂商纷纷投身于 Matter 产品的研发与验证。然而,开发者普遍面临技术门槛高、认证流程繁琐、生产管理复杂等诸多挑战。 乐鑫信息科技 (688018.SH) 凭借深厚的研发实力与行…

Python酷库之旅-第三方库openpyxl(15)

目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…

一、音视频基础

音视频基础 一、音视频录制原理二、音视频播放原理三、图像表示RGB-YUVV1.图像基础概念1.1 像素1.2 分辨率1.3 位深1.4 帧率1.5 码率1.6 Stride跨距 2.RGB、YUV深入讲解2.1 RGB2.2 YUV2.2.1 YUV采样表示法2.2.2 YUV数据存储 2.3 RGB和YUV的转换(了解)为什么解码出错显示绿屏&am…

借助 Aspose.Words,在 C# 中将 Word 转换为 Excel

有时我们会遇到需要将 Word 文档(DOC 或 DOCX)转换为 Excel 文档的任务。例如,这对于数据分析和报告很有用,或者如果您收到了任何文本数据并想将其转换为表格格式(XLS 或 XLSX)以便进一步工作。在本文中&am…

【DevExpress】WPF DevExpressMVVM 24.1版本开发指南

DevExpressMVVM WPF 环境安装 前言重要Bug(必看)环境安装控件目录Theme 主题LoginWindow 登陆窗口INavigationService 导航服务DockLayout Dock类型的画面布局TreeView 树状列表注意引用类型的时候ImageSource是PresentationCore程序集的博主找了好久&am…

AV Foundation学习笔记二 - 播放器

ASSets AVFoundation框架的最核心的类是AVAsset,该类是整个AVFoundation框架设计的中心。AVAsset是一个抽象的(意味着你不能调用AVAsset的alloc或者new方法来创建一个AVAsset实例对象,而是通过该类的静态方法来创建实例对象)、不…

社团成员信息系统

ER实体关系图与数据库模型 DDL CREATE TABLE club (club_id int(11) NOT NULL AUTO_INCREMENT,club_name varchar(100) NOT NULL,president_name varchar(50) DEFAULT NULL,foundation_date date DEFAULT NULL,description text,PRIMARY KEY (club_id),KEY president_name (pr…

DP(动态规划)【2】 最大连续子列和 最长不降子序列

1.最大连续子列和 #include <iostream> #include <vector> #include <cmath> #include <string> #include <cstring> #include <queue> using namespace std; const int N10002,maxn10;int n,m,k,f[N]{0},dp[N]{0};int main() {scanf(&quo…

1.SQL注入-数字型

SQL注入-数字型(post) 查询1的时候发现url后面的链接没有传入1的参数。验证为post请求方式&#xff0c;仅显示用户和邮箱 通过图中的显示的字段&#xff0c;我们可以猜测传入数据库里面的语句&#xff0c;例如&#xff1a; select 字段1,字段2 from 表名 where id1; 编辑一个…

【漏洞复现】宏景HCM人力资源信息管理系统——任意文件读取漏洞

声明&#xff1a;本文档或演示材料仅供教育和教学目的使用&#xff0c;任何个人或组织使用本文档中的信息进行非法活动&#xff0c;均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 宏景HCM人力资源信息管理系统是一款全面覆盖人力资源管理各模块…

GPT-4o首次引入!全新图像自动评估基准发布!

目录 01 什么是DreamBench&#xff1f; 02 与人类对齐的自动化评估 03 更全面的个性化数据集 04 实验结果 面对层出不穷的个性化图像生成技术&#xff0c;一个新问题摆在眼前&#xff1a;缺乏统一标准来衡量这些生成的图片是否符合人们的喜好。 对此&#xff0c;来自清华大…

心理辅导平台系统

摘 要 中文本论文基于Java Web技术设计与实现了一个心理辅导平台。通过对国内外心理辅导平台发展现状的调研&#xff0c;本文分析了心理辅导平台的背景与意义&#xff0c;并提出了论文研究内容与创新点。在相关技术介绍部分&#xff0c;对Java Web、SpringBoot、B/S架构、MVC模…

lvs+上一章的内容

书接上回这次加了个keepalived 一、集群与分布式 1.1 集群介绍 **集群&#xff08;Cluster&#xff09;**是将多台计算机组合成一个系统&#xff0c;以解决特定问题的计算机集合。集群系统可以分为以下三种类型&#xff1a; **LB&#xff08;Load Balancing&#xff0c;负载…

Golang | Leetcode Golang题解之第203题移除链表元素

题目&#xff1a; 题解&#xff1a; func removeElements(head *ListNode, val int) *ListNode {dummyHead : &ListNode{Next: head}for tmp : dummyHead; tmp.Next ! nil; {if tmp.Next.Val val {tmp.Next tmp.Next.Next} else {tmp tmp.Next}}return dummyHead.Next …