亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

基于PyQt6實(shí)現(xiàn)智能視頻分割器

 更新時(shí)間:2025年05月16日 14:16:25   作者:創(chuàng)客白澤  
這篇文章主要為大家詳細(xì)介紹了如何使用PyQt6打造一款支持拖拽操作,三種分割模式,高顏值界面的視頻分割神器,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

一、開(kāi)篇碎碎念

最近在整理旅行視頻時(shí),發(fā)現(xiàn)需要把長(zhǎng)視頻按場(chǎng)景分割成小片段。試了幾款工具都不夠順手,要么操作復(fù)雜,要么界面丑陋。作為技術(shù)人,當(dāng)然要自己造輪子!

本文將帶你用PyQt6打造一款支持拖拽操作、三種分割模式、高顏值界面的視頻分割神器。最重要的是——完整開(kāi)源!文末提供源碼下載~

(效果預(yù)覽圖)

二、功能全景圖

2.1 核心功能清單

功能模塊技術(shù)實(shí)現(xiàn)亮點(diǎn)
拖拽文件支持Qt6 DnD API拖拽即識(shí)別,邊框動(dòng)態(tài)反饋
三種分割模式FFmpeg子進(jìn)程按秒/按段/區(qū)間三種策略
實(shí)時(shí)進(jìn)度展示QProgressBar平滑動(dòng)畫(huà)+百分比計(jì)算
智能時(shí)長(zhǎng)解析FFprobe調(diào)用自動(dòng)識(shí)別視頻元數(shù)據(jù)
跨平臺(tái)支持Python封裝Win/macOS/Linux全兼容

2.2 技術(shù)選型對(duì)比

三、UI設(shè)計(jì)詳解

3.1 色彩方案

采用藍(lán)綠漸變科技風(fēng)配色:

COLOR_SCHEME = {
    'primary': '#5D9CEC',  # 主按鈕藍(lán)
    'success': '#4CAF50',  # 成功綠
    'danger': '#F44336',   # 警告紅
    'background': '#F0F7FA' # 背景淺藍(lán)
}

3.2 關(guān)鍵界面組件

拖拽文件區(qū):虛線(xiàn)邊框+懸浮高亮

   QLabel {
       border: 2px dashed #aaa;
       border-radius: 5px;
       background: rgba(255,255,255,0.7);
   }
   QLabel:hover { border-color: #5D9CEC; }

動(dòng)態(tài)進(jìn)度條:

   self.progress.setStyleSheet("""
       QProgressBar::chunk {
           background: qlineargradient(
               x1:0, y1:0, x2:1, y2:0,
               stop:0 #4CAF50, stop:1 #8BC34A);
       }
   """)

四、手把手教學(xué):從安裝到使用

4.1 環(huán)境準(zhǔn)備

# 必備依賴(lài)
pip install PyQt6 moviepy
# FFmpeg安裝(Windows示例)
choco install ffmpeg

4.2 視頻分割處理流程

五、核心代碼解析

5.1 拖拽處理三件套

def dragEnterEvent(self, event):
    """文件拖入時(shí)觸發(fā)"""
    if event.mimeData().hasUrls():
        event.acceptProposedAction()

def dropEvent(self, event):
    """文件放下時(shí)處理"""
    file_path = event.mimeData().urls()[0].toLocalFile()
    self.process_video(file_path)

5.2 FFmpeg多線(xiàn)程調(diào)用

Thread(target=lambda: 
    subprocess.run([
        'ffmpeg', '-ss', start_time,
        '-i', input_path,
        '-t', duration,
        '-c', 'copy', output_path
    ], check=True)
).start()

5.3 智能區(qū)間計(jì)算

def calculate_intervals(self):
    """自動(dòng)計(jì)算合理分段"""
    duration = self.get_duration()
    if mode == "seconds":
        return [(i*interval, (i+1)*interval) 
               for i in range(math.ceil(duration/interval))]

六、源碼下載

import os
import math
import subprocess
import json
import re
from threading import Thread
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                            QLabel, QPushButton, QLineEdit, QRadioButton, QButtonGroup,
                            QProgressBar, QListWidget, QFileDialog, QMessageBox,
                            QDialog, QFrame, QListWidgetItem)
from PyQt6.QtCore import Qt, QSize, QMimeData, QUrl
from PyQt6.QtGui import QIcon, QFont, QColor, QDragEnterEvent, QDropEvent


