最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

一、開篇碎碎念

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

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

(效果預覽圖)

二、功能全景圖

2.1 核心功能清單

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

2.2 技術選型對比

三、UI設計詳解

3.1 色彩方案

采用藍綠漸變科技風配色:

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

3.2 關鍵界面組件

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

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

動態(tài)進度條:

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

四、手把手教學:從安裝到使用

4.1 環(huán)境準備

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

4.2 視頻分割處理流程

五、核心代碼解析

5.1 拖拽處理三件套

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

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

5.2 FFmpeg多線程調用

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

5.3 智能區(qū)間計算

def calculate_intervals(self):
    """自動計算合理分段"""
    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):
        """設置視頻源文件"""
        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):
        """秒級分割邏輯"""
        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"處理進度 {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("沒有有效的提取區(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):
        """獲取視頻時長"""
        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"讀取時長失敗: {e.stderr}")
        except (json.JSONDecodeError, KeyError, ValueError) as e:
            raise RuntimeError(f"解析視頻信息失敗: {str(e)}")
        except Exception as e:
            raise RuntimeError(f"讀取時長失敗: {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):
        """將秒轉換為小時、分鐘、秒"""
        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):
        """將小時、分鐘、秒轉換為秒"""
        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ū)間 (范圍: 0-{self.format_time(self.max_duration)})")
        info_label.setStyleSheet("color: #555;")
        layout.addWidget(info_label)
        
        # 開始時間輸入
        start_frame = QWidget()
        start_layout = QHBoxLayout(start_frame)
        start_layout.setContentsMargins(0, 0, 0, 0)
        
        start_layout.addWidget(QLabel("開始時間:"))
        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("時"))
        
        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)
        
        # 結束時間輸入
        end_frame = QWidget()
        end_layout = QHBoxLayout(end_frame)
        end_layout.setContentsMargins(0, 0, 0, 0)
        
        end_layout.addWidget(QLabel("結束時間:"))
        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("時"))
        
        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, "錯誤", f"開始時間必須在0到{self.format_time(self.max_duration)}之間")
                return
                
            if end_seconds <= start_seconds or end_seconds > self.max_duration:
                QMessageBox.warning(self, "錯誤", f"結束時間必須大于開始時間且不超過{self.format_time(self.max_duration)}")
                return
                
            if end_seconds - start_seconds < 0.5:
                QMessageBox.warning(self, "錯誤", "區(qū)間長度太短(最少0.5秒)")
                return
                
            self.result = (start_seconds, end_seconds)
            self.accept()
            
        except ValueError:
            QMessageBox.warning(self, "錯誤", "請輸入有效的數(shù)字")
    
    def format_time(self, seconds):
        """將秒轉換為 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("拖放視頻文件到這里或點擊按鈕選擇")
        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("視頻時長: 未知")
        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)
        
        # 分割線
        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("預計段數(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("開始分割")
        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("打開輸出目錄")
        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)
        
        # 進度條
        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)
        
        # 連接信號
        self.mode_group.buttonClicked.connect(self.on_mode_changed)
        
        main_widget.setLayout(layout)
    
    def dragEnterEvent(self, event: QDragEnterEvent):
        """拖拽進入事件"""
        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):
        """拖拽離開事件"""
        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"視頻時長: {time_str} ({duration:.2f}秒)")
                    
                    self.auto_calculate()
                except Exception as e:
                    QMessageBox.critical(self, "錯誤", str(e))
            else:
                QMessageBox.warning(self, "錯誤", "請拖入有效的視頻文件 (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"視頻時長: {time_str} ({duration:.2f}秒)")
                
                self.auto_calculate()
            except Exception as e:
                QMessageBox.critical(self, "錯誤", str(e))
    
    def on_mode_changed(self, button):
        """模式變化時更新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):
        """自動計算分割參數(shù)預覽"""
        try:
            if not self.processor.source:
                self.result_label.setText("請先選擇文件")
                return
                
            if self.seconds_radio.isChecked():
                if not self.param_entry.text().strip():
                    self.result_label.setText("請輸入秒數(shù)")
                    return
                    
                param = float(self.param_entry.text())
                if param <= 0:
                    self.result_label.setText("請輸入大于0的值")
                    return
                    
                duration = self.processor.get_duration()
                segments = math.ceil(duration / param)
                self.result_label.setText(f"預計分成: {segments}段")
                
            elif self.segments_radio.isChecked():
                if not self.param_entry.text().strip():
                    self.result_label.setText("請輸入段數(shù)")
                    return
                    
                param = float(self.param_entry.text())
                if param <= 0:
                    self.result_label.setText("請輸入大于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("未設置區(qū)間")
                else:
                    total_duration = sum(end - start for start, end in self.intervals)
                    self.result_label.setText(f"將提取: {count}個區(qū)間 (共{total_duration:.1f}秒)")
                    
        except ValueError:
            self.result_label.setText("請輸入有效數(shù)字")
        except Exception as e:
            self.result_label.setText(f"計算錯誤: {str(e)[:15]}")
    
    def add_interval(self):
        """添加時間區(qū)間"""
        if not self.processor.source:
            QMessageBox.warning(self, "錯誤", "請先選擇視頻文件")
            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, "錯誤", 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} (時長: {duration:.2f}s)"
            )
            self.intervals_list.addItem(item)
    
    def toggle_process(self):
        """處理控制開關"""
        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):
        """驗證參數(shù)輸入有效性"""
        if not self.processor.source:
            QMessageBox.warning(self, "錯誤", "請先選擇視頻文件")
            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, "錯誤", "請輸入大于0的數(shù)值")
                    return False
            except ValueError:
                QMessageBox.warning(self, "錯誤", "請輸入有效的數(shù)字")
                return False
        else:  # 區(qū)間模式
            if not self.intervals:
                QMessageBox.warning(self, "錯誤", "請?zhí)砑又辽僖粋€提取區(qū)間")
                return False
                
        return True
    
    def execute_processing(self):
        """調用核心處理"""
        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, "錯誤", str(e))
        finally:
            self.running = False
            self.start_btn.setText("開始分割")
            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):
        """更新進度條"""
        self.progress.setValue(int(value))
    
    def update_status(self, text, color=None):
        """更新狀態(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):
        """打開輸出目錄"""
        if not self.processor.source:
            QMessageBox.warning(self, "錯誤", "請先選擇視頻文件")
            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"已打開: {os.path.basename(output_dir)}", "#5D9CEC")
            
        except Exception as e:
            QMessageBox.critical(self, "錯誤", f"無法打開輸出目錄: {str(e)}")


if __name__ == "__main__":
    app = QApplication([])
    
    # 設置應用樣式
    app.setStyle("Fusion")
    
    # 創(chuàng)建并使用自定義字體
    font = QFont()
    font.setFamily("Microsoft YaHei" if os.name == 'nt' else "PingFang SC")
    font.setPointSize(10)
    app.setFont(font)
    
    # 設置調色板
    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()

七、總結與展望

7.1 技術總結

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

7.2 未來計劃

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

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

相關文章

  • python實現(xiàn)簡易內存監(jiān)控

    python實現(xiàn)簡易內存監(jiān)控

    這篇文章主要介紹了python實現(xiàn)簡易內存監(jiān)控,每隔3秒獲取系統(tǒng)內存,當內存超過設定的警報值時,獲取所有進程占用內存并發(fā)出警報聲,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-06-06
  • python中requests小技巧

    python中requests小技巧

    Requests 使用的是 urllib3,因此繼承了它的所有特性。Requests 支持 HTTP 連接保持和連接池,支持使用 cookie 保持會話,支持文件上傳,支持自動確定響應內容的編碼,支持國際化的 URL 和 POST 數(shù)據(jù)自動編碼?,F(xiàn)代、國際化、人性化。
    2017-05-05
  • Python使用python-docx庫操作word(創(chuàng)建、讀取和插入)文檔的完整指南

    Python使用python-docx庫操作word(創(chuàng)建、讀取和插入)文檔的完整指南

    python-docx是一個可以操作Word文檔的python開源庫,簡單易用,適合用來自動化辦公,下面小編就和大家詳細介紹一下如何使用python-docx實現(xiàn)創(chuàng)建,打開,讀取和插入Word吧
    2025-12-12
  • Python3中使用PyMongo的方法詳解

    Python3中使用PyMongo的方法詳解

    平常在處理數(shù)據(jù)的時候,必不可少地要使用數(shù)據(jù)庫存取數(shù)據(jù),出于一些原因,個人非常喜歡MongoDB這個數(shù)據(jù)庫。下面這篇文章主要給大家介紹了Python3中使用PyMongo的方法示例,文中介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-07-07
  • Python opencv圖像基本操作學習之灰度圖轉換

    Python opencv圖像基本操作學習之灰度圖轉換

    使用opencv將圖片轉為灰度圖主要有兩種方法,第一種是將彩色圖轉為灰度圖,第二種是在使用OpenCV讀取圖片的時候直接讀取為灰度圖,今天通過實例代碼講解Python opencv圖像基本操作學習之灰度圖轉換,感興趣的朋友一起看看吧
    2023-02-02
  • python中的數(shù)組賦值與拷貝的區(qū)別詳解

    python中的數(shù)組賦值與拷貝的區(qū)別詳解

    今天小編就為大家分享一篇python中的數(shù)組賦值與拷貝的區(qū)別詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • Python如何繪制概率分布直方圖淺析

    Python如何繪制概率分布直方圖淺析

    項目中在前期經(jīng)常要看下數(shù)據(jù)的分布情況,這對于探究數(shù)據(jù)規(guī)律非常有用,概率分布表示樣本數(shù)據(jù)的模樣,使用Python繪制頻率分布直方圖非常簡潔,因為用的頻次非常高,這篇文章主要給大家介紹了關于Python如何繪制概率分布直方圖的相關資料,需要的朋友可以參考下
    2021-12-12
  • pycharm打開長代碼文件CPU占用率過高的解決

    pycharm打開長代碼文件CPU占用率過高的解決

    這篇文章主要介紹了pycharm打開長代碼文件CPU占用率過高的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • pycharm激活碼2020最新分享適用pycharm2020最新版親測可用

    pycharm激活碼2020最新分享適用pycharm2020最新版親測可用

    這篇文章主要介紹了pycharm激活碼2020最新分享適用pycharm2020最新版親測可用,同時也支持Intellij IDEA激活碼,PHPStorm激活碼大家可以放心使用需要的朋友可以參考下
    2020-11-11
  • Python中高效拆分PDF文檔的多種方法指南

    Python中高效拆分PDF文檔的多種方法指南

    當 PDF 文檔頁數(shù)較多時,我們往往需要提取部分內容或者將文檔拆分成多個文件,下面小編就為大家簡單介紹一下如何通過Spire.PDF for Python實現(xiàn)PDF的拆分處理吧
    2025-09-09

最新評論

清原| 东山县| 长丰县| 潜山县| 宣恩县| 兴安盟| 响水县| 肇庆市| 元氏县| 清水河县| 张家口市| 玉山县| 清新县| 保靖县| 田阳县| 甘肃省| 习水县| 岐山县| 信宜市| 霍山县| 禄劝| 甘泉县| 仪陇县| 尼木县| 日土县| 孝义市| 邢台市| 龙南县| 临西县| 昆山市| 台湾省| 诸城市| 赞皇县| 勐海县| 晋宁县| 濮阳县| 固阳县| 玉田县| 仙桃市| 临桂县| 木里|