diff --git a/backend/config.py b/backend/config.py index 00c3464..2d11076 100644 --- a/backend/config.py +++ b/backend/config.py @@ -2,7 +2,7 @@ import os from pathlib import Path from qfluentwidgets import (qconfig, ConfigItem, QConfig, OptionsValidator, BoolValidator, OptionsConfigItem, - EnumSerializer, RangeValidator, RangeConfigItem) + EnumSerializer, RangeValidator, RangeConfigItem, ConfigValidator) from backend.tools.constant import InpaintMode, SubtitleDetectMode import configparser @@ -105,6 +105,9 @@ class Config(QConfig): # 启动时检查应用更新 checkUpdateOnStartup = ConfigItem("Main", "CheckUpdateOnStartup", True, BoolValidator()) + # 视频保存目录 + saveDirectory = ConfigItem("Main", "SaveDirectory", "", ConfigValidator()) + CONFIG_FILE = 'config/config.json' config = Config() qconfig.load(CONFIG_FILE, config) diff --git a/backend/interface/ch.ini b/backend/interface/ch.ini index 0b90e6c..342a09c 100644 --- a/backend/interface/ch.ini +++ b/backend/interface/ch.ini @@ -43,6 +43,9 @@ UpdatesAvailableTitle = 有可用更新 UpdatesAvailableDesc = 发现新版本 {}, 是否更新? NoUpdatesAvailableTitle = 无可用更新 NoUpdatesAvailableDesc = 软件已是最新版本 +ChooseDirectory = 选择文件夹 +SaveDirectory = 视频保存目录 +SaveDirectoryDefault = 默认保存到输入视频当前目录 [SubtitleExtractorGUI] Title = 字幕去除器 diff --git a/backend/interface/chinese_cht.ini b/backend/interface/chinese_cht.ini index 45aa8a6..ff42f9a 100644 --- a/backend/interface/chinese_cht.ini +++ b/backend/interface/chinese_cht.ini @@ -43,6 +43,9 @@ UpdatesAvailableTitle = 有可用更新 UpdatesAvailableDesc = 發現新版本 {},是否更新? NoUpdatesAvailableTitle = 無可用更新 NoUpdatesAvailableDesc = 軟體已是最新版本 +ChooseDirectory = 選擇資料夾 +SaveDirectory = 影片保存目錄 +SaveDirectoryDefault = 預設保存到輸入影片的當前目錄 [SubtitleExtractorGUI] Title = 字幕去除器 diff --git a/backend/interface/en.ini b/backend/interface/en.ini index 80c8542..f440937 100644 --- a/backend/interface/en.ini +++ b/backend/interface/en.ini @@ -42,7 +42,10 @@ CheckUpdateOnStartupDesc = New versions offer improved stability and features (r UpdatesAvailableTitle = Update Available UpdatesAvailableDesc = New version {} found. Update now? NoUpdatesAvailableTitle = No Updates Available -NoUpdatesAvailableDesc = Software is up-to-date. +NoUpdatesAvailableDesc = Software is up-to-date. +ChooseDirectory = Choose Folder +SaveDirectory = Video save directory +SaveDirectoryDefault = Default save to the current directory of the input video [SubtitleExtractorGUI] Title = Subtitle Remover diff --git a/backend/interface/es.ini b/backend/interface/es.ini index 50428ee..0580c94 100644 --- a/backend/interface/es.ini +++ b/backend/interface/es.ini @@ -43,6 +43,9 @@ UpdatesAvailableTitle = Actualización disponible UpdatesAvailableDesc = Nueva versión {} disponible. ¿Actualizar ahora? NoUpdatesAvailableTitle = Sin actualizaciones NoUpdatesAvailableDesc = El software está actualizado. +ChooseDirectory = Seleccionar Carpeta +SaveDirectory = Directorio de guardado de video +SaveDirectoryDefault = Guardar por defecto en el directorio actual del video de entrada [SubtitleExtractorGUI] Title = Eliminador de subtítulos diff --git a/backend/interface/japan.ini b/backend/interface/japan.ini index 9ee98d9..b7ce6e9 100644 --- a/backend/interface/japan.ini +++ b/backend/interface/japan.ini @@ -43,6 +43,9 @@ UpdatesAvailableTitle = 利用可能なアップデート UpdatesAvailableDesc = 新バージョン {} を発見。更新しますか? NoUpdatesAvailableTitle = 利用可能なアップデートなし NoUpdatesAvailableDesc = 最新バージョンです +ChooseDirectory = フォルダを選択 +SaveDirectory = 動画保存ディレクトリ +SaveDirectoryDefault = 入力動画の現在ディレクトリにデフォルトで保存 [SubtitleExtractorGUI] Title = 字幕除去ツール diff --git a/backend/interface/ko.ini b/backend/interface/ko.ini index da6a674..eac91d3 100644 --- a/backend/interface/ko.ini +++ b/backend/interface/ko.ini @@ -43,6 +43,9 @@ UpdatesAvailableTitle = 업데이트 가능 UpdatesAvailableDesc = 새 버전 {} 발견. 업데이트할까요? NoUpdatesAvailableTitle = 사용 가능한 업데이트 없음 NoUpdatesAvailableDesc = 최신 버전입니다. +ChooseDirectory = 폴더 선택 +SaveDirectory = 동영상 저장 디렉터리 +SaveDirectoryDefault = 입력 동영상의 현재 디렉터리에 기본 저장 [SubtitleExtractorGUI] Title = 자막 제거 도구 diff --git a/backend/interface/vi.ini b/backend/interface/vi.ini index b8d56aa..7eb8f17 100644 --- a/backend/interface/vi.ini +++ b/backend/interface/vi.ini @@ -43,6 +43,9 @@ UpdatesAvailableTitle = Có bản cập nhật UpdatesAvailableDesc = Phát hiện phiên bản mới {}, cập nhật? NoUpdatesAvailableTitle = Không có cập nhật NoUpdatesAvailableDesc = Đang dùng phiên bản mới nhất +ChooseDirectory = Chọn Thư Mục +SaveDirectory = Thư mục lưu video +SaveDirectoryDefault = Mặc định lưu vào thư mục hiện tại của video đầu vào [SubtitleExtractorGUI] Title = Công cụ xóa phụ đề diff --git a/backend/main.py b/backend/main.py index e4c0cf8..e729f55 100644 --- a/backend/main.py +++ b/backend/main.py @@ -337,6 +337,7 @@ class SubtitleRemover: self.append_output(tr['Main']['SubtitleDetectionAcceleratorON'].format(accelerator_name)) if accelerator_name == 'DirectML' and config.inpaintMode.value not in [InpaintMode.STTN_AUTO, InpaintMode.STTN_DET]: self.append_output(tr['Main']['DirectMLWarning']) + os.makedirs(os.path.dirname(self.video_out_path), exist_ok=True) # 重置进度条 self.progress_total = 0 tbar = tqdm(total=int(self.frame_count), unit='frame', position=0, file=sys.__stdout__, diff --git a/ui/advanced_setting_interface.py b/ui/advanced_setting_interface.py index 135eb68..1bad94c 100644 --- a/ui/advanced_setting_interface.py +++ b/ui/advanced_setting_interface.py @@ -3,10 +3,11 @@ """ from PySide6 import QtWidgets, QtCore, QtGui +from PySide6.QtWidgets import QFileDialog from qfluentwidgets import (ScrollArea, ExpandLayout, CardWidget, SubtitleLabel, FluentIcon, NavigationWidget, NavigationItemPosition, SettingCardGroup, RangeSettingCard, SwitchSettingCard, - HyperlinkCard, PrimaryPushSettingCard, ComboBoxSettingCard, + HyperlinkCard, PrimaryPushSettingCard, PushSettingCard, MessageBox) from backend.config import config, tr, VERSION, PROJECT_HOME_URL, PROJECT_ISSUES_URL, PROJECT_RELEASES_URL from backend.tools.version_service import VersionService @@ -58,6 +59,7 @@ class AdvancedSettingInterface(ScrollArea): self.propainter_group.addSettingCard(self.propainter_max_load_num) self.expandLayout.addWidget(self.propainter_group) + self.advanced_group.addSettingCard(self.save_directory) self.advanced_group.addSettingCard(self.check_update_on_startup) self.expandLayout.addWidget(self.advanced_group) @@ -170,6 +172,16 @@ class AdvancedSettingInterface(ScrollArea): parent=self.propainter_group ) + # 视频保存路径 + self.save_directory = PushSettingCard( + text=tr["Setting"]["ChooseDirectory"], + icon=FluentIcon.DOWNLOAD, + title=tr["Setting"]["SaveDirectory"], + content=tr["Setting"]["SaveDirectoryDefault"] if not config.saveDirectory.value else config.saveDirectory.value, + parent=self.advanced_group + ) + self.save_directory.clicked.connect(self.choose_save_directory) + self.check_update_on_startup = SwitchSettingCard( configItem=config.checkUpdateOnStartup, icon=FluentIcon.UPDATE, @@ -244,4 +256,15 @@ class AdvancedSettingInterface(ScrollArea): self.show_message_box( tr["Setting"]["NoUpdatesAvailableTitle"], tr["Setting"]["NoUpdatesAvailableDesc"], - ) \ No newline at end of file + ) + + def choose_save_directory(self): + """选择保存目录""" + last_save_directory = "./" if not config.saveDirectory.value else config.saveDirectory.value + folder = QFileDialog.getExistingDirectory( + self, tr['Setting']['ChooseDirectory'], last_save_directory) + if not folder: + folder = "" + + config.set(config.saveDirectory, folder) + self.save_directory.setContent(tr["Setting"]["SaveDirectoryDefault"] if not config.saveDirectory.value else config.saveDirectory.value) \ No newline at end of file diff --git a/ui/component/task_list_component.py b/ui/component/task_list_component.py index b544854..8f13417 100644 --- a/ui/component/task_list_component.py +++ b/ui/component/task_list_component.py @@ -1,13 +1,17 @@ import os +from pathlib import Path from enum import Enum, unique from dataclasses import dataclass +from functools import cached_property + 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 +from backend.config import config, tr +from backend.tools.common_tools import is_image_file @unique class TaskStatus(Enum): @@ -28,8 +32,30 @@ class Task: name: str progress: int status: TaskStatus - output_path: str options: dict + # 用于储存只读的输出路径, 在任务完成后设置 + _output_path: str = None + + @property + def output_path(self): + """获取输出路径""" + if self._output_path is not None: + return self._output_path + save_directory = os.path.dirname(self.path) if not config.saveDirectory.value else config.saveDirectory.value + if self.is_image: + output_path = os.path.abspath(os.path.join(save_directory, f'{Path(self.path).stem}_no_sub.png')) + else: + output_path = os.path.abspath(os.path.join(save_directory, f'{Path(self.path).stem}_no_sub.mp4')) + return output_path + + @output_path.setter + def output_path(self, value): + self._output_path = value + + @cached_property + def is_image(self): + """判断是否是图片文件""" + return is_image_file(self.path) class TaskListComponent(QWidget): """任务列表组件""" @@ -81,7 +107,7 @@ class TaskListComponent(QWidget): layout.addWidget(self.table) - def add_task(self, video_path, output_path): + def add_task(self, video_path): """添加任务到列表 Args: @@ -102,7 +128,6 @@ class TaskListComponent(QWidget): name=file_name, progress=0, status=TaskStatus.PENDING, - output_path=output_path, options={}, ) self.tasks.append(task) diff --git a/ui/home_interface.py b/ui/home_interface.py index 4a69fe4..00f0417 100644 --- a/ui/home_interface.py +++ b/ui/home_interface.py @@ -4,7 +4,6 @@ import threading import multiprocessing import time import traceback -from pathlib import Path from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout from PySide6.QtCore import Slot, QRect, Signal from PySide6 import QtWidgets @@ -13,7 +12,7 @@ from ui.setting_interface import SettingInterface from ui.component.video_display_component import VideoDisplayComponent from ui.component.task_list_component import TaskListComponent, TaskStatus, TaskOptions from ui.icon.my_fluent_icon import MyFluentIcon -from backend.config import tr +from backend.config import config, tr from backend.tools.subtitle_remover_remote_call import SubtitleRemoverRemoteCall from backend.tools.process_manager import ProcessManager from backend.tools.common_tools import get_readable_path, is_image_file, read_image @@ -344,12 +343,17 @@ class HomeInterface(QWidget): if key == TaskOptions.SUB_AREAS.value: value = self.video_display_component.preview_coordinates_to_video_coordinates(value) options[key] = value - process = self.run_subtitle_remover_process(task.path, task.output_path, options) + # 清理缓存, 使用动态路径 + task.output_path = None + output_path = task.output_path + process = self.run_subtitle_remover_process(task.path, output_path, options) # 更新任务状态为已完成 task = self.task_list_component.get_task(self.current_processing_task_index) if process.exitcode == 0 and task and task.status == TaskStatus.PROCESSING: self.progress_signal.emit(100, True) + # 任务完成, 更新输出路径为只读 + task.output_path = output_path self.task_list_component.update_task_status(self.current_processing_task_index, TaskStatus.COMPLETED) else: self.task_list_component.update_task_status(self.current_processing_task_index, TaskStatus.FAILED) @@ -584,11 +588,7 @@ class HomeInterface(QWidget): # 正序添加, 确保任务列表顺序一致 for path in reversed(files_loaded): # 添加到任务列表 - if is_image_file(path): - output_path = os.path.abspath(os.path.join(os.path.dirname(path), f'{Path(path).stem}_no_sub.png')) - else: - output_path = os.path.abspath(os.path.join(os.path.dirname(path), f'{Path(path).stem}_no_sub.mp4')) - self.task_list_component.add_task(path, output_path) + self.task_list_component.add_task(path) index = max(0, self.task_list_component.find_task_index_by_path(path)) self.task_list_component.select_task(index)