class VideoProcessor:
    def __init__(self):
        self.source = ""
        self.running = True

    def set_source(self, path):
        """設(shè)置視頻源文件"""
        if not os.path.exists(path):
            raise FileNotFoundError("文件不存在")
        self.source = path

    def start(self, mode, param, progress_callback, status_callback):
        """處理入口"""
        duration = self.get_duration()

        if mode == "seconds":
            self.split_by_seconds(param, duration, progress_callback, status_callback)
        elif mode == "segments":
            self.split_by_segments(param, duration, progress_callback, status_callback)
        elif mode == "intervals":
            self.split_by_intervals(param, duration, progress_callback, status_callback)
        else:
            raise ValueError(f"不支持的分割模式: {mode}")

    def split_by_seconds(self, interval, duration, progress_callback, status_callback):
        """秒級(jí)分割邏輯"""
        full_segments = int(duration // interval)
        remaining = duration % interval

        actual_segments = full_segments + (1 if remaining >= 1 else 0)
        output_dir = self.create_output_dir("秒分割")

        base_name, ext = os.path.splitext(os.path.basename(self.source))
        for i in range(actual_segments):
            if not self.running:
                break
            start = i * interval
            end = (i + 1) * interval if i < actual_segments - 1 else duration

            output_name = f"{base_name}_副本_{i + 1}{ext}"
            subprocess.run([
                'ffmpeg', '-y', '-ss', str(start), '-i', self.source,
                '-t', str(end - start), '-c', 'copy',
                os.path.join(output_dir, output_name)
            ], check=True)
            progress_callback((i + 1) / actual_segments * 100)
            status_callback(f"生成分段 {i + 1}/{actual_segments}")

    def split_by_segments(self, count, duration, progress_callback, status_callback):
        """分段模式處理"""
        interval = duration / count
        output_dir = self.create_output_dir("段分割")

        base_name, ext = os.path.splitext(os.path.basename(self.source))
        for i in range(count):
            if not self.running:
                break
            start = i * interval
            end = duration if i == count - 1 else (i + 1) * interval

            output_name = f"{base_name}_副本_{i + 1}{ext}"
            subprocess.run([
                'ffmpeg', '-y', '-ss', str(start), '-i', self.source,
                '-t', str(end - start), '-c', 'copy',
                os.path.join(output_dir, output_name)
            ], check=True)
            progress_callback((i + 1) / count * 100)
            status_callback(f"處理進(jìn)度 {i + 1}/{count}")

    def split_by_intervals(self, intervals, duration, progress_callback, status_callback):
        """區(qū)間提取邏輯"""
        valid_intervals = []
        for interval in intervals:
            start, end = interval
            if 0 <= start < end <= duration and end - start >= 0.5:
                valid_intervals.append((start, end))

        if not valid_intervals:
            raise ValueError("沒(méi)有有效的提取區(qū)間")

        output_dir = self.create_output_dir("區(qū)間提取")

        base_name, ext = os.path.splitext(os.path.basename(self.source))
        total = len(valid_intervals)
        for i, (start, end) in enumerate(valid_intervals):
            if not self.running:
                break

            start_hour, start_minute, start_second = self.seconds_to_hms(start)
            end_hour, end_minute, end_second = self.seconds_to_hms(end)
            output_name = f"{base_name}_副本_{i + 1}_{start_hour:02d}{start_minute:02d}{start_second:02d}-{end_hour:02d}{end_minute:02d}{end_second:02d}{ext}"
            subprocess.run([
                'ffmpeg', '-y', '-ss', str(start), '-i', self.source,
                '-t', str(end - start), '-c', 'copy',
                os.path.join(output_dir, output_name)
            ], check=True)

            progress_callback((i + 1) / total * 100)
            status_callback(f"提取區(qū)間 {i + 1}/{total}: {start_hour:02d}:{start_minute:02d}:{start_second:02d}-{end_hour:02d}:{end_minute:02d}:{end_second:02d}")

        return output_dir

    def get_duration(self):
        """獲取視頻時(shí)長(zhǎng)"""
        try:
            cmd = [
                'ffprobe', '-v', 'error', '-show_entries', 
                'format=duration', '-of', 'json', self.source
            ]
            result = subprocess.run(cmd, capture_output=True, text=True, check=True)
            data = json.loads(result.stdout)
            duration = float(data['format']['duration'])
            return duration
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"讀取時(shí)長(zhǎng)失敗: {e.stderr}")
        except (json.JSONDecodeError, KeyError, ValueError) as e:
            raise RuntimeError(f"解析視頻信息失敗: {str(e)}")
        except Exception as e:
            raise RuntimeError(f"讀取時(shí)長(zhǎng)失敗: {str(e)}")

    def create_output_dir(self, prefix):
        """創(chuàng)建輸出目錄"""
        output_dir = os.path.join(os.path.dirname(self.source), f"{prefix}_輸出")
        os.makedirs(output_dir, exist_ok=True)
        return output_dir

    def seconds_to_hms(self, seconds):
        """將秒轉(zhuǎn)換為小時(shí)、分鐘、秒"""
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        return hours, minutes, secs

    def hms_to_seconds(self, hours, minutes, seconds):
        """將小時(shí)、分鐘、秒轉(zhuǎn)換為秒"""
        return hours * 3600 + minutes * 60 + seconds


