使用PySide6-Fluent-Widgets重构整套UI

添加任务列表组件并优化视频加载逻辑
支持可视化显示字幕区域
整理所有模型, 分别为STTN智能擦除, STTN字幕检测, LAMA, ProPainter, OpenCV
提高处理性能
新增CPU运行模式并优化多语言支持
修复Propainter模式部分视频报错

本次提交新增了CPU运行模式,适用于无GPU加速的场景。同时,优化了多语言支持,新增了日语、韩语、越南语等语言配置文件,并更新了README文档以反映新的运行模式和多语言支持。此外,修复了部分代码逻辑,提升了系统的稳定性和兼容性。
This commit is contained in:
Jason
2025-05-12 16:39:48 +08:00
parent 7049a24883
commit f78e985e1c
62 changed files with 5412 additions and 1520 deletions

View File

@@ -0,0 +1,331 @@
import os
from enum import Enum
from dataclasses import dataclass
from PySide6.QtWidgets import QWidget, QVBoxLayout, QMenu, QAbstractItemView, QTableWidgetItem, QHeaderView
from PySide6.QtCore import Qt, Signal, QModelIndex, QUrl
from qfluentwidgets import TableWidget, BodyLabel, FluentIcon, InfoBar, InfoBarPosition
from PySide6.QtGui import QAction, QColor, QBrush
from showinfm import show_in_file_manager
from backend.config import tr
class TaskStatus(Enum):
PENDING = tr['TaskList']['Pending']
PROCESSING = tr['TaskList']['Processing']
COMPLETED = tr['TaskList']['Completed']
FAILED = tr['TaskList']['Failed']
@dataclass
class Task:
path: str
name: str
progress: int
status: TaskStatus
output_path: str
class TaskListComponent(QWidget):
"""任务列表组件"""
# 定义信号
task_selected = Signal(int, str) # 任务被选中时发出信号,参数为任务索引和视频路径
task_deleted = Signal(int) # 任务被删除时发出信号,参数为任务索引
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("TaskListComponent")
# 初始化变量
self.tasks = [] # 存储任务列表
self.current_task_index = -1 # 当前选中的任务索引
# 创建布局
self.__initWidget()
def __initWidget(self):
"""初始化组件"""
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# 创建表格
self.table = TableWidget(self)
self.table.setColumnCount(3)
self.table.setHorizontalHeaderLabels([tr['TaskList']['Name'], tr['TaskList']['Progress'], tr['TaskList']['Status']])
# 设置表格样式
self.table.setShowGrid(False)
self.table.setAlternatingRowColors(True)
# 设置列宽模式
header = self.table.horizontalHeader()
header.setSectionResizeMode(0, QHeaderView.Stretch) # 名称列拉伸填充
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) # 进度列自适应内容宽度
header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # 状态列自适应内容宽度
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
# 连接信号
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu)
self.table.clicked.connect(self.on_task_clicked)
layout.addWidget(self.table)
def add_task(self, video_path, output_path):
"""添加任务到列表
Args:
video_path: 视频文件路径
"""
# 覆盖相同路径的任务
for row, task in enumerate(self.tasks[:]):
if task.path == video_path:
self.delete_task(row)
continue
# 获取文件名
file_name = os.path.basename(video_path)
# 添加到任务列表
task = Task(
path=video_path,
name=file_name,
progress=0,
status=TaskStatus.PENDING,
output_path=output_path,
)
self.tasks.append(task)
# 更新表格
row = len(self.tasks) - 1
self.table.setRowCount(len(self.tasks))
item0 = QTableWidgetItem(file_name)
item1 = QTableWidgetItem("0%")
item2 = QTableWidgetItem(TaskStatus.PENDING.value)
# 设置文件名单元格的省略模式为中间省略
item0.setTextAlignment(Qt.AlignVCenter | Qt.AlignLeft)
item0.setToolTip(video_path) # 设置完整路径为工具提示
# 设置表格的文本省略模式
self.table.setTextElideMode(Qt.ElideMiddle)
item1.setTextAlignment(Qt.AlignCenter)
item2.setTextAlignment(Qt.AlignCenter)
self.table.setItem(row, 0, item0)
self.table.setItem(row, 1, item1)
self.table.setItem(row, 2, item2)
# 滚动到最新添加的行
self.table.scrollToBottom()
return True
def update_task_progress(self, index, progress):
"""更新任务进度
Args:
index: 任务索引
progress: 进度值(0-100)
"""
if 0 <= index < len(self.tasks):
self.tasks[index].progress = progress
# 更新进度单元格
progress_item = self.table.item(index, 1)
if progress_item:
progress_item.setText(f"{progress}%")
# 如果是当前处理的任务,滚动到可见区域
if index == self.current_task_index:
self.table.scrollTo(self.table.model().index(index, 0))
def update_task_status(self, index, status):
"""更新任务状态
Args:
index: 任务索引
status: 任务状态
"""
if 0 <= index < len(self.tasks):
self.tasks[index].status = status
status_item = self.table.item(index, 2)
if status_item:
status_item.setText(status.value)
# 根据状态设置不同颜色
if status == TaskStatus.COMPLETED:
status_item.setForeground(QBrush(QColor("#2ecc71"))) # 绿色
elif status == TaskStatus.PROCESSING:
status_item.setForeground(QBrush(QColor("#3498db"))) # 蓝色
elif status == TaskStatus.FAILED:
status_item.setForeground(QBrush(QColor("#e74c3c"))) # 红色
# 如果是当前处理的任务,滚动到可见区域
if index == self.current_task_index:
self.table.scrollTo(self.table.model().index(index, 0))
# 选中当前行
self.table.selectRow(index)
def get_pending_tasks(self):
"""获取所有待处理的任务
Returns:
list: 待处理任务列表,每项为 (索引, 任务) 元组
"""
return [(i, task) for i, task in enumerate(self.tasks) if task.status == TaskStatus.PENDING]
def get_all_tasks(self):
"""获取所有任务
Returns:
list: 所有任务列表
"""
return self.tasks
def get_task(self, index):
"""获取指定索引的任务
Args:
index: 任务索引
Returns:
Task: 任务对象
"""
if 0 <= index < len(self.tasks):
return self.tasks[index]
return None
def find_task_index_by_path(self, path):
tasks = self.get_all_tasks()
for idx, task in enumerate(tasks):
if task.path == path:
return idx
return -1 # 没找到返回-1
def show_context_menu(self, pos):
"""显示右键菜单
Args:
pos: 鼠标位置
"""
index = self.table.indexAt(pos)
if index.isValid():
menu = QMenu(self)
# 打开视频文件位置
open_video_location_action = QAction(tr['TaskList']['OpenSourceVideoLocation'], self)
open_video_location_action.triggered.connect(lambda: self.open_file_location(self.tasks[index.row()].path))
menu.addAction(open_video_location_action)
# 打开目标文件位置
def open_target_location():
task = self.tasks[index.row()]
path = task.output_path
if task.status != TaskStatus.COMPLETED:
InfoBar.warning(
title=tr['TaskList']['Warning'],
content=tr['TaskList']['TargetFileNotFound'],
parent=self.get_root_parent(),
duration=3000
)
return
self.open_file_location(path)
open_target_location_action = QAction(tr['TaskList']['OpenTargetVideoLocation'], self)
open_target_location_action.triggered.connect(open_target_location)
menu.addAction(open_target_location_action)
reset_task_status_action = QAction(tr['TaskList']['ResetTaskStatus'], self)
reset_task_status_action.triggered.connect((lambda: (
self.update_task_status(index.row(), TaskStatus.PENDING),
self.update_task_progress(index.row(), 0)
)
))
menu.addAction(reset_task_status_action)
# 删除任务
delete_action = QAction(tr['TaskList']['DeleteTask'], self)
delete_action.triggered.connect(lambda: self.delete_task(index.row()))
menu.addAction(delete_action)
# 显示菜单
menu.exec_(self.table.viewport().mapToGlobal(pos))
def delete_task(self, row):
"""删除任务
Args:
row: 行索引
"""
if 0 <= row < len(self.tasks):
# 从列表中删除
del self.tasks[row]
# 从表格中删除
self.table.removeRow(row)
# 如果删除的是当前任务,重置当前任务索引
if row == self.current_task_index:
self.current_task_index = -1
# 发出任务删除信号
self.task_deleted.emit(row)
def on_task_clicked(self, index):
"""任务被点击时的处理
Args:
index: 索引
"""
row = index.row()
if 0 <= row < len(self.tasks):
self.current_task_index = row
# 发出信号,通知外部加载对应视频
self.task_selected.emit(row, self.tasks[row].path)
def set_current_task(self, index):
"""设置当前处理的任务
Args:
index: 任务索引
"""
if 0 <= index < len(self.tasks):
self.current_task_index = index
self.table.selectRow(index)
self.table.scrollTo(self.table.model().index(index, 0))
def select_task(self, index):
"""选中指定任务
Args:
index: 任务索引
"""
self.set_current_task(index)
def open_file_location(self, path):
"""打开文件所在位置
Args:
row: 行索引
path: 目标路径
"""
# 检查视频文件是否存在
if not os.path.exists(path):
InfoBar.warning(
title=tr['TaskList']['Warning'],
content=tr['TaskList']['UnableToLocateFile'],
parent=self.get_root_parent(),
duration=3000
)
return
show_in_file_manager(os.path.abspath(path))
def get_root_parent(self):
parent = self
while parent.parent():
parent = parent.parent()
return parent

