Files
video-subtitle-remover/ui/component/video_display_component.py
2025-05-22 08:42:00 +08:00

765 lines
32 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(list) # 选择框变化信号
def __init__(self, parent=None):
super().__init__(parent)
self.parent = parent
# 初始化变量
self.is_drawing = False
self.selection_rect = QRect() # 当前正在绘制或调整的选区
self.selection_rects = [] # 存储多个选区
self.selection_ratios = [] # 存储多个选区的比例
self.active_selection_index = -1 # 当前活动选区的索引
self.drag_start_pos = None
self.resize_edge = None
self.edge_size = 10 # 调整大小的边缘区域
self.enable_mouse_events = True # 控制是否启用鼠标事件
# 安装事件过滤器以捕获键盘事件
self.installEventFilter(self)
# 获取屏幕大小
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.__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_ratios and self.scaled_width and self.scaled_height:
self.selection_rects = []
for ratio in self.selection_ratios:
x_ratio, y_ratio, w_ratio, h_ratio = 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_rects.append(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 and self.active_selection_index >= 0:
self.selection_rects[self.active_selection_index] = rect
# 创建一个副本用于绘制
pixmap_copy = self.current_pixmap.copy()
painter = QtGui.QPainter(pixmap_copy)
# 绘制所有选区
for i, rect in enumerate(self.selection_rects):
# 设置选择框样式
if i == self.active_selection_index:
# 活动选区使用绿色
pen = QtGui.QPen(QtGui.QColor(0, 255, 0))
else:
# 非活动选区使用黄色
pen = QtGui.QPen(QtGui.QColor(255, 255, 0))
pen.setWidth(2)
painter.setPen(pen)
# 绘制选择框
painter.drawRect(rect)
# 如果正在绘制新选区,也绘制它
if self.is_drawing and self.selection_rect.isValid():
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
# 设置焦点到当前组件
self.setFocus()
pos = event.pos()
# 检查是否按下了Ctrl键
is_ctrl_pressed = event.modifiers() & Qt.ControlModifier
# 检测双击,重置所有选区
if event.type() == QtCore.QEvent.MouseButtonDblClick:
self.selection_rects = []
self.selection_ratios = []
self.active_selection_index = -1
self.update_preview_with_rect()
self.selection_changed.emit(self.selection_rects)
return
# 如果按下Ctrl键开始绘制新选区
if is_ctrl_pressed:
self.is_drawing = True
self.selection_rect = QRect(pos, pos)
self.drag_start_pos = pos
self.resize_edge = None
self.active_selection_index = -1 # 不选中任何已有选区
return
# 检查是否点击了已有选区
clicked_index = -1
for i, rect in enumerate(self.selection_rects):
# 检查是否在选区边缘(用于调整大小)
if self.is_on_rect_edge(pos, rect):
clicked_index = i
self.active_selection_index = i
self.resize_edge = self.get_resize_edge(pos, rect)
self.drag_start_pos = pos
self.update_preview_with_rect()
return
# 检查是否在选区内部(用于移动)
elif rect.contains(pos):
clicked_index = i
self.active_selection_index = i
self.resize_edge = "move"
self.drag_start_pos = pos
self.update_preview_with_rect()
return
# 如果没有点击任何选区,开始绘制新选区
if clicked_index == -1:
self.is_drawing = True
self.selection_rect = QRect(pos, pos)
self.drag_start_pos = pos
self.resize_edge = None
self.active_selection_index = -1
def is_on_rect_edge(self, pos, rect):
"""检查点是否在矩形边缘"""
# 右下角
if abs(pos.x() - rect.right()) <= self.edge_size and abs(pos.y() - rect.bottom()) <= self.edge_size:
return True
# 右上角
elif abs(pos.x() - rect.right()) <= self.edge_size and abs(pos.y() - rect.top()) <= self.edge_size:
return True
# 左下角
elif abs(pos.x() - rect.left()) <= self.edge_size and abs(pos.y() - rect.bottom()) <= self.edge_size:
return True
# 左上角
elif abs(pos.x() - rect.left()) <= self.edge_size and abs(pos.y() - rect.top()) <= self.edge_size:
return True
# 左边缘
elif abs(pos.x() - rect.left()) <= self.edge_size and rect.top() <= pos.y() <= rect.bottom():
return True
# 右边缘
elif abs(pos.x() - rect.right()) <= self.edge_size and rect.top() <= pos.y() <= rect.bottom():
return True
# 上边缘
elif abs(pos.y() - rect.top()) <= self.edge_size and rect.left() <= pos.x() <= rect.right():
return True
# 下边缘
elif abs(pos.y() - rect.bottom()) <= self.edge_size and rect.left() <= pos.x() <= rect.right():
return True
return False
def get_resize_edge(self, pos, rect):
"""获取调整大小的边缘类型"""
# 右下角
if abs(pos.x() - rect.right()) <= self.edge_size and abs(pos.y() - rect.bottom()) <= self.edge_size:
return "bottomright"
# 右上角
elif abs(pos.x() - rect.right()) <= self.edge_size and abs(pos.y() - rect.top()) <= self.edge_size:
return "topright"
# 左下角
elif abs(pos.x() - rect.left()) <= self.edge_size and abs(pos.y() - rect.bottom()) <= self.edge_size:
return "bottomleft"
# 左上角
elif abs(pos.x() - rect.left()) <= self.edge_size and abs(pos.y() - rect.top()) <= self.edge_size:
return "topleft"
# 左边缘
elif abs(pos.x() - rect.left()) <= self.edge_size and rect.top() <= pos.y() <= rect.bottom():
return "left"
# 右边缘
elif abs(pos.x() - rect.right()) <= self.edge_size and rect.top() <= pos.y() <= rect.bottom():
return "right"
# 上边缘
elif abs(pos.y() - rect.top()) <= self.edge_size and rect.left() <= pos.x() <= rect.right():
return "top"
# 下边缘
elif abs(pos.y() - rect.bottom()) <= self.edge_size and rect.left() <= pos.x() <= rect.right():
return "bottom"
return None
def selection_mouse_move(self, event):
"""鼠标移动事件处理"""
if not self.enable_mouse_events:
return
pos = event.pos()
# 根据不同的操作模式处理鼠标移动
if self.is_drawing: # 绘制新选择框
# 更新选择框的右下角
current_rect = QRect(self.drag_start_pos, pos).normalized()
self.selection_rect = current_rect
self.update_preview_with_rect()
elif self.resize_edge and self.active_selection_index >= 0: # 调整选择框大小或位置
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_rects[self.active_selection_index].width()
original_height = self.selection_rects[self.active_selection_index].height()
# 计算新位置
new_rect = self.selection_rects[self.active_selection_index].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_rects[self.active_selection_index] = new_rect
self.drag_start_pos = pos
else:
# 调整选择框大小
rect = self.selection_rects[self.active_selection_index]
if "left" in self.resize_edge:
rect.setLeft(pos.x())
if "right" in self.resize_edge:
rect.setRight(pos.x())
if "top" in self.resize_edge:
rect.setTop(pos.y())
if "bottom" in self.resize_edge:
rect.setBottom(pos.y())
# 确保选择框在视频显示区域内
display_rect = self.video_display.rect()
if rect.left() < 0:
rect.setLeft(0)
if rect.top() < 0:
rect.setTop(0)
if rect.right() > display_rect.width():
rect.setRight(display_rect.width())
if rect.bottom() > display_rect.height():
rect.setBottom(display_rect.height())
self.selection_rects[self.active_selection_index] = rect
self.update_preview_with_rect()
else:
# 更新鼠标指针形状
self.update_cursor_shape(pos)
def selection_mouse_release(self, event):
"""鼠标释放事件处理"""
if not self.enable_mouse_events:
return
# 结束绘制或调整
if self.is_drawing:
# 标准化选择框(确保宽度和高度为正)
self.selection_rect = self.selection_rect.normalized()
# 如果选择框有效(不是点击),添加到选区列表
if self.selection_rect.width() > 5 and self.selection_rect.height() > 5:
self.selection_rects.append(self.selection_rect)
self.active_selection_index = len(self.selection_rects) - 1
# 保存选择框的相对位置和大小
self.save_selections_to_configs()
# 发送选择框变化信号
self.selection_changed.emit(self.selection_rects)
self.is_drawing = False
self.selection_rect = QRect()
elif self.resize_edge and self.active_selection_index >= 0:
# 标准化选择框
self.selection_rects[self.active_selection_index] = self.selection_rects[self.active_selection_index].normalized()
# 保存选择框的相对位置和大小
self.save_selections_to_configs()
# 发送选择框变化信号
self.selection_changed.emit(self.selection_rects)
self.resize_edge = None
def update_cursor_shape(self, pos):
"""根据鼠标位置更新光标形状"""
# 首先检查是否有活动选区
if self.active_selection_index >= 0 and self.active_selection_index < len(self.selection_rects):
rect = self.selection_rects[self.active_selection_index]
# 检查鼠标是否在选择框边缘
if (abs(pos.x() - rect.left()) <= self.edge_size and
rect.top() <= pos.y() <= rect.bottom()):
self.video_display.setCursor(Qt.SizeHorCursor)
return
elif (abs(pos.x() - rect.right()) <= self.edge_size and
rect.top() <= pos.y() <= rect.bottom()):
self.video_display.setCursor(Qt.SizeHorCursor)
return
elif (abs(pos.y() - rect.top()) <= self.edge_size and
rect.left() <= pos.x() <= rect.right()):
self.video_display.setCursor(Qt.SizeVerCursor)
return
elif (abs(pos.y() - rect.bottom()) <= self.edge_size and
rect.left() <= pos.x() <= rect.right()):
self.video_display.setCursor(Qt.SizeVerCursor)
return
elif (abs(pos.x() - rect.left()) <= self.edge_size and
abs(pos.y() - rect.top()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeFDiagCursor)
return
elif (abs(pos.x() - rect.right()) <= self.edge_size and
abs(pos.y() - rect.top()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeBDiagCursor)
return
elif (abs(pos.x() - rect.left()) <= self.edge_size and
abs(pos.y() - rect.bottom()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeBDiagCursor)
return
elif (abs(pos.x() - rect.right()) <= self.edge_size and
abs(pos.y() - rect.bottom()) <= self.edge_size):
self.video_display.setCursor(Qt.SizeFDiagCursor)
return
elif rect.contains(pos):
self.video_display.setCursor(Qt.SizeAllCursor)
return
# 如果没有活动选区或鼠标不在活动选区上,检查所有其他选区
for rect in self.selection_rects:
# 检查鼠标是否在选择框边缘
if self.is_on_rect_edge(pos, rect):
# 根据边缘类型设置光标
edge_type = self.get_resize_edge(pos, rect)
if edge_type == "left" or edge_type == "right":
self.video_display.setCursor(Qt.SizeHorCursor)
return
elif edge_type == "top" or edge_type == "bottom":
self.video_display.setCursor(Qt.SizeVerCursor)
return
elif edge_type == "topleft" or edge_type == "bottomright":
self.video_display.setCursor(Qt.SizeFDiagCursor)
return
elif edge_type == "topright" or edge_type == "bottomleft":
self.video_display.setCursor(Qt.SizeBDiagCursor)
return
# 检查鼠标是否在选择框内部
elif rect.contains(pos):
self.video_display.setCursor(Qt.SizeAllCursor)
return
# 如果鼠标不在任何选区上,设置为默认光标
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_selections_to_config()
self.update_preview_with_rect()
def load_selections_from_config(self):
"""从配置中加载选择框的相对位置和大小"""
# 检查是否有有效的视频尺寸
if not self.scaled_width or not self.scaled_height:
return False
# 从配置中读取选择框的相对位置和大小
areas_str = config.subtitleSelectionAreas.value
# 检查配置值是否有效
if not areas_str:
return False
# 清空现有选区
self.selection_rects = []
self.selection_ratios = []
# 解析所有选区
for area_str in areas_str.split(';'):
parts = area_str.split(',')
if len(parts) != 4:
continue
try:
x_ratio = float(parts[0])
y_ratio = float(parts[1])
w_ratio = float(parts[2])
h_ratio = float(parts[3])
# 检查配置值是否在有效范围内
if w_ratio <= 0.01 or h_ratio <= 0.005:
continue
# 保存选择框比例
self.selection_ratios.append((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_rects.append(QRect(x, y, w, h))
except ValueError:
continue
# 如果有选区,设置最后一个为活动选区
if self.selection_rects:
self.active_selection_index = len(self.selection_rects) - 1
else:
self.active_selection_index = -1
# 更新预览
self.update_preview_with_rect()
return len(self.selection_rects) > 0
def save_selections_to_config(self):
"""保存所有选择框的相对位置和大小"""
if not self.scaled_width or not self.scaled_height:
return
self.selection_ratios = []
areas_str_parts = []
for rect in self.selection_rects:
# 计算相对于实际视频的位置和大小比例
x_ratio = (rect.x() - self.border_left) / self.scaled_width if self.scaled_width > 0 else 0
y_ratio = (rect.y() - self.border_top) / self.scaled_height if self.scaled_height > 0 else 0
w_ratio = rect.width() / self.scaled_width if self.scaled_width > 0 else 0
h_ratio = rect.height() / self.scaled_height if self.scaled_height > 0 else 0
# 添加到比例列表
self.selection_ratios.append((x_ratio, y_ratio, w_ratio, h_ratio))
# 添加到字符串部分
areas_str_parts.append(f"{x_ratio},{y_ratio},{w_ratio},{h_ratio}")
# 更新配置
config.subtitleSelectionAreas.value = ";".join(areas_str_parts)
qconfig.save()
def get_original_coordinates(self):
"""获取选择框在原始视频中的坐标"""
selection_rects = []
for rect in self.selection_rects:
if not rect.isValid() or not self.scaled_width or not self.scaled_height:
continue
# 调整选择框坐标,考虑黑边偏移
x_adjusted = max(0, rect.x() - self.border_left)
y_adjusted = max(0, rect.y() - self.border_top)
# 如果选择框超出了实际视频区域,需要调整宽度和高度
w_adjusted = min(rect.width(), self.scaled_width - x_adjusted)
h_adjusted = min(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))
selection_rects.append((ymin, ymax, xmin, xmax))
return selection_rects
def set_dragger_enabled(self, enabled):
"""设置拖动器是否可用"""
self.enable_mouse_events = enabled
self.video_display.setMouseTracking(enabled)
self.video_display.setCursor(Qt.ArrowCursor)
def save_selections_to_configs(self):
"""保存所有选择框的相对位置和大小"""
if not self.scaled_width or not self.scaled_height:
return
self.selection_ratios = []
areas_str_parts = []
for rect in self.selection_rects:
# 计算相对于实际视频的位置和大小比例
x_ratio = round((rect.x() - self.border_left) / self.scaled_width if self.scaled_width > 0 else 0, 4)
y_ratio = round((rect.y() - self.border_top) / self.scaled_height if self.scaled_height > 0 else 0, 4)
w_ratio = round(rect.width() / self.scaled_width if self.scaled_width > 0 else 0, 4)
h_ratio = round(rect.height() / self.scaled_height if self.scaled_height > 0 else 0, 4)
# 添加到比例列表
self.selection_ratios.append((x_ratio, y_ratio, w_ratio, h_ratio))
# 添加到字符串部分
areas_str_parts.append(f"{x_ratio},{y_ratio},{w_ratio},{h_ratio}")
# 更新配置
config.subtitleSelectionAreas.value = ";".join(areas_str_parts)
if len(config.subtitleSelectionAreas.value) <= 0:
config.subtitleSelectionAreas.value = config.subtitleSelectionAreas.defaultValue
qconfig.save()
def get_selection_rects(self):
"""获取所有选区"""
return self.selection_rects
def clear_selections(self):
"""清除所有选区"""
self.selection_rects = []
self.selection_ratios = []
self.active_selection_index = -1
self.update_preview_with_rect()
self.selection_changed.emit(self.selection_rects)
def eventFilter(self, obj, event):
"""事件过滤器,用于处理键盘事件"""
if event.type() == QEvent.KeyPress:
# 处理退格键或删除键
if (event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete) and self.active_selection_index >= 0:
# 删除当前活跃选区
self.selection_rects.pop(self.active_selection_index)
if self.selection_ratios:
self.selection_ratios.pop(self.active_selection_index)
# 如果还有选区,将最后一个选区设为活跃选区
if self.selection_rects:
self.active_selection_index = len(self.selection_rects) - 1
else:
self.active_selection_index = -1
self.save_selections_to_configs()
# 更新显示并发送选区变化信号
self.update_preview_with_rect()
self.selection_changed.emit(self.selection_rects)
return True
# 对于其他事件,继续传递给父类处理
return super().eventFilter(obj, event)