class AddIntervalDialog(QDialog):
    def __init__(self, parent, max_duration):
        super().__init__(parent)
        self.setWindowTitle("添加區(qū)間")
        self.setWindowModality(Qt.WindowModality.ApplicationModal)
        self.setFixedSize(350, 200)
        
        self.max_duration = max_duration
        self.result = None
        
        self.init_ui()
        
    def init_ui(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(15, 15, 15, 15)
        
        info_label = QLabel(f"請(qǐng)輸入時(shí)間區(qū)間 (范圍: 0-{self.format_time(self.max_duration)})")
        info_label.setStyleSheet("color: #555;")
        layout.addWidget(info_label)
        
        # 開(kāi)始時(shí)間輸入
        start_frame = QWidget()
        start_layout = QHBoxLayout(start_frame)
        start_layout.setContentsMargins(0, 0, 0, 0)
        
        start_layout.addWidget(QLabel("開(kāi)始時(shí)間:"))
        self.start_hour = QLineEdit()
        self.start_hour.setFixedWidth(30)
        self.start_hour.setStyleSheet("background: #f8f8f8;")
        start_layout.addWidget(self.start_hour)
        start_layout.addWidget(QLabel("時(shí)"))
        
        self.start_minute = QLineEdit()
        self.start_minute.setFixedWidth(30)
        self.start_minute.setStyleSheet("background: #f8f8f8;")
        start_layout.addWidget(self.start_minute)
        start_layout.addWidget(QLabel("分"))
        
        self.start_second = QLineEdit()
        self.start_second.setFixedWidth(30)
        self.start_second.setStyleSheet("background: #f8f8f8;")
        start_layout.addWidget(self.start_second)
        start_layout.addWidget(QLabel("秒"))
        
        start_layout.addStretch()
        layout.addWidget(start_frame)
        
        # 結(jié)束時(shí)間輸入
        end_frame = QWidget()
        end_layout = QHBoxLayout(end_frame)
        end_layout.setContentsMargins(0, 0, 0, 0)
        
        end_layout.addWidget(QLabel("結(jié)束時(shí)間:"))
        self.end_hour = QLineEdit()
        self.end_hour.setFixedWidth(30)
        self.end_hour.setStyleSheet("background: #f8f8f8;")
        end_layout.addWidget(self.end_hour)
        end_layout.addWidget(QLabel("時(shí)"))
        
        self.end_minute = QLineEdit()
        self.end_minute.setFixedWidth(30)
        self.end_minute.setStyleSheet("background: #f8f8f8;")
        end_layout.addWidget(self.end_minute)
        end_layout.addWidget(QLabel("分"))
        
        self.end_second = QLineEdit()
        self.end_second.setFixedWidth(30)
        self.end_second.setStyleSheet("background: #f8f8f8;")
        end_layout.addWidget(self.end_second)
        end_layout.addWidget(QLabel("秒"))
        
        end_layout.addStretch()
        layout.addWidget(end_frame)
        
        # 按鈕區(qū)
        btn_frame = QWidget()
        btn_layout = QHBoxLayout(btn_frame)
        btn_layout.setContentsMargins(0, 0, 0, 0)
        
        self.ok_btn = QPushButton("確定")
        self.ok_btn.setStyleSheet("""
            QPushButton {
                background: #4CAF50;
                color: white;
                border: none;
                padding: 5px 15px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background: #45a049;
            }
        """)
        self.ok_btn.clicked.connect(self.on_ok)
        btn_layout.addStretch()
        btn_layout.addWidget(self.ok_btn)
        
        cancel_btn = QPushButton("取消")
        cancel_btn.setStyleSheet("""
            QPushButton {
                background: #f44336;
                color: white;
                border: none;
                padding: 5px 15px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background: #d32f2f;
            }
        """)
        cancel_btn.clicked.connect(self.reject)
        btn_layout.addWidget(cancel_btn)
        
        layout.addWidget(btn_frame)
        
        self.setLayout(layout)
        
    def on_ok(self):
        try:
            start_h = int(self.start_hour.text() or 0)
            start_m = int(self.start_minute.text() or 0)
            start_s = int(self.start_second.text() or 0)
            
            end_h = int(self.end_hour.text() or 0)
            end_m = int(self.end_minute.text() or 0)
            end_s = int(self.end_second.text() or 0)
            
            start_seconds = start_h * 3600 + start_m * 60 + start_s
            end_seconds = end_h * 3600 + end_m * 60 + end_s
            
            if start_seconds < 0 or start_seconds >= self.max_duration:
                QMessageBox.warning(self, "錯(cuò)誤", f"開(kāi)始時(shí)間必須在0到{self.format_time(self.max_duration)}之間")
                return
                
            if end_seconds <= start_seconds or end_seconds > self.max_duration:
                QMessageBox.warning(self, "錯(cuò)誤", f"結(jié)束時(shí)間必須大于開(kāi)始時(shí)間且不超過(guò){self.format_time(self.max_duration)}")
                return
                
            if end_seconds - start_seconds < 0.5:
                QMessageBox.warning(self, "錯(cuò)誤", "區(qū)間長(zhǎng)度太短(最少0.5秒)")
                return
                
            self.result = (start_seconds, end_seconds)
            self.accept()
            
        except ValueError:
            QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)輸入有效的數(shù)字")
    
    def format_time(self, seconds):
        """將秒轉(zhuǎn)換為 hh:mm:ss 格式"""
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        return f"{hours:02d}:{minutes:02d}:{secs:02d}"


class VideoSplitterUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("智能視頻分割器")
        self.setMinimumSize(650, 600)
        
        self.processor = VideoProcessor()
        self.running = False
        self.intervals = []
        
        self.init_ui()
        self.center_window()
        
        # 啟用拖放功能
        self.setAcceptDrops(True)
        
    def center_window(self):
        """使窗口居中顯示"""
        screen = QApplication.primaryScreen().geometry()
        size = self.geometry()
        self.move((screen.width() - size.width()) // 2, 
                 (screen.height() - size.height()) // 2)
        
    def init_ui(self):
        """初始化UI界面"""
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        
        # 主布局
        layout = QVBoxLayout()
        layout.setContentsMargins(25, 25, 25, 25)
        layout.setSpacing(15)
        
        # 文件選擇區(qū)
        file_frame = QWidget()
        file_frame.setStyleSheet("""
            QWidget {
                background: #f0f7fa;
                border-radius: 8px;
                padding: 10px;
            }
        """)
        file_layout = QHBoxLayout(file_frame)
        file_layout.setContentsMargins(10, 5, 10, 5)
        
        self.select_btn = QPushButton("選擇文件")
        self.select_btn.setFixedWidth(100)
        self.select_btn.setStyleSheet("""
            QPushButton {
                background: #5D9CEC;
                color: white;
                border: none;
                padding: 6px;
                border-radius: 5px;
                font-weight: bold;
            }
            QPushButton:hover {
                background: #4A89DC;
            }
        """)
        self.select_btn.clicked.connect(self.select_file)
        file_layout.addWidget(self.select_btn)
        
        self.file_label = QLabel("拖放視頻文件到這里或點(diǎn)擊按鈕選擇")
        self.file_label.setStyleSheet("""
            QLabel {
                color: #555;
                font-size: 13px;
                padding: 5px;
                border: 2px dashed #aaa;
                border-radius: 5px;
                background: rgba(255,255,255,0.7);
            }
        """)
        self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        file_layout.addWidget(self.file_label, 1)
        
        layout.addWidget(file_frame)
        
        # 視頻信息區(qū)
        self.info_label = QLabel("視頻時(shí)長(zhǎng): 未知")
        self.info_label.setStyleSheet("""
            QLabel {
                color: #4A708B;
                font-size: 13px;
                font-weight: bold;
                padding: 5px;
                background: #E0F2F7;
                border-radius: 5px;
            }
        """)
        self.info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        layout.addWidget(self.info_label)
        
        # 分割線(xiàn)
        line = QFrame()
        line.setFrameShape(QFrame.Shape.HLine)
        line.setFrameShadow(QFrame.Shadow.Sunken)
        line.setStyleSheet("color: #d5e6eb;")
        layout.addWidget(line)
        
        # 模式選擇區(qū)
        mode_frame = QWidget()
        mode_frame.setStyleSheet("""
            QWidget {
                background: #f5f9fa;
                border-radius: 8px;
                padding: 10px;
            }
        """)
        mode_layout = QHBoxLayout(mode_frame)
        mode_layout.setContentsMargins(15, 5, 15, 5)
        
        self.mode_group = QButtonGroup()
        
        self.seconds_radio = QRadioButton("按秒分割")
        self.seconds_radio.setChecked(True)
        self.seconds_radio.setStyleSheet("""
            QRadioButton {
                color: #555;
                font-size: 13px;
            }
            QRadioButton::indicator {
                width: 16px;
                height: 16px;
            }
        """)
        self.mode_group.addButton(self.seconds_radio, 0)
        mode_layout.addWidget(self.seconds_radio)
        
        self.segments_radio = QRadioButton("按段分割")
        self.segments_radio.setStyleSheet("""
            QRadioButton {
                color: #555;
                font-size: 13px;
            }
            QRadioButton::indicator {
                width: 16px;
                height: 16px;
            }
        """)
        self.mode_group.addButton(self.segments_radio, 1)
        mode_layout.addWidget(self.segments_radio)
        
        self.intervals_radio = QRadioButton("區(qū)間提取")
        self.intervals_radio.setStyleSheet("""
            QRadioButton {
                color: #555;
                font-size: 13px;
            }
            QRadioButton::indicator {
                width: 16px;
                height: 16px;
            }
        """)
        self.mode_group.addButton(self.intervals_radio, 2)
        mode_layout.addWidget(self.intervals_radio)
        
        mode_layout.addStretch()
        layout.addWidget(mode_frame)
        
        # 參數(shù)輸入?yún)^(qū)
        self.param_frame = QWidget()
        self.param_frame.setStyleSheet("""
            QWidget {
                background: #f5f9fa;
                border-radius: 8px;
                padding: 10px;
            }
        """)
        param_layout = QHBoxLayout(self.param_frame)
        param_layout.setContentsMargins(15, 5, 15, 5)
        
        self.param_label = QLabel("間隔秒數(shù):")
        self.param_label.setStyleSheet("color: #555;")
        param_layout.addWidget(self.param_label)
        
        self.param_entry = QLineEdit()
        self.param_entry.setFixedWidth(80)
        self.param_entry.setStyleSheet("""
            QLineEdit {
                background: white;
                border: 1px solid #ccc;
                border-radius: 4px;
                padding: 3px;
            }
        """)
        self.param_entry.textChanged.connect(self.auto_calculate)
        param_layout.addWidget(self.param_entry)
        
        self.result_label = QLabel("預(yù)計(jì)段數(shù): 0")
        self.result_label.setStyleSheet("color: #666; font-weight: bold;")
        param_layout.addWidget(self.result_label)
        param_layout.addStretch()
        
        layout.addWidget(self.param_frame)
        
        # 區(qū)間提取區(qū)
        self.intervals_frame = QWidget()
        self.intervals_frame.setStyleSheet("""
            QWidget {
                background: #f5f9fa;
                border-radius: 8px;
                padding: 10px;
            }
        """)
        intervals_layout = QVBoxLayout(self.intervals_frame)
        intervals_layout.setContentsMargins(10, 10, 10, 10)
        
        intervals_title = QLabel("區(qū)間提取")
        intervals_title.setStyleSheet("""
            QLabel {
                font-weight: bold;
                color: #4A708B;
                font-size: 14px;
            }
        """)
        intervals_layout.addWidget(intervals_title)
        
        # 區(qū)間列表
        self.intervals_list = QListWidget()
        self.intervals_list.setMinimumHeight(120)
        self.intervals_list.setStyleSheet("""
            QListWidget {
                background: white;
                border: 1px solid #ccc;
                border-radius: 5px;
                padding: 5px;
                font-size: 12px;
            }
            QListWidget::item {
                padding: 5px;
                border-bottom: 1px solid #eee;
            }
            QListWidget::item:selected {
                background: #D6EAF8;
                color: #333;
            }
        """)
        intervals_layout.addWidget(self.intervals_list)
        
        # 區(qū)間控制按鈕
        intervals_btn_frame = QWidget()
        intervals_btn_layout = QHBoxLayout(intervals_btn_frame)
        intervals_btn_layout.setContentsMargins(0, 0, 0, 0)
        
        self.add_interval_btn = QPushButton("添加區(qū)間")
        self.add_interval_btn.setStyleSheet("""
            QPushButton {
                background: #5D9CEC;
                color: white;
                border: none;
                padding: 5px 12px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background: #4A89DC;
            }
        """)
        self.add_interval_btn.clicked.connect(self.add_interval)
        intervals_btn_layout.addWidget(self.add_interval_btn)
        
        self.remove_interval_btn = QPushButton("刪除選中")
        self.remove_interval_btn.setStyleSheet("""
            QPushButton {
                background: #F78181;
                color: white;
                border: none;
                padding: 5px 12px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background: #E74C3C;
            }
        """)
        self.remove_interval_btn.clicked.connect(self.remove_interval)
        intervals_btn_layout.addWidget(self.remove_interval_btn)
        
        self.clear_interval_btn = QPushButton("清空列表")
        self.clear_interval_btn.setStyleSheet("""
            QPushButton {
                background: #A4A4A4;
                color: white;
                border: none;
                padding: 5px 12px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background: #848484;
            }
        """)
        self.clear_interval_btn.clicked.connect(self.clear_intervals)
        intervals_btn_layout.addWidget(self.clear_interval_btn)
        
        intervals_btn_layout.addStretch()
        intervals_layout.addWidget(intervals_btn_frame)
        
        layout.addWidget(self.intervals_frame)
        self.intervals_frame.hide()
        
        # 控制區(qū)
        ctrl_frame = QWidget()
        ctrl_frame.setStyleSheet("""
            QWidget {
                background: #f0f7fa;
                border-radius: 8px;
                padding: 10px;
            }
        """)
        ctrl_layout = QHBoxLayout(ctrl_frame)
        ctrl_layout.setContentsMargins(10, 5, 10, 5)
        
        self.start_btn = QPushButton("開(kāi)始分割")
        self.start_btn.setFixedWidth(120)
        self.start_btn.setStyleSheet("""
            QPushButton {
                background: #4CAF50;
                color: white;
                border: none;
                padding: 8px;
                border-radius: 5px;
                font-weight: bold;
                font-size: 14px;
            }
            QPushButton:hover {
                background: #45a049;
            }
        """)
        self.start_btn.clicked.connect(self.toggle_process)
        ctrl_layout.addWidget(self.start_btn)
        
        self.open_output_btn = QPushButton("打開(kāi)輸出目錄")
        self.open_output_btn.setStyleSheet("""
            QPushButton {
                background: #5D9CEC;
                color: white;
                border: none;
                padding: 6px 12px;
                border-radius: 5px;
            }
            QPushButton:hover {
                background: #4A89DC;
            }
        """)
        self.open_output_btn.clicked.connect(self.open_output)
        ctrl_layout.addWidget(self.open_output_btn)
        
        ctrl_layout.addStretch()
        layout.addWidget(ctrl_frame)
        
        # 進(jìn)度條
        self.progress = QProgressBar()
        self.progress.setRange(0, 100)
        self.progress.setTextVisible(False)
        self.progress.setStyleSheet("""
            QProgressBar {
                border: 1px solid #ccc;
                border-radius: 5px;
                text-align: center;
                background: #f5f5f5;
                height: 15px;
            }
            QProgressBar::chunk {
                background: #4CAF50;
                border-radius: 4px;
            }
        """)
        layout.addWidget(self.progress)
        
        # 狀態(tài)欄
        self.status_label = QLabel("就緒")
        self.status_label.setStyleSheet("""
            QLabel {
                color: #666;
                font-size: 12px;
                padding: 5px;
                background: #f5f5f5;
                border-radius: 5px;
            }
        """)
        layout.addWidget(self.status_label)
        
        # 連接信號(hào)
        self.mode_group.buttonClicked.connect(self.on_mode_changed)
        
        main_widget.setLayout(layout)
    
    def dragEnterEvent(self, event: QDragEnterEvent):
        """拖拽進(jìn)入事件"""
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if len(urls) == 1 and urls[0].isLocalFile():
                file_path = urls[0].toLocalFile()
                if re.search(r'\.(mp4|avi|mkv|mov|flv|wmv)$', file_path, re.I):
                    event.acceptProposedAction()
                    self.file_label.setStyleSheet("""
                        QLabel {
                            color: #555;
                            font-size: 13px;
                            padding: 5px;
                            border: 2px dashed #5D9CEC;
                            border-radius: 5px;
                            background: rgba(93, 156, 236, 0.1);
                        }
                    """)
                    return
        
        event.ignore()
    
    def dragLeaveEvent(self, event):
        """拖拽離開(kāi)事件"""
        self.file_label.setStyleSheet("""
            QLabel {
                color: #555;
                font-size: 13px;
                padding: 5px;
                border: 2px dashed #aaa;
                border-radius: 5px;
                background: rgba(255,255,255,0.7);
            }
        """)
    
    def dropEvent(self, event: QDropEvent):
        """拖放事件"""
        self.file_label.setStyleSheet("""
            QLabel {
                color: #555;
                font-size: 13px;
                padding: 5px;
                border: 2px dashed #aaa;
                border-radius: 5px;
                background: rgba(255,255,255,0.7);
            }
        """)
        
        urls = event.mimeData().urls()
        if urls and urls[0].isLocalFile():
            file_path = urls[0].toLocalFile()
            if re.search(r'\.(mp4|avi|mkv|mov|flv|wmv)$', file_path, re.I):
                try:
                    self.processor.set_source(file_path)
                    self.file_label.setText(os.path.basename(file_path))
                    
                    duration = self.processor.get_duration()
                    mins, secs = divmod(duration, 60)
                    hours, mins = divmod(mins, 60)
                    time_str = f"{int(hours)}:{int(mins):02d}:{secs:.2f}" if hours else f"{int(mins)}:{secs:.2f}"
                    self.info_label.setText(f"視頻時(shí)長(zhǎng): {time_str} ({duration:.2f}秒)")
                    
                    self.auto_calculate()
                except Exception as e:
                    QMessageBox.critical(self, "錯(cuò)誤", str(e))
            else:
                QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)拖入有效的視頻文件 (MP4, AVI, MKV, MOV, FLV, WMV)")
    
    def select_file(self):
        """選擇視頻文件"""
        path, _ = QFileDialog.getOpenFileName(
            self, "選擇視頻文件", "", 
            "視頻文件 (*.mp4 *.avi *.mkv *.mov *.flv *.wmv)"
        )
        
        if path:
            try:
                self.processor.set_source(path)
                self.file_label.setText(os.path.basename(path))
                
                duration = self.processor.get_duration()
                mins, secs = divmod(duration, 60)
                hours, mins = divmod(mins, 60)
                time_str = f"{int(hours)}:{int(mins):02d}:{secs:.2f}" if hours else f"{int(mins)}:{secs:.2f}"
                self.info_label.setText(f"視頻時(shí)長(zhǎng): {time_str} ({duration:.2f}秒)")
                
                self.auto_calculate()
            except Exception as e:
                QMessageBox.critical(self, "錯(cuò)誤", str(e))
    
    def on_mode_changed(self, button):
        """模式變化時(shí)更新UI"""
        if button == self.seconds_radio:
            self.param_label.setText("間隔秒數(shù):")
            self.param_frame.show()
            self.intervals_frame.hide()
        elif button == self.segments_radio:
            self.param_label.setText("分割為段數(shù):")
            self.param_frame.show()
            self.intervals_frame.hide()
        else:
            self.param_frame.hide()
            self.intervals_frame.show()
            
        self.auto_calculate()
    
    def auto_calculate(self):
        """自動(dòng)計(jì)算分割參數(shù)預(yù)覽"""
        try:
            if not self.processor.source:
                self.result_label.setText("請(qǐng)先選擇文件")
                return
                
            if self.seconds_radio.isChecked():
                if not self.param_entry.text().strip():
                    self.result_label.setText("請(qǐng)輸入秒數(shù)")
                    return
                    
                param = float(self.param_entry.text())
                if param <= 0:
                    self.result_label.setText("請(qǐng)輸入大于0的值")
                    return
                    
                duration = self.processor.get_duration()
                segments = math.ceil(duration / param)
                self.result_label.setText(f"預(yù)計(jì)分成: {segments}段")
                
            elif self.segments_radio.isChecked():
                if not self.param_entry.text().strip():
                    self.result_label.setText("請(qǐng)輸入段數(shù)")
                    return
                    
                param = float(self.param_entry.text())
                if param <= 0:
                    self.result_label.setText("請(qǐng)輸入大于0的值")
                    return
                    
                duration = self.processor.get_duration()
                seconds = duration / param
                self.result_label.setText(f"每段約: {seconds:.1f}秒")
                
            else:  # 區(qū)間模式
                count = len(self.intervals)
                if count == 0:
                    self.result_label.setText("未設(shè)置區(qū)間")
                else:
                    total_duration = sum(end - start for start, end in self.intervals)
                    self.result_label.setText(f"將提取: {count}個(gè)區(qū)間 (共{total_duration:.1f}秒)")
                    
        except ValueError:
            self.result_label.setText("請(qǐng)輸入有效數(shù)字")
        except Exception as e:
            self.result_label.setText(f"計(jì)算錯(cuò)誤: {str(e)[:15]}")
    
    def add_interval(self):
        """添加時(shí)間區(qū)間"""
        if not self.processor.source:
            QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)先選擇視頻文件")
            return
            
        try:
            duration = self.processor.get_duration()
            dialog = AddIntervalDialog(self, duration)
            
            if dialog.exec() == QDialog.DialogCode.Accepted:
                self.intervals.append(dialog.result)
                self.update_intervals_list()
                self.auto_calculate()
                
        except Exception as e:
            QMessageBox.critical(self, "錯(cuò)誤", str(e))
    
    def remove_interval(self):
        """刪除選中的區(qū)間"""
        selected = self.intervals_list.currentRow()
        if selected >= 0 and selected < len(self.intervals):
            del self.intervals[selected]
            self.update_intervals_list()
            self.auto_calculate()
    
    def clear_intervals(self):
        """清空區(qū)間列表"""
        self.intervals = []
        self.update_intervals_list()
        self.auto_calculate()
    
    def update_intervals_list(self):
        """更新區(qū)間列表顯示"""
        self.intervals_list.clear()
        for i, (start, end) in enumerate(self.intervals):
            start_h, start_m, start_s = self.processor.seconds_to_hms(start)
            end_h, end_m, end_s = self.processor.seconds_to_hms(end)
            duration = end - start
            item = QListWidgetItem(
                f"{i + 1}. {start_h:02d}:{start_m:02d}:{start_s:02d} - "
                f"{end_h:02d}:{end_m:02d}:{end_s:02d} (時(shí)長(zhǎng): {duration:.2f}s)"
            )
            self.intervals_list.addItem(item)
    
    def toggle_process(self):
        """處理控制開(kāi)關(guān)"""
        if not self.running:
            if self.validate_params():
                self.running = True
                self.start_btn.setText("停止")
                self.start_btn.setStyleSheet("""
                    QPushButton {
                        background: #f44336;
                        color: white;
                        border: none;
                        padding: 8px;
                        border-radius: 5px;
                        font-weight: bold;
                        font-size: 14px;
                    }
                    QPushButton:hover {
                        background: #d32f2f;
                    }
                """)
                Thread(target=self.execute_processing).start()
        else:
            self.running = False
            self.processor.running = False
            self.update_status("操作中止", "#f44336")
    
    def validate_params(self):
        """驗(yàn)證參數(shù)輸入有效性"""
        if not self.processor.source:
            QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)先選擇視頻文件")
            return False
            
        if self.seconds_radio.isChecked() or self.segments_radio.isChecked():
            try:
                param = float(self.param_entry.text())
                if param <= 0:
                    QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)輸入大于0的數(shù)值")
                    return False
            except ValueError:
                QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)輸入有效的數(shù)字")
                return False
        else:  # 區(qū)間模式
            if not self.intervals:
                QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)?zhí)砑又辽僖粋€(gè)提取區(qū)間")
                return False
                
        return True
    
    def execute_processing(self):
        """調(diào)用核心處理"""
        try:
            if self.seconds_radio.isChecked():
                mode = "seconds"
                param = float(self.param_entry.text())
            elif self.segments_radio.isChecked():
                mode = "segments"
                param = int(float(self.param_entry.text()))
            else:
                mode = "intervals"
                param = self.intervals
                
            params = {
                'mode': mode,
                'param': param,
                'progress_callback': self.update_progress,
                'status_callback': self.update_status
            }
            
            self.processor.running = True
            self.processor.start(**params)
            self.update_status("完成", "#4CAF50")
            
        except Exception as e:
            QMessageBox.critical(self, "錯(cuò)誤", str(e))
        finally:
            self.running = False
            self.start_btn.setText("開(kāi)始分割")
            self.start_btn.setStyleSheet("""
                QPushButton {
                    background: #4CAF50;
                    color: white;
                    border: none;
                    padding: 8px;
                    border-radius: 5px;
                    font-weight: bold;
                    font-size: 14px;
                }
                QPushButton:hover {
                    background: #45a049;
                }
            """)
    
    def update_progress(self, value):
        """更新進(jìn)度條"""
        self.progress.setValue(int(value))
    
    def update_status(self, text, color=None):
        """更新?tīng)顟B(tài)文本"""
        self.status_label.setText(text)
        if color:
            self.status_label.setStyleSheet(f"""
                QLabel {{
                    color: {color};
                    font-size: 12px;
                    padding: 5px;
                    background: #f5f5f5;
                    border-radius: 5px;
                }}
            """)
        else:
            self.status_label.setStyleSheet("""
                QLabel {
                    color: #666;
                    font-size: 12px;
                    padding: 5px;
                    background: #f5f5f5;
                    border-radius: 5px;
                }
            """)
    
    def open_output(self):
        """打開(kāi)輸出目錄"""
        if not self.processor.source:
            QMessageBox.warning(self, "錯(cuò)誤", "請(qǐng)先選擇視頻文件")
            return
            
        try:
            base_dir = os.path.dirname(self.processor.source)
            if self.seconds_radio.isChecked():
                prefix = "秒分割"
            elif self.segments_radio.isChecked():
                prefix = "段分割"
            else:
                prefix = "區(qū)間提取"
                
            output_dir = os.path.join(base_dir, f"{prefix}_輸出")
            
            if not os.path.exists(output_dir):
                os.makedirs(output_dir, exist_ok=True)
                self.update_status("創(chuàng)建輸出目錄", "#5D9CEC")
                
            if os.name == 'nt':
                os.startfile(output_dir)
            elif os.name == 'posix':
                subprocess.call(('xdg-open' if os.uname().sysname == 'Linux' else 'open', output_dir))
                
            self.update_status(f"已打開(kāi): {os.path.basename(output_dir)}", "#5D9CEC")
            
        except Exception as e:
            QMessageBox.critical(self, "錯(cuò)誤", f"無(wú)法打開(kāi)輸出目錄: {str(e)}")