View File

@@ -0,0 +1,566 @@
import cv2
from PySide6.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy
from PySide6.QtCore import Qt, Signal, QRect, QRectF, QTimer, QObject, QEvent
from PySide6 import QtCore, QtWidgets, QtGui
from qfluentwidgets import qconfig, CardWidget, HollowHandleStyle
from backend.config import config, tr
class VideoDisplayComponent(QWidget):
"""视频显示组件,包含视频预览和选择框功能"""
# 定义信号
selection_changed = Signal(QRect) # 选择框变化信号
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
# 初始化变量
self.is_drawing = False
self.selection_rect = QRect()
self.drag_start_pos = None
self.resize_edge = None
self.edge_size = 10 # 调整大小的边缘区域
self.enable_mouse_events = True # 控制是否启用鼠标事件
# 获取屏幕大小
screen = QtWidgets.QApplication.primaryScreen().size()
self.screen_width = screen.width()
self.screen_height = screen.height()
# 设置视频预览区域大小(根据屏幕宽度动态调整)
self.video_preview_width = 960
self.video_preview_height = self.video_preview_width * 9 // 16
if self.screen_width // 2 < 960:
self.video_preview_width = 640
self.video_preview_height = self.video_preview_width * 9 // 16
# 视频相关参数
self.frame_width = None
self.frame_height = None
self.scaled_width = None
self.scaled_height = None
self.border_left = 0
self.border_top = 0
# 保存选择框的相对位置和大小(相对于实际视频的比例)
self.selection_ratio = None
self.__initWidget()
def __initWidget(self):
"""初始化组件"""
main_layout = QVBoxLayout(self)
main_layout.setSpacing(0)
main_layout.setContentsMargins(0, 0, 0, 0)
# 视频预览区域和进度条容器
self.video_container = CardWidget(self)
self.video_container.setObjectName('videoContainer')
video_layout = QVBoxLayout()
video_layout.setSpacing(0)
video_layout.setContentsMargins(2, 2, 2, 2)
video_layout.setAlignment(Qt.AlignCenter)
# 创建内部黑色背景容器
self.black_container = QWidget(self)
self.black_container.setObjectName('blackContainer')
self.black_container.setStyleSheet("""
#blackContainer {
background-color: black;
border-radius: 10px;
border: 0px solid transparent;
}
""")
black_layout = QVBoxLayout()
black_layout.setContentsMargins(0, 0, 0, 0)
black_layout.setSpacing(0)
black_layout.setAlignment(Qt.AlignCenter)
# 视频显示标签
self.video_display = QtWidgets.QLabel()
self.video_display.setStyleSheet("""
background-color: black;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border: 0px solid transparent;
""")
self.video_display.setMinimumSize(self.video_preview_width, self.video_preview_height)
self.video_display.setMouseTracking(True)
self.video_display.setScaledContents(True)
self.video_display.setAlignment(Qt.AlignCenter)
self.video_display.mousePressEvent = self.selection_mouse_press
self.video_display.mouseMoveEvent = self.selection_mouse_move
self.video_display.mouseReleaseEvent = self.selection_mouse_release
# 视频滑块
self.video_slider = QtWidgets.QSlider(Qt.Horizontal)
self.video_slider.setMinimum(1)
self.video_slider.setFixedHeight(22)
self.video_slider.setMaximum(100) # 默认最大值设为100与进度百分比一致
self.video_slider.setValue(1)
self.video_slider.setStyle(HollowHandleStyle({
"handle.color": QtGui.QColor(255, 255, 255),
"handle.ring-width": 4,
"handle.hollow-radius": 6,
"handle.margin": 1
}))
# 视频预览区域
self.video_display.setObjectName('videoDisplay')
# black_layout.addWidget(self.video_display, 0, Qt.AlignCenter)
# 创建一个容器来保持比例
ratio_container = QWidget()
ratio_layout = QVBoxLayout(ratio_container)
ratio_layout.setContentsMargins(0, 0, 0, 0)
ratio_layout.addWidget(self.video_display)
# 设置固定的宽高比
ratio_container.setFixedHeight(ratio_container.width() * 9 // 16)
ratio_container.setMinimumWidth(self.video_preview_width)
# 添加到布局
black_layout.addWidget(ratio_container)
# 添加一个事件过滤器来处理大小变化
class RatioEventFilter(QObject):
def eventFilter(self, obj, event):
if event.type() == QEvent.Resize:
obj.setFixedHeight(obj.width() * 9 // 16)
return False
ratio_filter = RatioEventFilter(ratio_container)
ratio_container.installEventFilter(ratio_filter)
# 进度条和滑块容器
control_container = QWidget(self)
control_layout = QVBoxLayout()
control_layout.setContentsMargins(8, 8, 8, 8)
control_layout.addWidget(self.video_slider)
control_container.setLayout(control_layout)
control_container.setStyleSheet("""
background-color: black;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
""")
black_layout.addWidget(control_container)
self.black_container.setLayout(black_layout)
video_layout.addWidget(self.black_container)
self.video_container.setLayout(video_layout)
main_layout.addWidget(self.video_container)
def update_video_display(self, frame, draw_selection=True):
"""更新视频显示"""
if frame is None:
return
# 调整视频帧大小以适应视频预览区域
frame = cv2.resize(frame, (self.video_preview_width, self.video_preview_height))
# 将 OpenCV 帧BGR 格式)转换为 QImage 并显示在 QLabel 上
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_frame.shape
bytes_per_line = ch * w
image = QtGui.QImage(rgb_frame.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
pix = QtGui.QPixmap.fromImage(image)
# 创建带圆角的图像
rounded_pix = QtGui.QPixmap(pix.size())
rounded_pix.fill(Qt.transparent) # 填充透明背景
painter = QtGui.QPainter(rounded_pix)
painter.setRenderHint(QtGui.QPainter.Antialiasing) # 抗锯齿
painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
# 创建圆角路径
path = QtGui.QPainterPath()
rect = QRectF(0, 0, pix.width(), pix.height())
# 手动创建只有左上和右上圆角的路径
radius = 8
path.moveTo(radius, 0)
path.lineTo(pix.width() - radius, 0)
path.arcTo(pix.width() - radius * 2, 0, radius * 2, radius * 2, 90, -90)
path.lineTo(pix.width(), pix.height())
path.lineTo(0, pix.height())
path.lineTo(0, radius)
path.arcTo(0, 0, radius * 2, radius * 2, 180, -90)
path.closeSubpath()
painter.setClipPath(path)
painter.drawPixmap(0, 0, pix)
painter.end()
# 保存当前的pixmap用于绘制选择框
self.current_pixmap = rounded_pix.copy()
self.video_display.setPixmap(rounded_pix)
# 如果有保存的选择框比例,根据新视频尺寸重新计算选择框
if draw_selection and self.selection_ratio is not None and self.scaled_width and self.scaled_height:
x_ratio, y_ratio, w_ratio, h_ratio = self.selection_ratio
# 计算新的选择框坐标和大小
x = int(x_ratio * self.scaled_width) + self.border_left
y = int(y_ratio * self.scaled_height) + self.border_top
w = int(w_ratio * self.scaled_width)
h = int(h_ratio * self.scaled_height)
# 创建新的选择框
self.selection_rect = QRect(x, y, w, h)
# 更新视频显示
self.update_preview_with_rect()
def update_preview_with_rect(self, rect=None):
"""更新带有选择框的预览"""
if not hasattr(self, 'current_pixmap') or self.current_pixmap is None:
return
# 如果提供了新的矩形,使用它
if rect is not None:
self.selection_rect = rect
# 创建一个副本用于绘制
pixmap_copy = self.current_pixmap.copy()
painter = QtGui.QPainter(pixmap_copy)
# 设置选择框样式
pen = QtGui.QPen(QtGui.QColor(0, 255, 0)) # 绿色
pen.setWidth(2)
painter.setPen(pen)
# 绘制选择框
painter.drawRect(self.selection_rect)
painter.end()
# 更新显示
self.video_display.setPixmap(pixmap_copy)
def selection_mouse_press(self, event):
"""鼠标按下事件处理"""
if not self.enable_mouse_events:
return
pos = event.pos()
# 检测双击或三击,重置选择框
if event.type() == QtCore.QEvent.MouseButtonDblClick:
self.selection_rect = QRect(pos, pos)
self.resize_edge = None
self.is_drawing = True
self.drag_start_pos = pos
return
# 检查是否在选择框边缘(用于调整大小)
if self.selection_rect.isValid():
# 右下角
if abs(pos.x() - self.selection_rect.right()) <= self.edge_size and abs(pos.y() - self.selection_rect.bottom()) <= self.edge_size:
self.resize_edge = "bottomright"
self.drag_start_pos = pos
return
# 右上角
elif abs(pos.x() - self.selection_rect.right()) <= self.edge_size and abs(pos.y() - self.selection_rect.top()) <= self.edge_size:
self.resize_edge = "topright"
self.drag_start_pos = pos
return
# 左下角
elif abs(pos.x() - self.selection_rect.left()) <= self.edge_size and abs(pos.y() - self.selection_rect.bottom()) <= self.edge_size:
self.resize_edge = "bottomleft"
self.drag_start_pos = pos
return
# 左边缘
elif abs(pos.x() - self.selection_rect.left()) <= self.edge_size and self.selection_rect.top() <= pos.y() <= self.selection_rect.bottom():
self.resize_edge = "left"
self.drag_start_pos = pos
return
# 右边缘
elif abs(pos.x() - self.selection_rect.right()) <= self.edge_size and self.selection_rect.top() <= pos.y() <= self.selection_rect.bottom():
self.resize_edge = "right"
self.drag_start_pos = pos
return
# 上边缘
elif abs(pos.y() - self.selection_rect.top()) <= self.edge_size and self.selection_rect.left() <= pos.x() <= self.selection_rect.right():
self.resize_edge = "top"
self.drag_start_pos = pos
return
# 下边缘
elif abs(pos.y() - self.selection_rect.bottom()) <= self.edge_size and self.selection_rect.left() <= pos.x() <= self.selection_rect.right():
self.resize_edge = "bottom"
self.drag_start_pos = pos
return
# 左上角
elif abs(pos.x() - self.selection_rect.left()) <= self.edge_size and abs(pos.y() - self.selection_rect.top()) <= self.edge_size:
self.resize_edge = "topleft"
self.drag_start_pos = pos
return
# 在选择框内部(用于移动)
elif self.selection_rect.contains(pos):
self.resize_edge = "move"
self.drag_start_pos = pos
return
# 开始新的选择
self.is_drawing = True
self.selection_rect = QRect(pos, pos)
self.drag_start_pos = pos
self.resize_edge = None
def selection_mouse_move(self, event):
"""鼠标移动事件处理"""
if not self.enable_mouse_events:
return
pos = event.pos()
# 根据不同的操作模式处理鼠标移动
if self.is_drawing: # 绘制新选择框
self.selection_rect.setBottomRight(pos)
self.update_preview_with_rect()
elif self.resize_edge: # 调整选择框大小或位置
if self.resize_edge == "move":
# 移动整个选择框
dx = pos.x() - self.drag_start_pos.x()
dy = pos.y() - self.drag_start_pos.y()
# 保存原始选择框尺寸
original_width = self.selection_rect.width()
original_height = self.selection_rect.height()
# 计算新位置
new_rect = self.selection_rect.translated(dx, dy)
# 获取视频显示区域
display_rect = self.video_display.rect()
# 检查是否超出边界,如果超出则调整位置但保持尺寸
if new_rect.left() < 0:
new_rect.moveLeft(0)
if new_rect.top() < 0:
new_rect.moveTop(0)
if new_rect.right() > display_rect.width():
new_rect.moveRight(display_rect.width())
if new_rect.bottom() > display_rect.height():
new_rect.moveBottom(display_rect.height())
# 确保尺寸不变
if new_rect.width() != original_width or new_rect.height() != original_height:
# 如果尺寸变了,恢复原始尺寸
if new_rect.left() == 0:
new_rect.setWidth(original_width)
if new_rect.top() == 0:
new_rect.setHeight(original_height)
if new_rect.right() == display_rect.width():
new_rect.setLeft(new_rect.right() - original_width)
if new_rect.bottom() == display_rect.height():
new_rect.setTop(new_rect.bottom() - original_height)
self.selection_rect = new_rect
self.drag_start_pos = pos
else:
# 调整选择框大小
if "left" in self.resize_edge:
self.selection_rect.setLeft(pos.x())
if "right" in self.resize_edge:
self.selection_rect.setRight(pos.x())
if "top" in self.resize_edge:
self.selection_rect.setTop(pos.y())
if "bottom" in self.resize_edge:
self.selection_rect.setBottom(pos.y())
# 确保选择框在视频显示区域内
display_rect = self.video_display.rect()
if self.selection_rect.left() < 0:
self.selection_rect.setLeft(0)
if self.selection_rect.top() < 0:
self.selection_rect.setTop(0)
if self.selection_rect.right() > display_rect.width():
self.selection_rect.setRight(display_rect.width())
if self.selection_rect.bottom() > display_rect.height():
self.selection_rect.setBottom(display_rect.height())
self.update_preview_with_rect()
else:
# 更新鼠标指针形状
self.update_cursor_shape(pos)
def selection_mouse_release(self, event):
"""鼠标释放事件处理"""
if not self.enable_mouse_events:
return
# 结束绘制或调整
self.is_drawing = False
self.resize_edge = None
# 标准化选择框(确保宽度和高度为正)
self.selection_rect = self.selection_rect.normalized()
# 保存选择框的相对位置和大小
self.save_selection_ratio()
# 发送选择框变化信号
self.selection_changed.emit(self.selection_rect)
def update_cursor_shape(self, pos):
"""根据鼠标位置更新光标形状"""
if not self.selection_rect.isValid():
self.video_display.setCursor(Qt.ArrowCursor)
return
# 检查鼠标是否在选择框边缘
if (abs(pos.x() - self.selection_rect.left()) <= self.edge_size and
self.selection_rect.top() <= pos.y() <= self.selection_rect.bottom()):
self.video_display.setCursor(Qt.SizeHorCursor)
elif (abs(pos.x() - self.selection_rect.right()) <= self.edge_size and
self.selection_rect.top() <= pos.y() <= self.selection_rect.bottom()):
self.video_display.setCursor(Qt.SizeHorCursor)
elif (abs(pos.y() - self.selection_rect.top()) <= self.edge_size and
self.selection_rect.left() <= pos.x() <= self.selection_rect.right()):
self.video_display.setCursor(Qt.SizeVerCursor)
elif (abs(pos.y() - self.selection_rect.bottom()) <= self.edge_size and
self.selection_rect.left() <= pos.x() <= self.selection_rect.right()):
self.video_display.setCursor(Qt.SizeVerCursor)
elif (abs(pos.x() - self.selection_rect.left()) <= self.edge_size and
abs(pos.y() - self.selection_rect.top()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeFDiagCursor)
elif (abs(pos.x() - self.selection_rect.right()) <= self.edge_size and
abs(pos.y() - self.selection_rect.top()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeBDiagCursor)
elif (abs(pos.x() - self.selection_rect.left()) <= self.edge_size and
abs(pos.y() - self.selection_rect.bottom()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeBDiagCursor)
elif (abs(pos.x() - self.selection_rect.right()) <= self.edge_size and
abs(pos.y() - self.selection_rect.bottom()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeFDiagCursor)
elif self.selection_rect.contains(pos):
self.video_display.setCursor(Qt.SizeAllCursor)
else:
self.video_display.setCursor(Qt.ArrowCursor)
def set_video_parameters(self, frame_width, frame_height, scaled_width=None, scaled_height=None, border_left=0, border_top=0):
"""设置视频参数"""
self.frame_width = frame_width
self.frame_height = frame_height
self.scaled_width = scaled_width
self.scaled_height = scaled_height
self.border_left = border_left
self.border_top = border_top
def get_selection_coordinates(self):
"""获取选择框坐标"""
return self.selection_rect
def set_selection_rect(self, rect):
"""设置选择框"""
self.selection_rect = rect
self.save_selection_ratio()
self.update_preview_with_rect()
def load_selection_ratio(self):
"""从配置中加载选择框的相对位置和大小"""
# 检查是否有有效的视频尺寸
if not self.scaled_width or not self.scaled_height:
return False
# 从配置中读取选择框的相对位置和大小
x_ratio = config.subtitleSelectionAreaX.value
y_ratio = config.subtitleSelectionAreaY.value
w_ratio = config.subtitleSelectionAreaW.value
h_ratio = config.subtitleSelectionAreaH.value
# 检查配置值是否有效
if x_ratio is None or y_ratio is None or w_ratio is None or h_ratio is None:
return False
# 检查配置值是否在有效范围内
if w_ratio <= 0.01 or h_ratio <= 0.005:
config.set(config.subtitleSelectionAreaX, config.subtitleSelectionAreaX.defaultValue)
config.set(config.subtitleSelectionAreaY, config.subtitleSelectionAreaY.defaultValue)
config.set(config.subtitleSelectionAreaW, config.subtitleSelectionAreaW.defaultValue)
config.set(config.subtitleSelectionAreaH, config.subtitleSelectionAreaH.defaultValue)
x_ratio = config.subtitleSelectionAreaX.value
y_ratio = config.subtitleSelectionAreaY.value
w_ratio = config.subtitleSelectionAreaW.value
h_ratio = config.subtitleSelectionAreaH.value
# 保存选择框比例
self.selection_ratio = (x_ratio, y_ratio, w_ratio, h_ratio)
# 计算实际像素坐标
x = int(x_ratio * self.scaled_width) + self.border_left
y = int(y_ratio * self.scaled_height) + self.border_top
w = int(w_ratio * self.scaled_width)
h = int(h_ratio * self.scaled_height)
# 创建选择框
self.selection_rect = QRect(x, y, w, h)
# 更新预览
self.update_preview_with_rect()
return True
def save_selection_ratio(self):
"""保存选择框的相对位置和大小(相对于实际视频的比例)"""
if not self.selection_rect.isValid() or not self.scaled_width or not self.scaled_height:
return
# 调整选择框坐标,考虑黑边偏移
x_adjusted = max(0, self.selection_rect.x() - self.border_left)
y_adjusted = max(0, self.selection_rect.y() - self.border_top)
# 如果选择框超出了实际视频区域,需要调整宽度和高度
w_adjusted = min(self.selection_rect.width(), self.scaled_width - x_adjusted)
h_adjusted = min(self.selection_rect.height(), self.scaled_height - y_adjusted)
# 转换为相对比例
x_ratio = x_adjusted / self.scaled_width
y_ratio = y_adjusted / self.scaled_height
w_ratio = w_adjusted / self.scaled_width
h_ratio = h_adjusted / self.scaled_height
self.selection_ratio = (x_ratio, y_ratio, w_ratio, h_ratio)
config.subtitleSelectionAreaY.value = y_ratio
config.subtitleSelectionAreaH.value = h_ratio
config.subtitleSelectionAreaX.value = x_ratio
config.subtitleSelectionAreaW.value = w_ratio
qconfig.save()
def get_original_coordinates(self):
"""获取选择框在原始视频中的坐标"""
if not self.selection_rect.isValid() or not self.scaled_width or not self.scaled_height:
return None
# 调整选择框坐标,考虑黑边偏移
x_adjusted = max(0, self.selection_rect.x() - self.border_left)
y_adjusted = max(0, self.selection_rect.y() - self.border_top)
# 如果选择框超出了实际视频区域,需要调整宽度和高度
w_adjusted = min(self.selection_rect.width(), self.scaled_width - x_adjusted)
h_adjusted = min(self.selection_rect.height(), self.scaled_height - y_adjusted)
# 转换为原始视频坐标
scale_x = self.frame_width / self.scaled_width
scale_y = self.frame_height / self.scaled_height
xmin = int(x_adjusted * scale_x)
xmax = int((x_adjusted + w_adjusted) * scale_x)
ymin = int(y_adjusted * scale_y)
ymax = int((y_adjusted + h_adjusted) * scale_y)
# 确保坐标在有效范围内
xmin = max(0, min(xmin, self.frame_width))
xmax = max(0, min(xmax, self.frame_width))
ymin = max(0, min(ymin, self.frame_height))
ymax = max(0, min(ymax, self.frame_height))
return (ymin, ymax, xmin, xmax)
def set_dragger_enabled(self, enabled):
"""设置拖动器是否可用"""
self.enable_mouse_events = enabled
self.video_display.setMouseTracking(enabled)
self.video_display.setCursor(Qt.ArrowCursor)