基于PyQt6的智能视频分割器实现

下面是一个基于PyQt6的智能视频分割器的完整实现方案。这个应用将允许用户加载视频文件,预览视频内容,并智能地分割视频片段。

图片[1]_基于PyQt6的智能视频分割器实现_知途无界

功能概述

  • 视频文件加载和预览
  • 智能场景检测分割
  • 手动分割点设置
  • 分割片段导出功能

完整代码实现

import sys
import cv2
import numpy as np
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
                             QPushButton, QLabel, QSlider, QFileDialog, QListWidget)
from PyQt6.QtCore import Qt, QTimer, QSize
from PyQt6.QtGui import QImage, QPixmap


class VideoSplitterApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("智能视频分割器")
        self.setGeometry(100, 100, 1200, 800)

        # 初始化变量
        self.video_path = None
        self.cap = None
        self.total_frames = 0
        self.fps = 0
        self.current_frame = 0
        self.scene_changes = []
        self.split_points = []

        # 创建UI
        self.init_ui()

    def init_ui(self):
        # 主窗口部件
        main_widget = QWidget()
        self.setCentralWidget(main_widget)

        # 主布局
        main_layout = QHBoxLayout()
        main_widget.setLayout(main_layout)

        # 左侧面板 - 视频预览和控制
        left_panel = QWidget()
        left_layout = QVBoxLayout()
        left_panel.setLayout(left_layout)

        # 视频显示区域
        self.video_label = QLabel("视频预览区域")
        self.video_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.video_label.setMinimumSize(800, 450)
        self.video_label.setStyleSheet("background-color: black; color: white;")
        left_layout.addWidget(self.video_label)

        # 控制面板
        control_panel = QWidget()
        control_layout = QHBoxLayout()
        control_panel.setLayout(control_layout)

        self.play_button = QPushButton("播放")
        self.play_button.clicked.connect(self.toggle_play)
        control_layout.addWidget(self.play_button)

        self.stop_button = QPushButton("停止")
        self.stop_button.clicked.connect(self.stop_video)
        control_layout.addWidget(self.stop_button)

        # 进度条
        self.progress_slider = QSlider(Qt.Orientation.Horizontal)
        self.progress_slider.setRange(0, 100)
        self.progress_slider.valueChanged.connect(self.slider_moved)
        control_layout.addWidget(self.progress_slider)

        left_layout.addWidget(control_panel)

        # 分割点控制
        split_control_panel = QWidget()
        split_control_layout = QHBoxLayout()
        split_control_panel.setLayout(split_control_layout)

        self.add_split_button = QPushButton("添加分割点")
        self.add_split_button.clicked.connect(self.add_split_point)
        split_control_layout.addWidget(self.add_split_button)

        self.detect_scenes_button = QPushButton("智能场景检测")
        self.detect_scenes_button.clicked.connect(self.detect_scene_changes)
        split_control_layout.addWidget(self.detect_scenes_button)

        self.export_button = QPushButton("导出分割片段")
        self.export_button.clicked.connect(self.export_segments)
        split_control_layout.addWidget(self.export_button)

        left_layout.addWidget(split_control_panel)

        # 右侧面板 - 分割点列表和状态
        right_panel = QWidget()
        right_layout = QVBoxLayout()
        right_panel.setLayout(right_layout)

        # 文件信息
        self.file_info_label = QLabel("未加载视频文件")
        right_layout.addWidget(self.file_info_label)

        # 分割点列表
        self.split_list = QListWidget()
        self.split_list.itemClicked.connect(self.split_point_selected)
        right_layout.addWidget(QLabel("分割点列表:"))
        right_layout.addWidget(self.split_list)

        # 状态信息
        self.status_label = QLabel("就绪")
        right_layout.addWidget(self.status_label)

        # 添加面板到主布局
        main_layout.addWidget(left_panel, 3)
        main_layout.addWidget(right_panel, 1)

        # 定时器用于视频播放
        self.timer = QTimer()
        self.timer.timeout.connect(self.update_frame)

        # 菜单栏
        self.create_menu_bar()

    def create_menu_bar(self):
        menubar = self.menuBar()

        # 文件菜单
        file_menu = menubar.addMenu("文件")

        open_action = file_menu.addAction("打开视频")
        open_action.triggered.connect(self.open_video)

        exit_action = file_menu.addAction("退出")
        exit_action.triggered.connect(self.close)

        # 工具菜单
        tool_menu = menubar.addMenu("工具")

        detect_action = tool_menu.addAction("智能场景检测")
        detect_action.triggered.connect(self.detect_scene_changes)

        export_action = tool_menu.addAction("导出所有片段")
        export_action.triggered.connect(self.export_segments)

    def open_video(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "打开视频文件", "", 
            "视频文件 (*.mp4 *.avi *.mov *.mkv);;所有文件 (*)"
        )

        if file_path:
            self.video_path = file_path
            self.cap = cv2.VideoCapture(file_path)

            if not self.cap.isOpened():
                self.status_label.setText("无法打开视频文件")
                return

            self.total_frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
            self.fps = self.cap.get(cv2.CAP_PROP_FPS)
            self.current_frame = 0
            self.scene_changes = []
            self.split_points = []
            self.split_list.clear()

            self.file_info_label.setText(
                f"文件: {file_path.split('/')[-1]}\n"
                f"总帧数: {self.total_frames}\n"
                f"FPS: {self.fps:.2f}\n"
                f"时长: {self.total_frames/self.fps:.2f}秒"
            )

            self.status_label.setText("视频加载成功")
            self.update_frame()

    def update_frame(self):
        if self.cap and self.cap.isOpened():
            ret, frame = self.cap.read()
            if ret:
                self.current_frame = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

                # 显示当前帧
                h, w, ch = frame.shape
                bytes_per_line = ch * w
                q_img = QImage(frame.data, w, h, bytes_per_line, QImage.Format.Format_RGB888)
                pixmap = QPixmap.fromImage(q_img)
                self.video_label.setPixmap(pixmap.scaled(
                    self.video_label.width(), 
                    self.video_label.height(), 
                    Qt.AspectRatioMode.KeepAspectRatio
                ))

                # 更新进度条
                progress = int((self.current_frame / self.total_frames) * 100)
                self.progress_slider.setValue(progress)

                # 检查是否到达分割点
                if self.current_frame in self.split_points:
                    self.status_label.setText(f"到达分割点: 帧 {self.current_frame}")
            else:
                self.stop_video()

    def toggle_play(self):
        if not self.cap:
            return

        if self.timer.isActive():
            self.timer.stop()
            self.play_button.setText("播放")
        else:
            self.timer.start(1000 / self.fps)
            self.play_button.setText("暂停")

    def stop_video(self):
        self.timer.stop()
        if self.cap:
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
            self.current_frame = 0
            self.progress_slider.setValue(0)
            self.play_button.setText("播放")
            self.update_frame()

    def slider_moved(self, value):
        if self.cap:
            frame_pos = int((value / 100) * self.total_frames)
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos)
            self.current_frame = frame_pos
            self.update_frame()

    def add_split_point(self):
        if self.cap and self.cap.isOpened():
            frame_pos = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
            if frame_pos not in self.split_points:
                self.split_points.append(frame_pos)
                self.split_points.sort()
                self.update_split_list()
                self.status_label.setText(f"添加分割点: 帧 {frame_pos}")

    def update_split_list(self):
        self.split_list.clear()
        for point in sorted(self.split_points):
            time_sec = point / self.fps
            self.split_list.addItem(f"帧 {point} - {time_sec:.2f}秒")

    def split_point_selected(self, item):
        text = item.text()
        frame_num = int(text.split()[1])
        if self.cap:
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num)
            self.update_frame()

    def detect_scene_changes(self):
        if not self.cap:
            return

        self.status_label.setText("正在检测场景变化...")
        QApplication.processEvents()  # 更新UI

        # 重置场景变化检测
        self.scene_changes = []

        # 保存当前帧位置
        current_pos = self.current_frame
        self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)

        # 场景检测参数
        threshold = 30.0  # 场景变化阈值
        min_scene_len = int(self.fps * 1)  # 最小场景长度(1秒)

        prev_frame = None
        scene_start = 0

        while True:
            ret, frame = self.cap.read()
            if not ret:
                break

            current_frame_num = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))

            # 转换为灰度并缩小尺寸以提高性能
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            gray = cv2.resize(gray, (0, 0), fx=0.25, fy=0.25)

            if prev_frame is not None:
                # 计算帧间差异
                diff = cv2.absdiff(gray, prev_frame)
                diff_mean = np.mean(diff)

                # 如果差异超过阈值且满足最小场景长度,则认为是场景变化
                if diff_mean > threshold and (current_frame_num - scene_start) >= min_scene_len:
                    self.scene_changes.append(current_frame_num)
                    scene_start = current_frame_num

            prev_frame = gray

        # 恢复原始位置
        self.cap.set(cv2.CAP_PROP_POS_FRAMES, current_pos)

        # 更新分割点列表
        self.split_points = list(set(self.split_points + self.scene_changes))
        self.split_points.sort()
        self.update_split_list()

        self.status_label.setText(f"检测到 {len(self.scene_changes)} 个场景变化")

    def export_segments(self):
        if not self.cap or not self.split_points:
            self.status_label.setText("没有可导出的分割点")
            return

        # 获取保存目录
        save_dir = QFileDialog.getExistingDirectory(self, "选择保存目录")
        if not save_dir:
            return

        self.status_label.setText("正在导出视频片段...")
        QApplication.processEvents()  # 更新UI

        # 确保分割点包含视频开始和结束
        all_points = [0] + self.split_points + [self.total_frames]
        all_points = sorted(list(set(all_points)))  # 去重并排序

        # 导出每个片段
        for i in range(len(all_points) - 1):
            start_frame = all_points[i]
            end_frame = all_points[i+1]

            # 跳过太短的片段(小于0.5秒)
            if (end_frame - start_frame) < (self.fps * 0.5):
                continue

            # 设置输出视频
            output_path = f"{save_dir}/segment_{i+1}_frames_{start_frame}-{end_frame}.mp4"
            fourcc = cv2.VideoWriter_fourcc(*'mp4v')
            out = cv2.VideoWriter(
                output_path, 
                fourcc, 
                self.fps, 
                (int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)), 
                 int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
            )

            # 定位到起始帧
            self.cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)

            # 写入视频帧
            while self.cap.get(cv2.CAP_PROP_POS_FRAMES) < end_frame:
                ret, frame = self.cap.read()
                if ret:
                    out.write(frame)
                else:
                    break

            out.release()

        # 恢复原始位置
        self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.current_frame)

        self.status_label.setText(f"成功导出 {len(all_points)-1} 个视频片段")

    def closeEvent(self, event):
        if self.cap:
            self.cap.release()
        event.accept()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = VideoSplitterApp()
    window.show()
    sys.exit(app.exec())

功能说明

  1. 视频加载与预览
  • 支持多种视频格式(mp4, avi, mov, mkv)
  • 显示视频基本信息(帧数、FPS、时长)
  • 提供播放/暂停、停止和进度条控制
  1. 智能场景检测
  • 基于帧间差异分析自动检测场景变化
  • 可配置阈值和最小场景长度
  • 检测结果自动添加到分割点列表
  1. 手动分割
  • 可在任意位置添加手动分割点
  • 分割点列表显示帧号和时间位置
  • 点击列表项可跳转到对应帧
  1. 片段导出
  • 可选择保存目录
  • 自动跳过过短片段
  • 保留原始视频参数(分辨率、FPS等)

扩展建议

  1. 性能优化
  • 添加多线程处理避免UI卡顿
  • 实现视频帧缓存提高播放流畅度
  1. 高级功能
  • 添加音频波形显示辅助分割
  • 实现基于内容的智能分割(人脸检测、运动检测等)
  • 支持批量处理多个视频文件
  1. 用户体验
  • 添加快捷键支持
  • 实现撤销/重做功能
  • 添加更多视频编辑功能(裁剪、合并等)

这个实现提供了视频分割的基本功能框架,您可以根据需要进一步扩展和完善。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞83 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容