if __name__ == "__main__":
    app = QApplication([])
    
    # 設(shè)置應(yīng)用樣式
    app.setStyle("Fusion")
    
    # 創(chuàng)建并使用自定義字體
    font = QFont()
    font.setFamily("Microsoft YaHei" if os.name == 'nt' else "PingFang SC")
    font.setPointSize(10)
    app.setFont(font)
    
    # 設(shè)置調(diào)色板
    palette = app.palette()
    palette.setColor(palette.ColorRole.Window, QColor(240, 247, 250))
    palette.setColor(palette.ColorRole.WindowText, QColor(53, 53, 53))
    palette.setColor(palette.ColorRole.Base, QColor(255, 255, 255))
    palette.setColor(palette.ColorRole.AlternateBase, QColor(240, 247, 250))
    palette.setColor(palette.ColorRole.ToolTipBase, QColor(255, 255, 255))
    palette.setColor(palette.ColorRole.ToolTipText, QColor(53, 53, 53))
    palette.setColor(palette.ColorRole.Text, QColor(53, 53, 53))
    palette.setColor(palette.ColorRole.Button, QColor(240, 247, 250))
    palette.setColor(palette.ColorRole.ButtonText, QColor(53, 53, 53))
    palette.setColor(palette.ColorRole.BrightText, QColor(255, 255, 255))
    palette.setColor(palette.ColorRole.Highlight, QColor(93, 156, 236))
    palette.setColor(palette.ColorRole.HighlightedText, QColor(255, 255, 255))
    app.setPalette(palette)
    
    window = VideoSplitterUI()
    window.show()
    app.exec()

