Spaces:
Running
Running
| from pathlib import Path | |
| from PySide6.QtCore import Qt, QTimer, QSize, QUrl | |
| from PySide6.QtGui import QIcon, QDesktopServices | |
| from PySide6.QtWidgets import ( | |
| QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QLabel, | |
| QPushButton, QWidget, QTableWidget, QTableWidgetItem, QHeaderView, QAbstractItemView | |
| ) | |
| from videotrans.configure.config import ROOT_DIR, tr, settings, logger | |
| from videotrans.util import tools | |
| class EditRecognResultDialog2(QDialog): | |
| def __init__( | |
| self, | |
| parent=None, | |
| target_sub: str = None | |
| ): | |
| super().__init__() | |
| self.parent = parent | |
| self.target_sub = target_sub | |
| self.srt_list_dict = tools.get_subtitle_from_srt(self.target_sub) | |
| self.setWindowTitle( tr("Modify secondary recognition results") ) | |
| self.setWindowIcon(QIcon(f"{ROOT_DIR}/videotrans/styles/icon.ico")) | |
| self.setMinimumWidth(int(parent.width*0.95)) | |
| self.setMinimumHeight(int(parent.height*0.95)) | |
| self.setWindowFlags(Qt.Window | | |
| Qt.WindowStaysOnTopHint | # 2. 始终在最顶层 | |
| Qt.WindowTitleHint | # 3. 显示标题栏 | |
| Qt.CustomizeWindowHint | # 4. 允许自定义标题栏按钮(否则OS会强制加关闭按钮) | |
| Qt.WindowMaximizeButtonHint # 5. 只加最大化按钮,不加关闭按钮 | |
| ) | |
| self.count_down = int(float(settings.get('countdown_sec', 1))) | |
| main_layout = QVBoxLayout(self) | |
| # Top Bar | |
| hstop = QHBoxLayout() | |
| self.prompt_label = QLabel(tr("jimiaohoufanyi")) | |
| self.prompt_label.setStyleSheet('font-size:14px;color:#aaaaaa') | |
| hstop.addWidget(self.prompt_label) | |
| self.stop_button = QPushButton(f"{tr('Click here to stop the countdown')}({self.count_down})") | |
| self.stop_button.setStyleSheet("font-size: 16px;color:#ffff00") | |
| self.stop_button.setCursor(Qt.PointingHandCursor) | |
| self.stop_button.clicked.connect(self.stop_countdown) | |
| hstop.addWidget(self.stop_button) | |
| main_layout.addLayout(hstop) | |
| prompt_label2 = QLabel(tr("If you need to delete a line of subtitles, just clear the text in that line")) | |
| prompt_label2.setAlignment(Qt.AlignCenter) | |
| main_layout.addWidget(prompt_label2) | |
| # Search Bar | |
| search_replace_layout = QHBoxLayout() | |
| search_replace_layout.addStretch() | |
| self.search_input = QLineEdit() | |
| self.search_input.setPlaceholderText(tr("Original text")) | |
| self.replace_input = QLineEdit() | |
| self.replace_input.setPlaceholderText(tr("Replace")) | |
| replace_button = QPushButton(tr("Replace")) | |
| replace_button.clicked.connect(self.replace_text) | |
| search_replace_layout.addWidget(self.search_input) | |
| search_replace_layout.addWidget(self.replace_input) | |
| search_replace_layout.addWidget(replace_button) | |
| search_replace_layout.addStretch() | |
| main_layout.addLayout(search_replace_layout) | |
| # Loading | |
| self.loading_widget = QWidget() | |
| self.loading_label = QLabel(tr('Loading...'), self) | |
| self.loading_label.setAlignment(Qt.AlignCenter) | |
| load_layout = QVBoxLayout(self.loading_widget) | |
| load_layout.addWidget(self.loading_label) | |
| main_layout.addWidget(self.loading_widget) | |
| # Table Widget (初始隐藏) | |
| self.table = QTableWidget() | |
| self.table.setVisible(False) | |
| main_layout.addWidget(self.table) | |
| # Bottom Bar | |
| self.save_button = QPushButton(tr("nextstep")) | |
| self.save_button.clicked.connect(self.save_and_close) | |
| self.save_button.setCursor(Qt.PointingHandCursor) | |
| self.save_button.setMinimumSize(QSize(300, 35)) | |
| self.save_button2 = QPushButton(tr("nosaveandstep")) | |
| self.save_button2.clicked.connect(self.save_and_close2) | |
| self.save_button2.setCursor(Qt.PointingHandCursor) | |
| self.save_button2.setMinimumSize(QSize(200, 35)) | |
| cancel_button = QPushButton(tr("Terminate this mission")) | |
| cancel_button.clicked.connect(self.cancel_and_close) | |
| cancel_button.setCursor(Qt.PointingHandCursor) | |
| cancel_button.setMaximumSize(QSize(150, 30)) | |
| cancel_button.setStyleSheet("background-color:transparent;color:#ff0") | |
| bottom_layout = QHBoxLayout() | |
| bottom_layout.addStretch() | |
| bottom_layout.addWidget(self.save_button) | |
| bottom_layout.addWidget(self.save_button2) | |
| bottom_layout.addWidget(cancel_button) | |
| bottom_layout.addStretch() | |
| main_layout.addLayout(bottom_layout) | |
| # 延迟加载表格 | |
| QTimer.singleShot(10, self.load_table) | |
| def load_table(self): | |
| """极致性能加载表格""" | |
| if not self.isVisible(): | |
| return | |
| try: | |
| # 1. 创建 QTableWidget | |
| self.table.setColumnCount(3) | |
| self.table.setHorizontalHeaderLabels([tr("Line"), tr("Time Axis"), tr("Subtitle Text")]) | |
| # 2. 禁用所有非必要功能 | |
| self.table.setShowGrid(False) | |
| self.table.setAlternatingRowColors(False) | |
| self.table.setWordWrap(False) | |
| self.table.setMouseTracking(False) | |
| self.table.setFocusPolicy(Qt.NoFocus) | |
| self.table.setSelectionMode(QAbstractItemView.NoSelection) | |
| # 垂直表头 | |
| v_header = self.table.verticalHeader() | |
| v_header.setVisible(False) | |
| v_header.setSectionResizeMode(QHeaderView.Fixed) | |
| v_header.setDefaultSectionSize(22) # 最小行高 | |
| # 水平表头 | |
| h_header = self.table.horizontalHeader() | |
| h_header.setStretchLastSection(True) | |
| h_header.setSectionResizeMode(0, QHeaderView.Fixed) | |
| h_header.setSectionResizeMode(1, QHeaderView.Fixed) | |
| h_header.setSectionResizeMode(2, QHeaderView.Fixed) | |
| self.table.setColumnWidth(0, 50) | |
| self.table.setColumnWidth(1, 230) | |
| self.table.setStyleSheet(""" | |
| QTableWidget { | |
| color: #cccccc; | |
| border: none; | |
| } | |
| QTableWidget::item { | |
| padding: 2px; | |
| border: none; | |
| } | |
| QHeaderView::section { | |
| background-color: #2b2b2b; | |
| color: #dddddd; | |
| border: none; | |
| border-right: 1px solid #3e3e3e; | |
| padding: 3px; | |
| height: 24px; | |
| } | |
| """) | |
| # 3. 预计算所有显示数据 | |
| self.display_data = [] | |
| for item in self.srt_list_dict: | |
| duration = (item['end_time'] - item['start_time']) / 1000.0 | |
| time_str = f"{item['startraw']} --> {item['endraw']} ({duration:.1f}s)" | |
| self.display_data.append({ | |
| 'line': item['line'], | |
| 'time_str': time_str, | |
| 'text': item['text'], | |
| 'startraw': item['startraw'], | |
| 'endraw': item['endraw'] | |
| }) | |
| # 4. 设置行数 | |
| total_rows = len(self.display_data) | |
| self.table.setRowCount(total_rows) | |
| # 5. 【分批填充】先填充前100行,避免界面冻结 | |
| self._batch_fill(0, min(total_rows, 100)) | |
| # 6. 显示表格 | |
| self.loading_widget.setVisible(False) | |
| self.table.setVisible(True) | |
| # 7. 延迟加载剩余数据 | |
| if total_rows > 100: | |
| QTimer.singleShot(0, lambda: self._load_remaining(100)) | |
| # 8. 启动倒计时 | |
| self.timer = QTimer(self) | |
| self.timer.timeout.connect(self.update_countdown) | |
| self.timer.start(1000) | |
| if self.parent: | |
| self.parent.activateWindow() | |
| except Exception as e: | |
| import traceback | |
| traceback.print_exc() | |
| self.loading_label.setText(f"Error: {e}") | |
| def _batch_fill(self, start_row, end_row): | |
| """批量填充数据""" | |
| for row in range(start_row, end_row): | |
| data = self.display_data[row] | |
| # Line (只读) | |
| item0 = QTableWidgetItem(str(data['line'])) | |
| item0.setFlags(Qt.ItemIsEnabled) | |
| self.table.setItem(row, 0, item0) | |
| # Time (只读) | |
| item1 = QTableWidgetItem(data['time_str']) | |
| item1.setFlags(Qt.ItemIsEnabled) | |
| self.table.setItem(row, 1, item1) | |
| # Text (可编辑) | |
| item2 = QTableWidgetItem(data['text']) | |
| item2.setFlags(Qt.ItemIsEnabled | Qt.ItemIsEditable | Qt.ItemIsSelectable) | |
| self.table.setItem(row, 2, item2) | |
| def _load_remaining(self, start_row): | |
| """延迟加载剩余行""" | |
| total = len(self.display_data) | |
| batch_size = 300 # 每批300行 | |
| end_row = min(start_row + batch_size, total) | |
| self._batch_fill(start_row, end_row) | |
| if end_row < total: | |
| QTimer.singleShot(0, lambda: self._load_remaining(end_row)) | |
| def cancel_and_close(self): | |
| if hasattr(self, 'timer') and self.timer: | |
| self.timer.stop() | |
| self.reject() | |
| def update_countdown(self): | |
| self.count_down -= 1 | |
| if self.stop_button: | |
| self.stop_button.setText(f"{tr('Click here to stop the countdown')}({self.count_down})") | |
| if self.count_down <= 0: | |
| self.timer.stop() | |
| self.save_and_close() | |
| def stop_countdown(self): | |
| if hasattr(self, 'timer'): | |
| self.timer.stop() | |
| self.stop_button.deleteLater() | |
| self.prompt_label.deleteLater() | |
| def replace_text(self): | |
| """替换文本""" | |
| search_text = self.search_input.text() | |
| replace_text = self.replace_input.text() | |
| if not search_text: | |
| return | |
| # 禁用更新提升性能 | |
| self.table.setUpdatesEnabled(False) | |
| first = -1 | |
| last = -1 | |
| for i, data in enumerate(self.display_data): | |
| if search_text in data['text']: | |
| new_text = data['text'].replace(search_text, replace_text) | |
| data['text'] = new_text | |
| item = self.table.item(i, 2) | |
| if item: | |
| item.setText(new_text) | |
| if first == -1: | |
| first = i | |
| last = i | |
| self.table.setUpdatesEnabled(True) | |
| def save_and_close2(self): | |
| self.accept() | |
| def closeEvent(self, event): | |
| event.ignore() # 忽略关闭请求,窗口保持不动 | |
| def save_and_close(self): | |
| self.save_button.setDisabled(True) | |
| srt_str_list = [] | |
| # 从表格获取最新文本 | |
| for i, data in enumerate(self.display_data): | |
| item = self.table.item(i, 2) | |
| text = item.text().strip() if item else data['text'].strip() | |
| if text: | |
| srt_str_list.append(f'{data["line"]}\n{data["startraw"]} --> {data["endraw"]}\n{text}') | |
| try: | |
| Path(self.target_sub).write_text("\n\n".join(srt_str_list), encoding="utf-8") | |
| except Exception as e: | |
| logger.exception(f"Save error: {e}",exc_info=True) | |
| self.accept() |