diff --git a/FasterWhisperGUI.py b/FasterWhisperGUI.py index 15c2549..5a01a6a 100644 --- a/FasterWhisperGUI.py +++ b/FasterWhisperGUI.py @@ -25,15 +25,6 @@ faster_whisper_logger_handler.setFormatter(formatter1) logger_faster_whisper.addHandler(faster_whisper_logger_handler) -# pyside6 日志 -# logger_pyside = logging.getLogger("faster_whisper_GUI") -# logger_pyside.setLevel(logging.DEBUG) -# pyside_logger_handler = logging.FileHandler(r"./pyside.log", mode="w") -# pyside_logger_handler.setLevel(logging.DEBUG) -# formatter2 = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",datefmt='%Y-%m-%d_%H:%M:%S') -# pyside_logger_handler.setFormatter(formatter2) -# logger_pyside.addHandler(pyside_logger_handler) - # # 将默认的递归深度修改为3000 # sys.setrecursionlimit(7000) @@ -43,7 +34,6 @@ from qfluentwidgets import ProgressBar - class MySplashScreen(QSplashScreen): # 鼠标点击事件 def mousePressEvent(self, event): @@ -51,12 +41,10 @@ def mousePressEvent(self, event): from resource import rc_Image - # 启动一个Qt程序,并使用传入的系统参数 app = QApplication(sys.argv) app.setObjectName("FasterWhisperGUIAPP") - #设置启动界面 splash = MySplashScreen() @@ -84,8 +72,6 @@ def mousePressEvent(self, event): # 显示启动界面 splash.show() - - # app.processEvents() # 处理主进程事件 import os diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..f88867b --- /dev/null +++ b/config/config.json @@ -0,0 +1,6 @@ +{ + "QFluentWidgets": { + "ThemeColor": "#ffdb4437", + "ThemeMode": "Light" + } +} \ No newline at end of file diff --git a/fasterWhisperGUIConfig.json b/fasterWhisperGUIConfig.json index a2e0034..6432c1a 100644 --- a/fasterWhisperGUIConfig.json +++ b/fasterWhisperGUIConfig.json @@ -8,7 +8,7 @@ "model_param": { "localModel": true, "onlineModel": false, - "model_path": "F:/WhisperModels/faster-whisper/large-v3-float32", + "model_path": "", "modelName": 0, "use_v3_model": true, "device": 1, @@ -16,7 +16,7 @@ "preciese": 5, "thread_num": "4", "num_worker": "1", - "download_root": "C:/Users/12059/.cache/huggingface/hub", + "download_root": "", "local_files_only": false }, "vad_param": { @@ -34,16 +34,17 @@ "language": 2, "huggingface_user_token": "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", "autoGoToOutputPage": 2, - "autoClearTempFiles": true + "autoClearTempFiles": true, + "themeColor": "#009faa" }, "Transcription_param": { - "language": 0, + "language": 1, "task": false, "beam_size": "5", "best_of": "1", "patience": "1.0", "length_penalty": "1.0", - "temperature": "0.5", + "temperature": "0.3", "compression_ratio_threshold": "2.4", "log_prob_threshold": "-1.0", "no_speech_threshold": "0.6", @@ -65,7 +66,7 @@ "tabMovable": false, "tabScrollable": true, "tabShadowEnabled": false, - "tabMaxWidth": 300, + "tabMaxWidth": 403, "closeDisplayMode": 0, "whisperXMinSpeaker": 2, "whisperXMaxSpeaker": 2, diff --git a/faster_whisper_GUI/UI_MainWindows.py b/faster_whisper_GUI/UI_MainWindows.py index 6d0028b..e735e46 100644 --- a/faster_whisper_GUI/UI_MainWindows.py +++ b/faster_whisper_GUI/UI_MainWindows.py @@ -27,6 +27,7 @@ NavigationAvatarWidget, NavigationInterface , setTheme + , setThemeColor , Theme , FluentIcon , NavigationItemPosition @@ -86,9 +87,20 @@ def tr(self, text): # app.installTranslator(self.translator) - def readConfigJson(self): + def readConfigJson(self, config_file_path: str = ""): + self.default_theme = "light" + self.model_param = {} + self.setting = {} + self.demucs = {} + self.Transcription_param = {} + self.output_whisperX_param = {} + self.vad_param = {} + + if not config_file_path: + return + self.use_auth_token_speaker_diarition= "" - with open(r"./fasterWhisperGUIConfig.json","r", encoding="utf8") as fp: + with open(os.path.abspath(config_file_path),"r", encoding="utf8") as fp: json_data = json.load(fp) try: @@ -130,6 +142,7 @@ def readConfigJson(self): def setConfig(self): setTheme(Theme.DARK if self.default_theme == "dark" else Theme.LIGHT) + # setThemeColor("#aaff009f") if self.model_param != {}: self.page_model.setParam(self.model_param) if not self.model_param["download_root"]: @@ -165,7 +178,6 @@ def __init__(self, translator:QTranslator=None): else: self.translator = translator - # TODO: 无边框窗口方案需要等待 pyside6>6.5.0 支持 # self.setWindowFlags(Qt.FramelessWindowHint) # self.setAttribute(Qt.WA_TranslucentBackground) @@ -186,10 +198,16 @@ def __init__(self, translator:QTranslator=None): self.initWin() # 读配置文件 - self.readConfigJson() + self.readConfigJson(r"./fasterWhisperGUIConfig.json") # 设置配置 self.setConfig() + try: + self.setWidgetsStatusFromConfig() + except Exception as e: + print(str(e)) + + def setWidgetsStatusFromConfig(self): # 根据读取的配置设置完控件状态之后,根据控件状态设置相关属性 self.page_output.tableTab.onDisplayModeChanged(self.page_output.tableTab.closeDisplayModeComboBox.currentIndex()) self.page_output.tableTab.tabBar.setMovable(self.page_output.tableTab.movableCheckBox.isChecked()) @@ -197,6 +215,7 @@ def __init__(self, translator:QTranslator=None): self.page_output.tableTab.tabBar.setTabShadowEnabled(self.page_output.tableTab.shadowEnabledCheckBox.isChecked()) self.page_output.tableTab.tabBar.setTabMaximumWidth(self.page_output.tableTab.tabMaxWidthSpinBox.value()) + def initWin(self): self.setObjectName("FramlessMainWin") diff --git a/faster_whisper_GUI/config.py b/faster_whisper_GUI/config.py index 3a2f733..aa5350b 100644 --- a/faster_whisper_GUI/config.py +++ b/faster_whisper_GUI/config.py @@ -154,3 +154,31 @@ "GB2312":"gb18030", "ANSI":"ansi" } + +THEME_COLORS = [ + "#009faa", + "#ff009f", + "#84BE84", + "#aaff00", + "#FF9500", + "#00CD00", + "#DB4437", + "#23CD5E", + "#E61D34", + "#00FF00", + "#FF00FF", + "#1ABC9C", + "#FF3300", + "#FFFF00", + "#FFC019", + "#FF6600", + "#00FFFF", + "#FF7A1D", + "#E71A1B", + "#FF8800", + "#3388FF", + "#F4B400", + "#0069B7", + "#FFCC00", + "#0078D4", +] diff --git a/faster_whisper_GUI/de_mucs.py b/faster_whisper_GUI/de_mucs.py index 5aa9579..d0c67f5 100644 --- a/faster_whisper_GUI/de_mucs.py +++ b/faster_whisper_GUI/de_mucs.py @@ -55,7 +55,7 @@ def run(self) -> None: self.file_process_status.emit({"file":"", "status":False, "task": "load model"}) if self.model is None: try: - self.loadModel(self.model_path,device=device) + self.loadModel(self.model_path, device=device) except Exception as e: print(f"load model error: \n{str(e)}") self.signal_vr_over.emit(False) @@ -78,7 +78,9 @@ def run(self) -> None: try: samples = self.reSampleAudio(audio, 44100, device=device) - samples = torch.tensor(samples,dtype=torch.float32).to(device) + samples = np.asarray(samples) + print("samples shape: ", samples.shape) + # samples = torch.tensor(samples,dtype=torch.float32).to(device) except Exception as e: print(f"resample audio error:\n{str(e)}") self.signal_vr_over.emit(False) @@ -96,7 +98,8 @@ def run(self) -> None: self.file_process_status.emit({"file":audio, "status":False, "task": "separate sources"}) try: - sources = self.separate_sources(self.model, + sources = self.separate_sources( + self.model, samples[None], self.segment, self.overlap, @@ -104,7 +107,7 @@ def run(self) -> None: self.sampleRate ) except Exception as e: - print(f"separate audio sources error:\n{str(e)}") + print(f"\nseparate audio sources error:\n {str(e)}") self.signal_vr_over.emit(False) self.stop() @@ -192,7 +195,8 @@ def separate_sources(self, be on `device`, while the entire tracks will be stored on `mix.device`. """ if device is None: - device = mix.device + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) else: device = device @@ -211,6 +215,7 @@ def separate_sources(self, return None chunk = mix[:, :, start:end] + chunk = torch.tensor(chunk, dtype=torch.float32).to(device) with torch.no_grad(): out = model.forward(chunk) diff --git a/faster_whisper_GUI/mainWindows.py b/faster_whisper_GUI/mainWindows.py index 6acb735..c5eb102 100644 --- a/faster_whisper_GUI/mainWindows.py +++ b/faster_whisper_GUI/mainWindows.py @@ -28,8 +28,10 @@ , FluentIcon , isDarkTheme ) -from faster_whisper import TranscriptionInfo + +from faster_whisper.transcribe import TranscriptionInfo import torch + from .config import ( Task_list , STR_BOOL @@ -201,7 +203,7 @@ def onModelLoadClicked(self): param_for_model_load = { "model_size_or_path":model_param["model_size_or_path"], - "device":model_param["device"] , + "device":model_param["device"], "device_index":model_param["device_index"], "compute_type":model_param["compute_type"], "cpu_threads":model_param["cpu_threads"], @@ -247,6 +249,7 @@ def getParam_model(self) -> dict: """ 获取模型参数 """ + if self.page_model.model_local_RadioButton.isChecked(): model_size_or_path = self.page_model.lineEdit_model_path.text() else: @@ -280,7 +283,6 @@ def getParam_model(self) -> dict: def onButtonProcessClicked(self): - # self.result_whisperx_aligment = None # self.result_faster_whisper = None # self.result_whisperx_speaker_diarize = None @@ -328,7 +330,8 @@ def audioCaptureProcess(self): channels = rate_channel_dType["channel"] dType = rate_channel_dType["dType"] - self.audio_capture_thread = CaptureAudioWorker(rate=rate + self.audio_capture_thread = CaptureAudioWorker( + rate=rate , channels=channels , dType=dType ) @@ -428,12 +431,13 @@ def transcribeProcess(self): # 创建进程 self.log.write(f"create transcribe process with {num_worker} workers\n") - self.transcribe_thread = TranscribeWorker(model = self.FasterWhisperModel - , parameters = Transcribe_params - , vad_filter = vad_filter - , vad_parameters = VAD_param - , num_workers = num_worker - ) + self.transcribe_thread = TranscribeWorker( + model = self.FasterWhisperModel + , parameters = Transcribe_params + , vad_filter = vad_filter + , vad_parameters = VAD_param + , num_workers = num_worker + ) self.transcribe_thread.signal_process_over.connect(self.transcribeOver) @@ -493,25 +497,33 @@ def changeTableData(self, results) -> None: # 获取文件名列表 text_list = [os.path.split(result[1])[1] for result in results] # 获取文件目录列表 - file_list = [result[1] for result in results] + file_list = [result[1].replace("\\", "/") for result in results] # 获取表格标签列表 tabBarItems = self.page_output.tableTab.tabBar.items # 遍历表格标签 删除已经不存在的转写结果 并改写存在的转写结果 for tabBarItem in tabBarItems: + + # 清理掉已经过时的结果, 标签名不存在于结果文件名列表中的时候就直接清理掉项目 if not(tabBarItem.text() in text_list): index = tabBarItems.index(tabBarItem) self.page_output.tableTab.tabBar.removeTab(index) - else: - for file in file_list: - if tabBarItem.text() == os.path.split(file)[1]: - self.tableModel_list[file]._data = results[file_list.index(file)][0] + continue + + # 从 objectName 获取文件名 + print(tabBarItem.objectName()) + tabBarItem_objectName_fileName = "_".join(tabBarItem.objectName().split("_")[1:]).replace("\\", "/") + + # 标签名存在文件名列表中且文件路径在目录列表中的时候 更新相关表格的数据 + if tabBarItem_objectName_fileName in file_list: + # 转写结果已经存在的情况下更新数据 + self.tableModel_list[tabBarItem_objectName_fileName]._data = results[file_list.index(tabBarItem_objectName_fileName)][0] # 遍历转写结果列表,查找当前不存在的表格 - tabBarItems_text = [tabBarItem.text() for tabBarItem in self.page_output.tableTab.tabBar.items] - for text in text_list: - if not (text in tabBarItems_text): - self.createResultInTable([results[text_list.index(text)]]) + tabBarItems_objectName_fileName = ["_".join(tabBarItem.objectName().split("_")[1:]).replace("\\", "/") for tabBarItem in self.page_output.tableTab.tabBar.items] + for file in file_list: + if not (file_list in tabBarItems_objectName_fileName): + self.createResultInTable([results[file_list.index(file)]]) def showResultInTable(self, results): if len(self.tableModel_list) == 0: @@ -525,6 +537,7 @@ def createResultInTable(self, results): i = len(self.page_output.tableTab.tabBar.items) for result in results: segments, file, _ = result + file = file.replace("\\", "/") table_view_widget = TableView() table_model = TableModel(segments) @@ -535,11 +548,11 @@ def createResultInTable(self, results): table_view_widget.setModel(self.tableModel_list[file]) self.page_output.tableTab.addSubInterface( - table_view_widget - , f"tab_{file}" - , text - , None - ) + table_view_widget + , f"tab_{file}" + , text + , None + ) i += 1 @@ -569,13 +582,13 @@ def transcribeOver(self, segments_path_info:list): ) self.result_faster_whisper = segments_path_info - for segments in self.result_faster_whisper: - segment_, path, info = segments - print(path, info.language) - print(f"len:{len(segment_)}") - for segment in segment_: - print(f"[{segment.start}s --> {segment.end}] | {segment.text}") - print(f"len_words: {len(segment.words)}") + # for segments in self.result_faster_whisper: + # segment_, path, info = segments + # print(path, info.language) + # print(f"len:{len(segment_)}") + # for segment in segment_: + # print(f"[{segment.start}s --> {segment.end}] | {segment.text}") + # print(f"len_words: {len(segment.words)}") print(f"len_segments_path_info_result: {len(segments_path_info)}") self.showResultInTable(self.result_faster_whisper) @@ -789,8 +802,8 @@ def loadModelResult(self, state:bool): elif not state: self.setStateTool(text=self.tr("结束"), status=True) self.raiseErrorInfoBar( - title=self.tr("错误") - , content=self.tr("加载失败,退出并检查 fasterWhispergui.log 文件可能会获取错误信息。") + title=self.tr("错误"), + content=self.tr("加载失败,退出并检查 fasterWhispergui.log 文件可能会获取错误信息。") ) def setModelStatusLabelTextForAll(self, status:bool): @@ -855,7 +868,7 @@ def aligmentOver(self, segments_path_info:list): self.setStateTool(title=self.tr("WhisperX"), text=self.tr("结束"), status=True) # if segments_path_info is None: - # self.raiseErrorInfoBar(self.tr("错误"),content=self.tr("对齐失败,退出软件后检查 fasterwhispergui.log 文件可能会获取错误信息")) + # self.raiseErrorInfoBar(self.tr("错误"), content=self.tr("对齐失败,退出软件后检查 fasterwhispergui.log 文件可能会获取错误信息")) # return self.result_whisperx_aligment = segments_path_info @@ -866,9 +879,11 @@ def aligmentOver(self, segments_path_info:list): , content=self.tr("时间戳对齐结束") ) self.current_result = self.result_whisperx_aligment + else: - self.raiseErrorInfoBar(self.tr("错误"), - content=self.tr("对齐失败,退出软件后检查 fasterwhispergui.log 文件可能会获取更多信息") + self.raiseErrorInfoBar( + self.tr("错误"), + content=self.tr("对齐失败,检查 fasterwhispergui.log 文件可能会获取更多信息") ) try: del self.whisperXWorker.model_alignment @@ -960,6 +975,7 @@ def setPageOutButtonStatus(self): self.page_output.outputSubtitleFileButton.setEnabled(not self.page_output.outputSubtitleFileButton.isEnabled()) self.page_output.WhisperXSpeakerDiarizeButton.setEnabled(not self.page_output.WhisperXSpeakerDiarizeButton.isEnabled()) self.page_output.outputAudioPartWithSpeakerButton.setEnabled(not self.page_output.outputAudioPartWithSpeakerButton.isEnabled()) + self.page_output.unloadWhisperModelPushbutton.setEnabled(not self.page_output.unloadWhisperModelPushbutton.isEnabled()) def speakerDiarizeOver(self, segments_path_info:list): self.setPageOutButtonStatus() @@ -1265,7 +1281,7 @@ def unloadWhisperModel(self): self.raiseErrorInfoBar(self.tr("模型正在使用"), self.tr("语音识别正在运行")) return - if self.result_faster_whisper is not None and len(self.page_transcribes.LineEdit_temperature.text().strip().split(",")) > 1 : + if self.result_faster_whisper is not None and self.page_transcribes.LineEdit_temperature.text().strip() == "0" : print(f"Temperature: {self.page_transcribes.LineEdit_temperature.text().strip()} and transcript has already been run") print("Temperature fallback configuration may take effect, that may take crash when unload model from memory!") @@ -1277,6 +1293,7 @@ def unloadWhisperModel(self): return try: + # self.FasterWhisperModel.model.to(torch.device("cpu")) del self.FasterWhisperModel self.FasterWhisperModel = None @@ -1347,25 +1364,71 @@ def singleAndSlotProcess(self): self.page_model.button_model_lodar.clicked.connect(self.onModelLoadClicked) self.page_process.button_process.clicked.connect(self.onButtonProcessClicked) self.page_process.processResultText.textChanged.connect(lambda: self.page_process.processResultText.moveCursor(QTextCursor.MoveOperation.End, mode=QTextCursor.MoveMode.MoveAnchor)) - - self.page_output.outputSubtitleFileButton.clicked.connect(self.outputSubtitleFile) - self.page_output.WhisperXAligmentTimeStampleButton.clicked.connect(self.whisperXAligmentTimeStample) - self.page_output.WhisperXSpeakerDiarizeButton.clicked.connect(self.whisperXDiarizeSpeakers) - self.page_output.tableTab.tabBar.tabAddRequested.connect(self.openExcitedFiles) + self.page_process.fileNameListView.ignore_files_signal.connect(lambda ignore_files_info: self.raiseInfoBar(self.tr("忽略文件"), ignore_files_info["ignore_reason"]+"\n"+"\n".join(ignore_files_info["ignore_files"]))) self.page_home.itemLabel_demucs.mainButton.clicked.connect(lambda:self.stackedWidget.setCurrentWidget(self.page_demucs)) self.page_home.itemLabel_faster_whisper.mainButton.clicked.connect(lambda:self.stackedWidget.setCurrentWidget(self.page_process)) self.page_home.itemLabel_whisperx.mainButton.clicked.connect(lambda:self.stackedWidget.setCurrentWidget(self.page_output)) self.page_home.itemLabel_faster_whisper.subButton.clicked.connect(lambda:self.stackedWidget.setCurrentWidget(self.page_transcribes)) - self.page_demucs.process_button.clicked.connect(self.demucsProcess) - + self.page_output.outputSubtitleFileButton.clicked.connect(self.outputSubtitleFile) + self.page_output.WhisperXAligmentTimeStampleButton.clicked.connect(self.whisperXAligmentTimeStample) + self.page_output.WhisperXSpeakerDiarizeButton.clicked.connect(self.whisperXDiarizeSpeakers) + self.page_output.tableTab.tabBar.tabAddRequested.connect(self.openExcitedFiles) self.page_output.tableTab.signal_delete_table.connect(self.deleteResultTableEvent) self.page_output.unloadWhisperModelPushbutton.clicked.connect(self.unloadWhisperModel) self.page_output.outputAudioPartWithSpeakerButton.clicked.connect(self.outputAudioPartWithSpeaker) + self.page_demucs.process_button.clicked.connect(self.demucsProcess) self.page_demucs.fileListView.ignore_files_signal.connect(lambda ignore_files_info: self.raiseInfoBar(self.tr("忽略文件"), ignore_files_info["ignore_reason"]+"\n"+"\n".join(ignore_files_info["ignore_files"]))) - self.page_process.fileNameListView.ignore_files_signal.connect(lambda ignore_files_info: self.raiseInfoBar(self.tr("忽略文件"), ignore_files_info["ignore_reason"]+"\n"+"\n".join(ignore_files_info["ignore_files"]))) + + self.page_setting.pushButton_backupConfigFile.clicked.connect(self.backupConfigFile) + self.page_setting.pushButton_loadConfigFile.clicked.connect(self.loadBackupConfigFile) + + def backupConfigFile(self): + config_file_path,_ = QFileDialog.getSaveFileName( + self, + self.tr("选择保存位置"), + r"./", + "json file(*.json)" + ) + if not config_file_path: + return + + self.saveConfig(config_file_path) + + # shutil.copy(config_file_path, config_file_path+".bak") + self.raiseInfoBar(self.tr("备份配置文件成功"), self.tr("配置文件已备份到:\n") + config_file_path) + + def loadBackupConfigFile(self): + + config_file_name, _ = QFileDialog.getOpenFileName( + self, + self.tr("选择配置文件"), + r"./", + "json file(*.json)" + ) + + if not config_file_name: + return + + try: + self.readConfigJson(config_file_path=config_file_name) + self.setConfig() + self.setWidgetsStatusFromConfig() + self.raiseInfoBar(self.tr("加载配置文件成功"), self.tr("配置文件已加载:\n") + config_file_name) + + except Exception as e: + self.raiseErrorBar(self.tr("加载配置文件失败"), self.tr("配置文件加载失败:\n") + str(e)) + print(str(e)) + + # 根据读取的配置设置完控件状态之后,根据控件状态设置相关属性 + # self.page_output.tableTab.onDisplayModeChanged(self.page_output.tableTab.closeDisplayModeComboBox.currentIndex()) + # self.page_output.tableTab.tabBar.setMovable(self.page_output.tableTab.movableCheckBox.isChecked()) + # self.page_output.tableTab.tabBar.setScrollable(self.page_output.tableTab.scrollableCheckBox.isChecked()) + # self.page_output.tableTab.tabBar.setTabShadowEnabled(self.page_output.tableTab.shadowEnabledCheckBox.isChecked()) + # self.page_output.tableTab.tabBar.setTabMaximumWidth(self.page_output.tableTab.tabMaxWidthSpinBox.value()) + def raiseInfoBar(self, title:str, content:str ): InfoBar.info( @@ -1428,10 +1491,11 @@ def closeEvent(self, event) -> None: messageBoxW = MessageBox(self.tr('退出'), self.tr("是否要退出程序?"), self) if messageBoxW.exec(): + + outputWithDateTime("Exit") if self.page_setting.switchButton_saveConfig.isChecked(): - self.saveConfig() - print("save config files") + self.saveConfig(config_file_name=r'./fasterWhisperGUIConfig.json') if self.page_setting.switchButton_autoClearTempFiles.isChecked(): try: @@ -1477,15 +1541,18 @@ def closeEvent(self, event) -> None: event.accept() else: event.ignore() - - def saveConfig(self): - outputWithDateTime("Exit") + def saveConfig(self, config_file_name: str = ""): + + if config_file_name == "": + return + + outputWithDateTime("SaveConfigFile") model_param = self.page_model.getParam() setting_param = self.page_setting.getParam() demucs_param = self.page_demucs.getParam() Transcription_param = self.page_transcribes.getParam() - output_whisperX_param = self.page_output.getparam() + output_whisperX_param = self.page_output.getParam() vad_param = self.page_VAD.getParam() config_json = { @@ -1498,7 +1565,7 @@ def saveConfig(self): "output_whisperX":output_whisperX_param } - with open(r'./fasterWhisperGUIConfig.json','w',encoding='utf8')as fp: + with open(os.path.abspath(config_file_name),'w',encoding='utf8')as fp: json.dump( config_json, fp, diff --git a/faster_whisper_GUI/modelPageNavigationInterface.py b/faster_whisper_GUI/modelPageNavigationInterface.py index 37659d0..bcd4d61 100644 --- a/faster_whisper_GUI/modelPageNavigationInterface.py +++ b/faster_whisper_GUI/modelPageNavigationInterface.py @@ -1,4 +1,5 @@ from PySide6.QtCore import (QCoreApplication, Qt) +from PySide6.QtGui import QFont from PySide6.QtWidgets import ( QCompleter, @@ -6,7 +7,8 @@ QHBoxLayout, QLabel, QStyle, - QVBoxLayout + QVBoxLayout, + QSplitter ) from qfluentwidgets import ( @@ -17,6 +19,9 @@ EditableComboBox, LineEdit , SwitchButton, + FluentIcon, + PrimaryPushButton, + ) from .navigationInterface import NavigationBaseInterface @@ -48,7 +53,7 @@ def __init__(self, parent=None): self.setObjectName('modelNavigationInterface') self.setupUI() - StyleSheet.MODELLOAD.apply(self.button_model_lodar) + # StyleSheet.MODELLOAD.apply(self.button_model_lodar) self.SignalAndSlotConnect() @@ -70,6 +75,28 @@ def setModelLocationLayout(self): widget.setEnabled(self.model_online_RadioButton.isChecked()) def setupUI(self): + self.layout_button_model_lodar = QVBoxLayout() + # self.button_model_lodar = PushButton() + self.button_model_lodar = PrimaryPushButton(self) + + self.button_model_lodar.setText(self.__tr("加载模型")) + self.button_model_lodar.setFixedHeight(65) + self.button_model_lodar.setFixedWidth(195) + font = QFont("Segoe UI", 15) + font.setBold(True) + + self.button_model_lodar.setFont(font) + + self.button_model_lodar.setIcon(FluentIcon.PLAY) + self.button_model_lodar.setObjectName("buttonModelLodar") + + + self.layout_button_model_lodar.addWidget(self.button_model_lodar) + self.layout_button_model_lodar.setAlignment(Qt.AlignmentFlag.AlignRight) + self.addLayout(self.layout_button_model_lodar) + + + # ========================================================================================================== model_local_RadioButton = RadioButton() model_local_RadioButton.setChecked(True) @@ -78,6 +105,8 @@ def setupUI(self): self.model_local_RadioButton = model_local_RadioButton self.addWidget(self.model_local_RadioButton) + # ========================================================================================================== + self.hBoxLayout_local_model = QHBoxLayout() self.addLayout(self.hBoxLayout_local_model) @@ -106,6 +135,8 @@ def setupUI(self): self.addLayout(self.hBoxLayout_online_model) # 添加一些控件到布局中 + + # ========================================================================================================== self.label_online_model_name = QLabel() self.label_online_model_name.setText(self.__tr("模型名称")) self.label_online_model_name.setObjectName("LabelOnlineModelName") @@ -245,20 +276,6 @@ def setupUI(self): self.button_convert_model.setToolTip(self.__tr("转换 OpenAi 模型到本地格式,\n必须选择在线模型")) hBoxLayout_model_convert.addWidget(self.button_convert_model) self.button_convert_model.setEnabled(False) - - # ========================================================================================================== - self.layout_button_model_lodar = QVBoxLayout() - self.button_model_lodar = PushButton() - self.button_model_lodar.setText(self.__tr("加载模型")) - self.button_model_lodar.setFixedHeight(65) - self.button_model_lodar.setFixedWidth(195) - # self.button_model_lodar.setIcon(FluentIcon.PLAY) - self.button_model_lodar.setObjectName("buttonModelLodar") - - - self.layout_button_model_lodar.addWidget(self.button_model_lodar) - self.layout_button_model_lodar.setAlignment(Qt.AlignmentFlag.AlignRight) - self.addLayout(self.layout_button_model_lodar) # self.setViewportMargins(0, self.toolBar.height(), 0, 216) # self.LineEdit_download_root.setText(self.parent().download_cache_path) diff --git a/faster_whisper_GUI/navigationInterface.py b/faster_whisper_GUI/navigationInterface.py index 33470d6..90c310c 100644 --- a/faster_whisper_GUI/navigationInterface.py +++ b/faster_whisper_GUI/navigationInterface.py @@ -3,7 +3,7 @@ from PySide6.QtCore import ( Qt , QUrl - , QTranslator + # , QTranslator ) from PySide6.QtGui import ( @@ -17,7 +17,7 @@ , QLabel , QVBoxLayout , QHBoxLayout - , QApplication + # , QApplication ) from qfluentwidgets import (ScrollArea @@ -30,6 +30,7 @@ , TitleLabel , CaptionLabel , MessageBox + , PrimaryToolButton ) from .style_sheet import StyleSheet @@ -75,6 +76,7 @@ def __init__(self, title, subtitle, parent=None): # self.tr('Documentation'), self, FluentIcon.DOCUMENT) self.sourceButton = PushButton(self.tr('软件更新'), self, FluentIcon.GITHUB) self.themeButton = ToolButton(FluentIcon.CONSTRACT, self) + # self.themeButton = PrimaryToolButton(FluentIcon.CONSTRACT, self) # self.languageButton = ToolButton(FluentIcon.LANGUAGE, self) # 分割线 @@ -167,7 +169,7 @@ def __init__(self, title: str, subtitle: str, parent=None): self.vBoxLayout = QVBoxLayout(self.view) self.vBoxLayout.setAlignment(Qt.AlignmentFlag.AlignTop) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.setViewportMargins(0, self.toolBar.height(), 0, 0) self.setWidget(self.view) self.setWidgetResizable(True) diff --git a/faster_whisper_GUI/outputPageNavigationInterface.py b/faster_whisper_GUI/outputPageNavigationInterface.py index 348df6b..3e90cab 100644 --- a/faster_whisper_GUI/outputPageNavigationInterface.py +++ b/faster_whisper_GUI/outputPageNavigationInterface.py @@ -41,7 +41,7 @@ def setParam(self, param:dict): except Exception as e: print(f"set output-whisperX param error: {str(e)}") - def getparam(self) -> dict: + def getParam(self) -> dict: param = {} param["tabMovable"] = self.tableTab.movableCheckBox.isChecked() param["tabScrollable"] = self.tableTab.scrollableCheckBox.isChecked() diff --git a/faster_whisper_GUI/settingPageNavigation.py b/faster_whisper_GUI/settingPageNavigation.py index 2e95e3e..32f9918 100644 --- a/faster_whisper_GUI/settingPageNavigation.py +++ b/faster_whisper_GUI/settingPageNavigation.py @@ -1,9 +1,14 @@ import os -from PySide6.QtCore import (QCoreApplication, Qt) +import random +import time +from PySide6.QtCore import (QAbstractListModel, QCoreApplication, Qt) +from PySide6.QtGui import QColor, QColorSpace from PySide6.QtWidgets import ( - QGridLayout, + QComboBox, + QGridLayout, + QHBoxLayout, QVBoxLayout, QWidget ) @@ -14,16 +19,29 @@ LineEdit, MessageBox, PushButton, - TitleLabel + TitleLabel, + ScrollArea, + themeColor, + setThemeColor, + ColorPickerButton, + ToolButton, + FluentIcon, + PrimaryToolButton ) from .paramItemWidget import ParamWidget from .style_sheet import StyleSheet -from .config import default_Huggingface_user_token +from .config import default_Huggingface_user_token, THEME_COLORS from .util import outputWithDateTime -class SettingPageNavigationInterface(QWidget): +class ThemeColorModel(QAbstractListModel): + def data(self, index, role): + if role == Qt.DisplayRole: + color = THEME_COLORS[index.row()] + return f'
' + +class SettingPageNavigationInterface(ScrollArea): def __tr(self, text): return QCoreApplication.translate(self.__class__.__name__, text) """ @@ -32,21 +50,33 @@ def __tr(self, text): def __init__(self, parent=None): super().__init__(parent) + + self.themeColor_str = themeColor().name() - self.layout = QGridLayout(self) - self.setLayout(self.layout) + # self.layout = QGridLayout(self) + # self.setLayout(self.layout) self.mainWidget = QWidget(self) self.mainWidget.setObjectName("mainObject") - self.layout.addWidget(self.mainWidget, 0,0) - + # self.layout.addWidget(self.mainWidget, 0,0) self.mainLayout = QVBoxLayout(self.mainWidget) self.mainLayout.setAlignment(Qt.AlignmentFlag.AlignTop) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.setViewportMargins(0, 0, 0, 0) + self.setWidget(self.mainWidget) + self.setWidgetResizable(True) + + self.mainLayout.setSpacing(10) + self.mainLayout.setContentsMargins(36, 10, 36, 10) + self.setupUI() self.signalAndSlotProcess() StyleSheet.SETTINGPAGEINTERFACE.apply(self) + + def addWidget(self, widget): self.mainLayout.addWidget(widget) def addLayout(self, layout): @@ -71,6 +101,16 @@ def setupUI(self): self.paramItemWidget_autoLoadModel = ParamWidget(self.__tr("自动加载模型"), self.__tr("程序启动时自动加载模型"),self.switchButton_autoLoadModel, self) self.addWidget(self.paramItemWidget_autoLoadModel) + # -------------------------------------------------------------------------------------------------------------------------------------------------------------- + self.pushButton_backupConfigFile = PushButton() + self.pushButton_backupConfigFile.setText(self.__tr("备份当前配置")) + self.pushButton_loadConfigFile = PushButton() + self.pushButton_loadConfigFile.setText(self.__tr("加载配置文件")) + + self.paramItemWidget_ConfigFile = ParamWidget(self.__tr("配置文件"), self.__tr("备份、加载当前配置文件,重装、升级软件时可用来备份软件配置"), self.pushButton_backupConfigFile) + self.paramItemWidget_ConfigFile.widgetVLayout.addWidget(self.pushButton_loadConfigFile) + self.addWidget(self.paramItemWidget_ConfigFile) + # -------------------------------------------------------------------------------------------------------------------------------------------------------------- self.combox_language = ComboBox() self.combox_language.addItems(["中文","English",self.__tr("自动")]) @@ -80,6 +120,32 @@ def setupUI(self): # -------------------------------------------------------------------------------------------------------------------------------------------------------------- + self.colorPickerButton = ColorPickerButton(self.themeColor_str, "ThemeColor", parent=self) + # self.colorPickerButton.clicked.disconnect() + self.colorPickerButton.colorChanged.connect(self.setThemeColorAndText) + + self.randomPickThemeColorToolButton = PrimaryToolButton() + # self.randomPickThemeColorToolButton = ToolButton() + self.randomPickThemeColorToolButton.setIcon(FluentIcon.BASKETBALL) + self.randomPickThemeColorToolButton.setToolTip(self.__tr("切换预置主题色")) + self.randomPickThemeColorToolButton.clicked.connect(self.setColorAndThemeColorRandom) + + self.themeColorLineEdit = LineEdit() + self.themeColorLineEdit.setText(self.themeColor_str) + self.themeColorLineEdit.textChanged.connect(self.setThemeColorWithLineEditText) + + self.paramItemWidget_themeColor = ParamWidget(self.__tr("主题色"), self.__tr("主题配色"), self.themeColorLineEdit, self) + + hb = QHBoxLayout() + hb.addWidget(self.randomPickThemeColorToolButton) + hb.addWidget(self.colorPickerButton) + hb.setAlignment(Qt.AlignmentFlag.AlignLeft) + # self.paramItemWidget_themeColor.widgetVLayout.addWidget(self.themeColorLineEdit) + self.paramItemWidget_themeColor.widgetVLayout.addLayout(hb) + + self.addWidget(self.paramItemWidget_themeColor) + + # -------------------------------------------------------------------------------------------------------------------------------------------------------------- self.LineEdit_use_auth_token = LineEdit() self.LineEdit_use_auth_token.setFixedWidth(330) self.use_auth_token_param_widget = ParamWidget(self.__tr("HuggingFace用户令牌"), @@ -138,7 +204,6 @@ def signalAndSlotProcess(self): self.pushButton_openTempDir.clicked.connect(lambda: os.startfile(os.path.abspath(r"./temp/").replace("\\","/"))) self.pushButton_openLogFile.clicked.connect(lambda: os.startfile(os.path.abspath(r"./fasterwhispergui.log").replace("\\","/"))) self.pushButton_openFWLogFile.clicked.connect(lambda: os.startfile(os.path.abspath(r"./faster_whisper.log").replace("\\","/"))) - self.pushButton_clearTempFiles.clicked.connect(self.deletTempFiles) @@ -165,6 +230,9 @@ def setParam(self,param:dict) -> None: self.LineEdit_use_auth_token.setText(param["huggingface_user_token"]) self.combox_autoGoToOutputPage.setCurrentIndex(param["autoGoToOutputPage"]) self.switchButton_autoClearTempFiles.setChecked(param["autoClearTempFiles"]) + self.colorPickerButton.setColor(param["themeColor"]) + self.setThemeColorAndText() + # setThemeColor(param["themeColor"]) except: pass @@ -176,4 +244,34 @@ def getParam(self): param["huggingface_user_token"] = self.LineEdit_use_auth_token.text().strip() or default_Huggingface_user_token param["autoGoToOutputPage"] = self.combox_autoGoToOutputPage.currentIndex() param["autoClearTempFiles"] = self.switchButton_autoClearTempFiles.isChecked() - return param \ No newline at end of file + param["themeColor"] = self.themeColor_str + return param + + def setColorAndThemeColorRandom(self): + + if self.themeColor_str in THEME_COLORS: + index = THEME_COLORS.index(self.themeColor_str) + if index != len(THEME_COLORS)-1 : + index += 1 + else: + index = 0 + else: + index = 0 + + + self.themeColor_str = THEME_COLORS[index] + self.colorPickerButton.setColor(self.themeColor_str) + setThemeColor(self.themeColor_str) + self.themeColorLineEdit.setText(self.themeColor_str) + + def setThemeColorAndText(self): + self.themeColor_str = self.colorPickerButton.color.name() + self.themeColorLineEdit.setText(self.themeColor_str) + setThemeColor(self.themeColor_str) + + def setThemeColorWithLineEditText(self,text): + if len(text) == 7: + self.colorPickerButton.setColor(text) + setThemeColor(text) + self.themeColor_str = text + \ No newline at end of file diff --git a/faster_whisper_GUI/tableModel_segments_path_info.py b/faster_whisper_GUI/tableModel_segments_path_info.py index 2bca87d..a6f7356 100644 --- a/faster_whisper_GUI/tableModel_segments_path_info.py +++ b/faster_whisper_GUI/tableModel_segments_path_info.py @@ -5,8 +5,7 @@ from faster_whisper import Word from .seg_ment import segment_Transcribe -from .transcribe import secondsToHMS -from .util import HMSToSeconds +from .util import HMSToSeconds, secondsToHMS # 自定义数据模型,用于在表格中显示数据 class TableModel(QAbstractTableModel): diff --git a/faster_whisper_GUI/tableViewInterface.py b/faster_whisper_GUI/tableViewInterface.py index d6ca512..f8b2fca 100644 --- a/faster_whisper_GUI/tableViewInterface.py +++ b/faster_whisper_GUI/tableViewInterface.py @@ -180,20 +180,24 @@ def addSubInterface(self if widget is None: widget = TableView() + widget.setObjectName(objectName) # 设置缩放策略 widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) - # 设置列宽适应内容 - # widget.resizeColumnsToContents() - widget.resizeColumnToContents(2) - # 设置列宽 - widget.setColumnWidth(0,110) - widget.setColumnWidth(1,110) - # widget.setColumnWidth(3,500) + widget.setColumnWidth(0,115) + widget.setColumnWidth(1,115) + widget.setColumnWidth(2,400) # widget.setColumnWidth(4,50) + + # 设置列宽适应内容 + # widget.resizeColumnsToContents() + # widget.resizeColumnToContents(0) + # widget.resizeColumnToContents(1) + widget.resizeColumnToContents(3) + # widget.resizeColumnToContents(2) # 设置填充可用空间 widget.horizontalHeader().setStretchLastSection(True) diff --git a/faster_whisper_GUI/transcribe.py b/faster_whisper_GUI/transcribe.py index 54ebe58..d1e53cb 100644 --- a/faster_whisper_GUI/transcribe.py +++ b/faster_whisper_GUI/transcribe.py @@ -30,6 +30,7 @@ ) from .seg_ment import segment_Transcribe +from .util import secondsToHMS, secondsToMS from .config import ENCODING_DICT, Task_list @@ -665,72 +666,5 @@ def getSaveFileName(audioFile: str, format:str = "srt", rootDir:str = ""): saveFileName = os.path.join(path, fileName).replace("\\", "/") return saveFileName -# --------------------------------------------------------------------------------------------------------------------------- -def secondsToHMS(t) -> str: - try: - t_f:float = float(t) - except: - print("time transform error") - return - - H = int(t_f // 3600) - M = int((t_f - H * 3600) // 60) - S = (t_f - H *3600 - M *60) - - H = str(H) - M = str(M) - S = str(round(S,4)) - S = S.replace(".", ",") - S = S.split(",") - - if len(S) < 2 : - S.append("000") - - if len(S[0]) < 2: - S[0] = "0" + S[0] - - while(len(S[1]) < 3): - S[1] = S[1] + "0" - - S = ",".join(S) - - if len(H) < 2: - H = "0" + H - if len(M) < 2: - M = "0" + M - - return H + ":" + M + ":" + S - -def secondsToMS(t) -> str: - try: - t_f:float = float(t) - except: - print("time transform error") - return - - M = t_f // 60 - S = t_f - M * 60 - - M = str(int(M)) - if len(M)<2: - M = "0" + M - - S = str(round(S,4)) - S = S.split(".") - - if len(S) < 2: - S.append("00") - - if len(S[0]) < 2: - S[0] = "0" + S[0] - if len(S[1] ) < 2: - S[1] = "0" + S[1] - if len(S[1]) >= 3: - S[1] = S[1][:2] - - S:str = ".".join(S) - - return M + ":" + S - diff --git a/faster_whisper_GUI/util.py b/faster_whisper_GUI/util.py index ac1d2bc..b4c2f16 100644 --- a/faster_whisper_GUI/util.py +++ b/faster_whisper_GUI/util.py @@ -40,6 +40,7 @@ def secondsToHMS(t) -> str: return H + ":" + M + ":" + S +# --------------------------------------------------------------------------------------------------------------------------- def HMSToSeconds(t:str) -> float: hh,mm,ss = t.split(":") @@ -47,4 +48,36 @@ def HMSToSeconds(t:str) -> float: return float(hh) * 3600 + float(mm) * 60 + float(ss) + +def secondsToMS(t) -> str: + try: + t_f:float = float(t) + except: + print("time transform error") + return + + M = t_f // 60 + S = t_f - M * 60 + + M = str(int(M)) + if len(M)<2: + M = "0" + M + + S = str(round(S,4)) + S = S.split(".") + + if len(S) < 2: + S.append("00") + if len(S[0]) < 2: + S[0] = "0" + S[0] + if len(S[1] ) < 2: + S[1] = "0" + S[1] + if len(S[1]) >= 3: + S[1] = S[1][:2] + + S:str = ".".join(S) + + return M + ":" + S + + diff --git a/faster_whisper_GUI/version.py b/faster_whisper_GUI/version.py index 48adf95..316f6a5 100644 --- a/faster_whisper_GUI/version.py +++ b/faster_whisper_GUI/version.py @@ -1,4 +1,4 @@ -__version__ = "0.5.5" -__FasterWhisper_version__ = "0.9.0" +__version__ = "0.5.6" +__FasterWhisper_version__ = "0.10.0" __WhisperX_version__ = "3.1.1" __Demucs_version__ = "v4.0" diff --git a/requirements.txt b/requirements.txt index 2b1188c..6fc749c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ pyside6-fluent-widgets==1.3.2 -faster-whisper==0.9.0 +faster-whisper==0.10.0 nuitka<=1.8.6 pyAV ffmpeg-python diff --git a/resource/_rc/qss/TranscribeParamItemWidget_dark.qss b/resource/_rc/qss/TranscribeParamItemWidget_dark.qss index 32bc607..9377f72 100644 --- a/resource/_rc/qss/TranscribeParamItemWidget_dark.qss +++ b/resource/_rc/qss/TranscribeParamItemWidget_dark.qss @@ -6,7 +6,7 @@ StrongBodyLabel{ CaptionLabel{ font-size: 12px; background-color: transparent; - color: rgb(225, 225, 225); + color: rgb(200, 200, 200); } #mainObject{ diff --git a/resource/_rc/qss/modelLoad_light.qss b/resource/_rc/qss/modelLoad_light.qss index 3191dcb..729c1bc 100644 --- a/resource/_rc/qss/modelLoad_light.qss +++ b/resource/_rc/qss/modelLoad_light.qss @@ -9,7 +9,7 @@ } #buttonModelLodar:hover{ - background-color:rgb(0, 150, 200); + background-color:rgb(33, 43, 46); } #buttonModelLodar:pressed{ diff --git a/resource/_rc/qss/settingPageInterface_dark.qss b/resource/_rc/qss/settingPageInterface_dark.qss index 0b6fca9..6df09ed 100644 --- a/resource/_rc/qss/settingPageInterface_dark.qss +++ b/resource/_rc/qss/settingPageInterface_dark.qss @@ -1,3 +1,7 @@ #mainObject { background-color: rgb(70, 70, 70); } + +QScrollArea { + border: none; +} diff --git a/resource/_rc/qss/settingPageInterface_light.qss b/resource/_rc/qss/settingPageInterface_light.qss index 7965231..b137b3b 100644 --- a/resource/_rc/qss/settingPageInterface_light.qss +++ b/resource/_rc/qss/settingPageInterface_light.qss @@ -2,3 +2,6 @@ background-color: rgb(249, 249, 249); } +QScrollArea { + border: none; +} diff --git a/resource/_rc/rc_qss.py b/resource/_rc/rc_qss.py index 3a1edab..0abbff9 100644 --- a/resource/_rc/rc_qss.py +++ b/resource/_rc/rc_qss.py @@ -304,7 +304,7 @@ color: rgba(125,\ 125, 125, 85);\x0d\ \x0a}\x0d\x0a\x0d\x0a\x0d\x0a\ -\x00\x00\x01\x89\ +\x00\x00\x01\x88\ #\ buttonModelLodar\ {\x0d\x0a font : 'S\ @@ -322,21 +322,24 @@ ttonModelLodar:h\ over{\x0d\x0a backg\ round-color:rgb(\ -0, 150, 200);\x0d\x0a}\ -\x0d\x0a\x0d\x0a#buttonModel\ -Lodar:pressed{\x0d\x0a\ - background-c\ -olor:rgb(30, 85,\ - 102);\x0d\x0a padd\ -ing-left:2px;\x0d\x0a \ - padding-top:2\ -px;\x0d\x0a}\x0d\x0a\ -\x00\x00\x00:\ +33, 43, 46);\x0d\x0a}\x0d\ +\x0a\x0d\x0a#buttonModelL\ +odar:pressed{\x0d\x0a \ + background-co\ +lor:rgb(30, 85, \ +102);\x0d\x0a paddi\ +ng-left:2px;\x0d\x0a \ + padding-top:2p\ +x;\x0d\x0a}\x0d\x0a\ +\x00\x00\x00a\ #\ mainObject {\x0d\x0a \ background-col\ or: rgb(70, 70, \ -70);\x0d\x0a}\x0d\x0a\ +70);\x0d\x0a}\x0d\x0a\x0d\x0aQScro\ +llArea {\x0d\x0a bo\ +rder: none;\x0d\x0a}\x0d\x0a\ +\ \x00\x00\x00\xeb\ #\ tabView {\x0d\x0a b\ @@ -398,8 +401,8 @@ px;\x0d\x0a backgro\ und-color: trans\ parent;\x0d\x0a col\ -or: rgb(225, 225\ -, 225);\x0d\x0a}\x0d\x0a\x0d\x0a#m\ +or: rgb(200, 200\ +, 200);\x0d\x0a}\x0d\x0a\x0d\x0a#m\ ainObject{\x0d\x0a \ background-color\ : rgb(43, 43, 43\ @@ -577,12 +580,15 @@ d;\x0d\x0a qpropert\ y-alignment: Ali\ gnLeft;\x0d\x0a}\x0d\x0a\x0d\x0a\ -\x00\x00\x00?\ +\x00\x00\x00d\ #\ mainObject {\x0d\x0a \ background-col\ or: rgb(249, 249\ -, 249);\x0d\x0a}\x0d\x0a\x0d\x0a\ +, 249);\x0d\x0a}\x0d\x0a\x0d\x0aQS\ +crollArea {\x0d\x0a \ + border: none;\x0d\x0a\ +}\x0d\x0a\ " qt_resource_name = b"\ @@ -739,53 +745,53 @@ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x01\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x06G\ \x00\x00\x01\x8b\xd8\x1e\xf8\xdf\ -\x00\x00\x04\x88\x00\x00\x00\x00\x00\x01\x00\x00\x17p\ +\x00\x00\x04\x88\x00\x00\x00\x00\x00\x01\x00\x00\x17\x96\ \x00\x00\x01\x8b\x93\xc2\xe2\x89\ \x00\x00\x014\x00\x00\x00\x00\x00\x01\x00\x00\x04+\ \x00\x00\x01\x8b\xdb\xdc&1\ -\x00\x00\x04\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x19\xce\ +\x00\x00\x04\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x19\xf4\ \x00\x00\x01\x8b\x0a\x1f]\xcf\ \x00\x00\x02^\x00\x00\x00\x00\x00\x01\x00\x00\x106\ \x00\x00\x01\x8b\x0a\x1f]\xcd\ -\x00\x00\x02\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x12\xb0\ -\x00\x00\x01\x8b\xd3\xb3\x9d?\ -\x00\x00\x030\x00\x00\x00\x00\x00\x01\x00\x00\x13\xdd\ +\x00\x00\x02\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x12\xaf\ +\x00\x00\x01\x8c\xe2aMV\ +\x00\x00\x030\x00\x00\x00\x00\x00\x01\x00\x00\x14\x03\ \x00\x00\x01\x8b\xbf\x0c\xed\xd2\ \x00\x00\x01\x0a\x00\x00\x00\x00\x00\x01\x00\x00\x02\x9f\ \x00\x00\x01\x8b\xd36\xfc\xd3\ -\x00\x00\x02\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x12\xee\ +\x00\x00\x02\xfe\x00\x00\x00\x00\x00\x01\x00\x00\x13\x14\ \x00\x00\x01\x8b\x0a\x1f]\xd6\ \x00\x00\x01p\x00\x00\x00\x00\x00\x01\x00\x00\x05\xa4\ \x00\x00\x01\x8b\xd8\x12\x9by\ \x00\x00\x00X\x00\x00\x00\x00\x00\x01\x00\x00\x00p\ \x00\x00\x01\x8bF\xbb]\xa0\ -\x00\x00\x03\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x15o\ -\x00\x00\x01\x8b\xdb\xd1\x1f\xf0\ -\x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x14\xd5\ +\x00\x00\x03\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x15\x95\ +\x00\x00\x01\x8c\xe8\x932\xd2\ +\x00\x00\x03\xb2\x00\x00\x00\x00\x00\x01\x00\x00\x14\xfb\ \x00\x00\x01\x8b\xd8\x1e\xed\x08\ \x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x00\x11#\ -\x00\x00\x01\x8b\xd36\xe1;\ +\x00\x00\x01\x8c\xeek<@\ \x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x00\xd6\ \x00\x00\x01\x8bF\xcc\x9d:\ \x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x01#\ \x00\x00\x01\x8b\xdb\xd9w\x03\ -\x00\x00\x05L\x00\x00\x00\x00\x00\x01\x00\x00\x1b\x9f\ +\x00\x00\x05L\x00\x00\x00\x00\x00\x01\x00\x00\x1b\xc5\ \x00\x00\x01\x8b\xbf\x0c\xed\xcb\ -\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00\x16\x84\ +\x00\x00\x04<\x00\x00\x00\x00\x00\x01\x00\x00\x16\xaa\ \x00\x00\x01\x8b\xdb\xd0\xc5\x1c\ \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x01\x8bF\xbb\x87;\ -\x00\x00\x05\xda\x00\x00\x00\x00\x00\x01\x00\x00 B\ -\x00\x00\x01\x8b\xdb\xd1\x9cC\ -\x00\x00\x04\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x18\xcf\ +\x00\x00\x05\xda\x00\x00\x00\x00\x00\x01\x00\x00 h\ +\x00\x00\x01\x8c\xe2_\xd2\x8c\ +\x00\x00\x04\xb8\x00\x00\x00\x00\x00\x01\x00\x00\x18\xf5\ \x00\x00\x01\x8b\x0a\x1f]\xd5\ \x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x00\x0b\x86\ \x00\x00\x01\x8bG\x87\xbf\x84\ -\x00\x00\x05\x92\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x1f\ +\x00\x00\x05\x92\x00\x00\x00\x00\x00\x01\x00\x00\x1eE\ \x00\x00\x01\x8b\xbf\x0c\xed\xcc\ -\x00\x00\x03x\x00\x00\x00\x00\x00\x01\x00\x00\x14~\ +\x00\x00\x03x\x00\x00\x00\x00\x00\x01\x00\x00\x14\xa4\ \x00\x00\x01\x8bF\xcb\x9e\x19\ -\x00\x00\x05\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xb9\ +\x00\x00\x05\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xdf\ \x00\x00\x01\x8b\xbf\x0c\xed\xc9\ \x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\x06\xd7\ \x00\x00\x01\x8bG\x89\x1eK\