七、總結(jié)與展望

7.1 技術(shù)總結(jié)

  • Qt6的拖拽API真香!
  • FFmpeg處理視頻穩(wěn)定高效
  • 多線(xiàn)程+信號(hào)槽讓UI保持流暢

7.2 未來(lái)計(jì)劃

  • 云端視頻處理
  • AI智能分段
  • 插件系統(tǒng)

到此這篇關(guān)于基于PyQt6實(shí)現(xiàn)智能視頻分割器的文章就介紹到這了,更多相關(guān)PyQt6視頻分割內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python解析html提取數(shù)據(jù),并生成word文檔實(shí)例解析

    python解析html提取數(shù)據(jù),并生成word文檔實(shí)例解析

    這篇文章主要介紹了python解析html提取數(shù)據(jù),并生成word文檔實(shí)例解析,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • OpenCV半小時(shí)掌握基本操作之圖像金字塔

    OpenCV半小時(shí)掌握基本操作之圖像金字塔

    這篇文章主要介紹了OpenCV基本操作之圖像金字塔,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • 純numpy數(shù)值微分法實(shí)現(xiàn)手寫(xiě)數(shù)字識(shí)別

    純numpy數(shù)值微分法實(shí)現(xiàn)手寫(xiě)數(shù)字識(shí)別

    本文主要介紹了純numpy數(shù)值微分法實(shí)現(xiàn)手寫(xiě)數(shù)字識(shí)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Opencv圖像處理方法最全總結(jié)

    Opencv圖像處理方法最全總結(jié)

    這篇文章主要給大家介紹了關(guān)于Opencv圖像處理方法的相關(guān)資料,OpenCV是一個(gè)開(kāi)源的計(jì)算機(jī)視覺(jué)庫(kù),提供了很多圖像處理、計(jì)算機(jī)視覺(jué)和機(jī)器學(xué)習(xí)等方面的函數(shù)和工具,被廣泛應(yīng)用于各種計(jì)算機(jī)視覺(jué)領(lǐng)域的研究和應(yīng)用中,需要的朋友可以參考下
    2024-06-06
  • Python match語(yǔ)句的具體使用

    Python match語(yǔ)句的具體使用

    match語(yǔ)句接受一個(gè)表達(dá)式,并將其值與作為一個(gè)或多個(gè)case塊給出的連續(xù)模式進(jìn)行比較,本文主要介紹了Python match語(yǔ)句的具體使用,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Python生成隨機(jī)密碼

    Python生成隨機(jī)密碼

    這篇文章主要介紹了Python生成隨機(jī)密碼的代碼分享,由于是新手,僅僅是簡(jiǎn)單的實(shí)現(xiàn),未作任何其他處理,小伙伴們自己參考下吧。
    2015-03-03
  • 使用Django和Postgres進(jìn)行全文搜索的實(shí)例代碼

    使用Django和Postgres進(jìn)行全文搜索的實(shí)例代碼

    這篇文章主要介紹了使用Django和Postgres進(jìn)行全文搜索,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-02-02
  • python報(bào)錯(cuò): ''list'' object has no attribute ''shape''的解決

    python報(bào)錯(cuò): ''list'' object has no attribute ''shape''的解決

    這篇文章主要介紹了python報(bào)錯(cuò): 'list' object has no attribute 'shape'的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-07-07
  • Python環(huán)境下搭建屬于自己的pip源的教程

    Python環(huán)境下搭建屬于自己的pip源的教程

    這篇文章主要介紹了Python環(huán)境下搭建屬于自己的pip源的教程,同時(shí)也附帶講解了修改pip源設(shè)定的方法,需要的朋友可以參考下
    2016-05-05
  • Python可視化之seborn圖形外觀設(shè)置

    Python可視化之seborn圖形外觀設(shè)置

    這篇文章主要介紹了Python可視化之seborn圖形外觀設(shè)置,本文介紹seaborn圖形外觀、圖形縮放設(shè)置.具有一的的參考價(jià)值,需要的小伙伴可以參考一下
    2022-03-03

最新評(píng)論