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

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

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

一、開篇碎碎念

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

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

(效果預(yù)覽圖)

二、功能全景圖

2.1 核心功能清單

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

2.2 技術(shù)選型對比

三、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ū):虛線邊框+懸浮高亮

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

動態(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)備

# 必備依賴
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多線程調(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):
    """自動計(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):
        """秒級分割邏輯"""
        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("沒有有效的提取區(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í)長"""
        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í)長失敗: {e.stderr}")
        except (json.JSONDecodeError, KeyError, ValueError) as e:
            raise RuntimeError(f"解析視頻信息失敗: {str(e)}")
        except Exception as e:
            raise RuntimeError(f"讀取時(shí)長失敗: {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"請輸入時(shí)間區(qū)間 (范圍: 0-{self.format_time(self.max_duration)})")
        info_label.setStyleSheet("color: #555;")
        layout.addWidget(info_label)
        
        # 開始時(shí)間輸入
        start_frame = QWidget()
        start_layout = QHBoxLayout(start_frame)
        start_layout.setContentsMargins(0, 0, 0, 0)
        
        start_layout.addWidget(QLabel("開始時(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"開始時(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í)間必須大于開始時(shí)間且不超過{self.format_time(self.max_duration)}")
                return
                
            if end_seconds - start_seconds < 0.5:
                QMessageBox.warning(self, "錯(cuò)誤", "區(qū)間長度太短(最少0.5秒)")
                return
                
            self.result = (start_seconds, end_seconds)
            self.accept()
            
        except ValueError:
            QMessageBox.warning(self, "錯(cuò)誤", "請輸入有效的數(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í)長: 未知")
        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("預(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("開始分割")
        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)
        
        # 進(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)
        
        # 連接信號
        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):
        """拖拽離開事件"""
        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í)長: {time_str} ({duration:.2f}秒)")
                    
                    self.auto_calculate()
                except Exception as e:
                    QMessageBox.critical(self, "錯(cuò)誤", str(e))
            else:
                QMessageBox.warning(self, "錯(cuò)誤", "請拖入有效的視頻文件 (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í)長: {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):
        """自動計(jì)算分割參數(shù)預(yù)覽"""
        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"預(yù)計(jì)分成: {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("未設(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("請輸入有效數(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ò)誤", "請先選擇視頻文件")
            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í)長: {duration:.2f}s)"
            )
            self.intervals_list.addItem(item)
    
    def toggle_process(self):
        """處理控制開關(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ò)誤", "請先選擇視頻文件")
            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ò)誤", "請輸入大于0的數(shù)值")
                    return False
            except ValueError:
                QMessageBox.warning(self, "錯(cuò)誤", "請輸入有效的數(shù)字")
                return False
        else:  # 區(qū)間模式
            if not self.intervals:
                QMessageBox.warning(self, "錯(cuò)誤", "請?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("開始分割")
            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à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, "錯(cuò)誤", "請先選擇視頻文件")
            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, "錯(cuò)誤", f"無法打開輸出目錄: {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)定高效
  • 多線程+信號槽讓UI保持流暢

7.2 未來計(jì)劃

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

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

相關(guān)文章

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

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

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

    python中requests小技巧

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

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

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

    Python3中使用PyMongo的方法詳解

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

    Python opencv圖像基本操作學(xué)習(xí)之灰度圖轉(zhuǎn)換

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

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

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

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

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

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

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

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

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

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

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

最新評論

璧山县| 琼中| 柳林县| 剑川县| 铜梁县| 怀化市| 拉萨市| 甘南县| 牡丹江市| 西平县| 剑川县| 宁南县| 湘阴县| 赤壁市| 永丰县| 海宁市| 建平县| 奇台县| 大理市| 米易县| 鄄城县| 廉江市| 怀宁县| 静海县| 楚雄市| 大田县| 香河县| 布拖县| 桐乡市| 涟源市| 八宿县| 明光市| 饶平县| 洛南县| 福鼎市| 织金县| 建昌县| 灵璧县| 金乡县| 沙河市| 乐平市|