diff --git a/videotrans/__init__.py b/videotrans/__init__.py index f9433cf7..c5550093 100644 --- a/videotrans/__init__.py +++ b/videotrans/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -VERSION="v1.66" -VERSION_NUM=11066 \ No newline at end of file +VERSION="v1.67" +VERSION_NUM=11067 \ No newline at end of file diff --git a/videotrans/box/win.py b/videotrans/box/win.py index 1c1ecab2..b6380509 100644 --- a/videotrans/box/win.py +++ b/videotrans/box/win.py @@ -15,9 +15,9 @@ from videotrans.box.logs_worker import LogsWorker from videotrans.box.worker import Worker, WorkerWhisper, WorkerTTS, FanyiWorker from videotrans.configure import config -from videotrans import translator +from videotrans import translator from videotrans.translator import GOOGLE_NAME -from videotrans.util import tools +from videotrans.util import tools import shutil from videotrans.ui.toolboxen import Ui_MainWindow from videotrans.util.tools import get_azure_rolelist, get_edge_rolelist @@ -27,14 +27,15 @@ class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setupUi(self) - self.initSize=None - self.shibie_out_path=None - self.hecheng_files=[] - self.fanyi_files=[] - self.fanyi_errors="" + self.initSize = None + self.shibie_out_path = None + self.hecheng_files = [] + self.fanyi_files = [] + self.fanyi_errors = "" self.initUI() self.setWindowIcon(QIcon(f"{config.rootdir}/videotrans/styles/icon.ico")) - self.setWindowTitle(f"pyVideoTrans{config.uilanglist['Video Toolbox']} {VERSION} pyvideotrans.com {' Q群 905857759 ' if config.defaulelang=='zh' else ''}") + self.setWindowTitle( + f"pyVideoTrans{config.uilanglist['Video Toolbox']} {VERSION} pyvideotrans.com {' Q群 905857759 ' if config.defaulelang == 'zh' else ''}") def closeEvent(self, event): if config.exit_soft: @@ -88,11 +89,10 @@ def initUI(self): self.shibie_dropbtn.setMinimumSize(0, 150) self.shibie_widget.insertWidget(0, self.shibie_dropbtn) - self.shibie_language.addItems(config.langnamelist) self.shibie_model.addItems(config.model_list) self.shibie_startbtn.clicked.connect(self.shibie_start_fun) - self.shibie_opendir.clicked.connect(lambda :self.opendir_fn(self.shibie_out_path)) + self.shibie_opendir.clicked.connect(lambda: self.opendir_fn(self.shibie_out_path)) self.is_cuda.toggled.connect(self.check_cuda) self.shibie_model_type.currentIndexChanged.connect(self.model_type_change) @@ -105,13 +105,12 @@ def initUI(self): self.hecheng_plaintext.setSizePolicy(sizePolicy) self.hecheng_plaintext.setMinimumSize(0, 150) self.hecheng_plaintext.setPlaceholderText(config.transobj['tuodonghuoshuru']) - + self.hecheng_importbtn = QtWidgets.QPushButton(self.tab_2) self.hecheng_importbtn.setObjectName("hecheng_importbtn") - self.hecheng_importbtn.setFixedHeight(35) + self.hecheng_importbtn.setFixedHeight(150) self.hecheng_importbtn.setCursor(Qt.PointingHandCursor) - self.hecheng_importbtn.setText(config.box_lang['Import text to be translated from a file..']) self.hecheng_importbtn.clicked.connect(self.hecheng_import_fun) @@ -129,35 +128,35 @@ def initUI(self): self.tts_issrt.stateChanged.connect(self.tts_issrt_change) # tab-5 格式转换 - self.geshi_input = TextGetdir() - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.geshi_result.sizePolicy().hasHeightForWidth()) - self.geshi_input.setSizePolicy(sizePolicy) - self.geshi_input.setMinimumSize(300, 0) - - self.geshi_input.setPlaceholderText(config.transobj['tuodongdaoci']) - - self.geshi_importbtn = QtWidgets.QPushButton(self.tab_6) - self.geshi_importbtn.setObjectName("geshi_importbtn") - self.geshi_importbtn.setFixedWidth(100) - self.geshi_importbtn.setText(config.box_lang['import audio or video']) - self.geshi_importbtn.clicked.connect(lambda :self.geshi_import_fun(self.geshi_input)) - self.horizontalLayout_14.insertWidget(0,self.geshi_importbtn) - - self.geshi_layout.insertWidget(0, self.geshi_input) - self.geshi_mp4.clicked.connect(lambda: self.geshi_start_fun("mp4")) - self.geshi_avi.clicked.connect(lambda: self.geshi_start_fun("avi")) - self.geshi_mov.clicked.connect(lambda: self.geshi_start_fun("mov")) - self.geshi_mp3.clicked.connect(lambda: self.geshi_start_fun("mp3")) - self.geshi_wav.clicked.connect(lambda: self.geshi_start_fun("wav")) - self.geshi_aac.clicked.connect(lambda: self.geshi_start_fun("aac")) - self.geshi_m4a.clicked.connect(lambda: self.geshi_start_fun("m4a")) - self.geshi_flac.clicked.connect(lambda: self.geshi_start_fun("flac")) - self.geshi_output.clicked.connect(lambda: self.opendir_fn(f'{config.homedir}/conver')) - if not os.path.exists(f'{config.homedir}/conver'): - os.makedirs(f'{config.homedir}/conver', exist_ok=True) + # self.geshi_input = TextGetdir() + # sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + # sizePolicy.setHorizontalStretch(0) + # sizePolicy.setVerticalStretch(0) + # sizePolicy.setHeightForWidth(self.geshi_result.sizePolicy().hasHeightForWidth()) + # self.geshi_input.setSizePolicy(sizePolicy) + # self.geshi_input.setMinimumSize(300, 0) + # + # self.geshi_input.setPlaceholderText(config.transobj['tuodongdaoci']) + # + # self.geshi_importbtn = QtWidgets.QPushButton(self.tab_6) + # self.geshi_importbtn.setObjectName("geshi_importbtn") + # self.geshi_importbtn.setFixedWidth(100) + # self.geshi_importbtn.setText(config.box_lang['import audio or video']) + # self.geshi_importbtn.clicked.connect(lambda: self.geshi_import_fun(self.geshi_input)) + # self.horizontalLayout_14.insertWidget(0, self.geshi_importbtn) + # + # self.geshi_layout.insertWidget(0, self.geshi_input) + # self.geshi_mp4.clicked.connect(lambda: self.geshi_start_fun("mp4")) + # self.geshi_avi.clicked.connect(lambda: self.geshi_start_fun("avi")) + # self.geshi_mov.clicked.connect(lambda: self.geshi_start_fun("mov")) + # self.geshi_mp3.clicked.connect(lambda: self.geshi_start_fun("mp3")) + # self.geshi_wav.clicked.connect(lambda: self.geshi_start_fun("wav")) + # self.geshi_aac.clicked.connect(lambda: self.geshi_start_fun("aac")) + # self.geshi_m4a.clicked.connect(lambda: self.geshi_start_fun("m4a")) + # self.geshi_flac.clicked.connect(lambda: self.geshi_start_fun("flac")) + # self.geshi_output.clicked.connect(lambda: self.opendir_fn(f'{config.homedir}/conver')) + # if not os.path.exists(f'{config.homedir}/conver'): + # os.makedirs(f'{config.homedir}/conver', exist_ok=True) # 混流 self.hun_file1btn.clicked.connect(lambda: self.hun_get_file('file1')) @@ -165,7 +164,6 @@ def initUI(self): self.hun_startbtn.clicked.connect(self.hun_fun) self.hun_opendir.clicked.connect(lambda: self.opendir_fn(self.hun_out.text())) - self.fanyi_target.addItems(["-"] + config.langnamelist) self.fanyi_import.clicked.connect(self.fanyi_import_fun) self.fanyi_start.clicked.connect(self.fanyi_start_fun) @@ -196,7 +194,7 @@ def initUI(self): self.task_logs.post_logs.connect(self.receiver) self.task_logs.start() - def geshi_import_fun(self,obj): + def geshi_import_fun(self, obj): fnames, _ = QFileDialog.getOpenFileNames(self, config.transobj['selectmp4'], config.last_opendir, "Video files(*.mp4 *.avi *.mov *.m4a *.mp3 *.aac *.flac *.wav)") if len(fnames) < 1: @@ -205,7 +203,7 @@ def geshi_import_fun(self,obj): fnames[i] = it.replace('\\', '/') if len(fnames) > 0: - config.last_opendir=os.path.dirname(fnames[0]) + config.last_opendir = os.path.dirname(fnames[0]) self.settings.setValue("last_dir", config.last_opendir) obj.setPlainText("\n".join(fnames)) @@ -214,8 +212,8 @@ def hun_get_file(self, name='file1'): fname, _ = QFileDialog.getOpenFileName(self, "Select audio", config.last_opendir, "Audio files(*.wav *.mp3 *.aac *.m4a *.flac)") if fname: - fname=fname.replace('file:///', '').replace('\\', '/') - config.last_opendir=os.path.dirname(fname) + fname = fname.replace('file:///', '').replace('\\', '/') + config.last_opendir = os.path.dirname(fname) self.settings.setValue("last_dir", config.last_opendir) if name == 'file1': self.hun_file1.setText(fname) @@ -227,30 +225,30 @@ def fanyi_import_fun(self, obj=None): fnames, _ = QFileDialog.getOpenFileNames(self, config.transobj['tuodongfanyi'], config.last_opendir, - "Subtitles files(*.srt)") + "Subtitles files(*.srt)") if len(fnames) < 1: return for (i, it) in enumerate(fnames): fnames[i] = it.replace('\\', '/').replace('file:///', '') if fnames: - self.fanyi_files=fnames - config.last_opendir=os.path.dirname(fnames[0]) + self.fanyi_files = fnames + config.last_opendir = os.path.dirname(fnames[0]) self.settings.setValue("last_dir", config.last_opendir) self.fanyi_sourcetext.setPlainText(f'{config.transobj["yidaorujigewenjian"]}{len(fnames)}') def hecheng_import_fun(self): fnames, _ = QFileDialog.getOpenFileNames(self, "Select srt", config.last_opendir, - "Text files(*.srt)") + "Text files(*.srt)") if len(fnames) < 1: return for (i, it) in enumerate(fnames): fnames[i] = it.replace('\\', '/').replace('file:///', '') if len(fnames) > 0: - config.last_opendir=os.path.dirname(fnames[0]) + config.last_opendir = os.path.dirname(fnames[0]) self.settings.setValue("last_dir", config.last_opendir) self.hecheng_files = fnames - content="" + content = "" try: with open(fnames[0], 'r', encoding='utf-8') as f: content = f.read().strip() @@ -260,7 +258,8 @@ def hecheng_import_fun(self): self.hecheng_plaintext.setPlainText(content) self.tts_issrt.setChecked(True) self.tts_issrt.setDisabled(True) - self.hecheng_importbtn.setText(f'导入{len(fnames)}个srt文件' if config.defaulelang=='zh' else f'Import {len(fnames)} Subtitles' ) + self.hecheng_importbtn.setText( + f'导入{len(fnames)}个srt文件' if config.defaulelang == 'zh' else f'Import {len(fnames)} Subtitles') def render_play(self, t): if t != 'ok': @@ -288,115 +287,80 @@ def opendir_fn(self, dirname=None): def receiver(self, json_data): data = json.loads(json_data) # fun_name 方法名,type类型,text具体文本 - - if data['func_name'] == "yspfl_end": - # 音视频分离完成了 - self.yspfl_startbtn.setText(config.transobj["zhixingwc"] if data['type'] == "end" else config.transobj["zhixinger"]) - self.yspfl_startbtn.setDisabled(False) - self.statuslabel.setText("Succeed") - elif data['func_name'] == 'ysphb_end': - self.ysphb_startbtn.setText(config.transobj["zhixingwc"] if data['type'] == "end" else config.transobj["zhixinger"]) - self.ysphb_startbtn.setDisabled(False) - self.ysphb_opendir.setDisabled(False) - if data['type'] == 'end': - self.statuslabel.setText("Succeed") - basename = os.path.basename(self.ysphb_videoinput.text()) - if os.path.exists(config.rootdir + f"/{basename}.srt"): - os.unlink(config.rootdir + f"/{basename}.srt") - elif data['func_name'] == "shibie_end": - # 识别执行完成 - self.disabled_shibie(False) - if data['type'] == 'replace_subtitle': - self.shibie_text.clear() - text,per=data['text'].split('@@@@@@') - self.shibie_text.insertPlainText(text) - self.shibie_startbtn.setText(config.transobj["ing"]+per) - elif data['type']=='allend': - self.shibie_startbtn.setText(config.transobj["zhixingwc"]) - self.statuslabel.setText("Succeed") - self.shibie_dropbtn.setText(config.transobj['quanbuend']+". "+config.transobj['xuanzeyinshipin']) - if data['text']: - self.label_shibie10.setText(data['text']) - self.label_shibie10.setStyleSheet('''color:#ff0000''') + func = data['func_name'] + type = data['type'] + if func == 'yspfl': + if type == 'end' or type == 'error': + self.yspfl_startbtn.setDisabled(False) + self.yspfl_startbtn.setText(config.transobj["zhixingwc"]) + if type=='error': QMessageBox.critical(self,config.transobj['anerror'],data['text']) else: - self.shibie_dropbtn.setText(data['text']) - - elif data['func_name'] == 'hecheng_set': - # 填充合成文本 - self.hecheng_plaintext.setPlainText(data['text']) - elif data['func_name'] == 'hecheng_end': - if data['type'] == 'ing': - self.hecheng_startbtn.setText(config.transobj["running"]+data['text']) - elif data['type']=='end': - self.hecheng_out.setDisabled(False) - self.hecheng_startbtn.setDisabled(False) - self.hecheng_startbtn.setText(config.uilanglist['Start']) - self.hecheng_importbtn.setText(config.box_lang['Import text to be translated from a file..']) - self.hecheng_plaintext.clear() - self.tts_issrt.setDisabled(False) - if data['text']!='Succeed': + self.yspfl_startbtn.setText(data['text']) + self.yspfl_startbtn.setDisabled(True) + elif func == 'ysphb': + if type == 'end' or type == 'error': + self.ysphb_startbtn.setDisabled(False) + self.ysphb_startbtn.setText(config.transobj["zhixingwc"]) + self.ysphb_opendir.setDisabled(False) + if type=='error': QMessageBox.critical(self,config.transobj['anerror'],data['text']) - self.hecheng_startbtn.setDisabled(False) - self.hecheng_startbtn.setText(config.uilanglist['Start']) else: - QMessageBox.critical(self,config.transobj['anerror'],data['text']) + self.ysphb_startbtn.setText(data['text']) + self.ysphb_startbtn.setDisabled(True) + elif func == 'shibie': + if type == 'replace': + self.shibie_text.clear() + self.shibie_text.insertPlainText(data['text']) + elif type == 'set': + self.shibie_text.moveCursor(QTextCursor.End) + self.shibie_text.insertPlainText(data['text'].capitalize()) + elif type == 'error' or type == 'end': + self.shibie_startbtn.setDisabled(False) + self.shibie_dropbtn.setDisabled(False) + if type=='end': + self.shibie_startbtn.setText(config.transobj["zhixingwc"]) + self.shibie_dropbtn.setText(config.transobj['quanbuend'] + ". " + config.transobj['xuanzeyinshipin']) + else: + self.shibie_dropbtn.setText(data['text']) + else: + self.shibie_startbtn.setText(data['text']) + elif func == 'hecheng': + if type=='replace': + self.hecheng_plaintext.clear() + self.hecheng_plaintext.setPlainText(data['text']) + elif type == 'error' or type == 'end': self.hecheng_startbtn.setDisabled(False) - self.hecheng_startbtn.setText(config.uilanglist['Start']) - elif data['func_name'] == 'geshi_end': - config.geshi_num -= 1 - self.geshi_result.moveCursor(QTextCursor.End) - self.geshi_result.insertPlainText("\n"+data['text']) - if config.geshi_num <= 0: - self.disabled_geshi(False) - self.geshi_result.moveCursor(QTextCursor.End) - self.geshi_result.insertPlainText(config.transobj["zhixingwc"]) - self.geshi_input.clear() - self.statuslabel.setText("Succeed") - elif data['func_name'] == 'hun_end': - self.hun_startbtn.setDisabled(False) - self.hun_out.setDisabled(False) - self.statuslabel.setText("Succeed") - # 完成一个字幕文件翻译 - elif data['func_name'] == 'fanyi_end': - self.fanyi_targettext.clear() - self.fanyi_targettext.setPlainText(data['text']) - #设置源文本框字幕 - elif data['func_name']=='fanyi_set_source': - self.fanyi_sourcetext.clear() - self.fanyi_sourcetext.setPlainText(data['text']) - #全部翻译结束 - elif data['func_name']=='fanyi_all': - self.fanyi_start.setDisabled(False) - self.fanyi_start.setText(config.transobj['starttrans']) - self.daochu.setDisabled(False) - self.fanyi_sourcetext.clear() - self.statuslabel.clear() - if self.fanyi_errors: - self.fanyi_sourcetext.setPlainText(self.fanyi_errors) + self.hecheng_startbtn.setText(data['text'] if type=='error' else config.transobj["zhixingwc"]) else: - self.fanyi_sourcetext.setPlainText(config.transobj['quanbuwanbi']) - # 设置翻译结果文本框,每行更新 - elif data['func_name']=='set_fanyi': - self.fanyi_targettext.moveCursor(QTextCursor.End) - self.fanyi_targettext.insertPlainText(data['text'].capitalize()) - #翻译出错了 - elif data['func_name']=='fanyi_oneerror': - self.fanyi_errors+=data['text'] if data['text'] else "" - self.statuslabel.setText(data['text']) - self.fanyi_start.setDisabled(False) - self.fanyi_start.setText(config.transobj['starttrans']) - self.daochu.setDisabled(False) - elif data['func_name']=='set_subtitle': - self.shibie_text.moveCursor(QTextCursor.End) - self.shibie_text.insertPlainText(data['text'].capitalize()) - elif "func_name" not in data or not data['func_name']: - self.statuslabel.setText(data['text'][:60]) - if data['type'] == 'error': - self.statuslabel.setStyle("""color:#ff0000""") - QMessageBox.critical(self,config.transobj['anerror'],data['text']) - else: - self.statuslabel.setText(data['text']) + self.hecheng_startbtn.setText(data['text']) + elif func == 'fanyi': + if type == 'error' or type == 'end': + self.fanyi_start.setDisabled(False) + self.daochu.setDisabled(False) + self.fanyi_start.setText(config.transobj["zhixingwc"]) + self.fanyi_sourcetext.setPlainText(config.transobj["zhixingwc"]) + if type == 'error': + self.fanyi_sourcetext.setPlainText(data['text']) + self.fanyi_targettext.moveCursor(QTextCursor.End) + self.fanyi_targettext.insertPlainText(data['text']) + elif type == 'replace': + self.fanyi_targettext.clear() + self.fanyi_targettext.setPlainText(data['text']) + elif type == 'set': + self.fanyi_targettext.moveCursor(QTextCursor.End) + self.fanyi_targettext.insertPlainText(data['text'].capitalize()) + else: + self.fanyi_sourcetext.setPlainText(data['text']) + + elif func == 'hunhe': + if type == 'error' or type == 'end': + self.hun_startbtn.setDisabled(False) + self.hun_startbtn.setText(config.transobj["zhixingwc"]) + if type=='error': + QMessageBox.critical(self,config.transobj['anerror'],data['text']) + else: + self.hun_startbtn.setText(data['text']) # tab-1 音视频分离启动 def yspfl_start_fn(self): @@ -404,15 +368,15 @@ def yspfl_start_fn(self): return QMessageBox.critical(self, config.transobj['anerror'], config.transobj['selectvideodir']) file = self.yspfl_video_wrap.filepath basename = os.path.basename(file) - rs,newfile,base=tools.rename_move(file,is_dir=False) + rs, newfile, base = tools.rename_move(file, is_dir=False) if rs: - file=newfile - basename=base + file = newfile + basename = base video_out = f"{config.homedir}/{basename}" if not os.path.exists(video_out): os.makedirs(video_out, exist_ok=True) self.yspfl_task = Worker( - [['-y', '-i', file, '-an', f"{video_out}/{basename}.mp4", f"{video_out}/{basename}.wav"]], "yspfl_end", + [['-y', '-i', file, '-an', f"{video_out}/{basename}.mp4", f"{video_out}/{basename}.wav"]], "yspfl", self) self.yspfl_task.start() @@ -442,8 +406,7 @@ def ysphb_select_fun(self, name): else: mime = "Srt files(*.srt)" showname = " srt " - fname, _ = QFileDialog.getOpenFileName(self, f"Select {showname} file", os.path.expanduser('~') + "\\Videos", - mime) + fname, _ = QFileDialog.getOpenFileName(self, f"Select {showname} file", os.path.expanduser('~') + "\\Videos", mime) if not fname: return @@ -461,7 +424,7 @@ def ysphb_start_fun(self): srtfile = self.ysphb_srtinput.text() wavfile = self.ysphb_wavinput.text() # 是否保留原声 - save_raw=self.ysphb_replace.isChecked() + save_raw = self.ysphb_replace.isChecked() if not videofile or not os.path.exists(videofile): QMessageBox.critical(self, config.transobj['anerror'], config.transobj['selectvideodir']) return @@ -473,20 +436,18 @@ def ysphb_start_fun(self): return if os.path.exists(videofile): - rs,newfile,base=tools.rename_move(videofile,is_dir=False) + rs, newfile, base = tools.rename_move(videofile, is_dir=False) if rs: - videofile=newfile - basename=base + videofile = newfile + basename = base if os.path.exists(srtfile): - rs,newsrtfile,_=tools.rename_move(srtfile,is_dir=False) + rs, newsrtfile, _ = tools.rename_move(srtfile, is_dir=False) if rs: - srtfile=newsrtfile + srtfile = newsrtfile if os.path.exists(wavfile): - rs,newwavfile,_=tools.rename_move(wavfile,is_dir=False) + rs, newwavfile, _ = tools.rename_move(wavfile, is_dir=False) if rs: - wavfile=newwavfile - - + wavfile = newwavfile savedir = f"{config.homedir}/hebing-{basename}" os.makedirs(savedir, exist_ok=True) @@ -512,41 +473,41 @@ def ysphb_start_fun(self): # 视频里是否有音轨 并且保留原声音 if video_info['streams_audio'] > 0 and save_raw: tmp_a = f'{config.rootdir}/tmp/box-a.m4a' - + cmds = [ - ['-y', '-i', videofile, '-i', wavfile,'-vn', '-filter_complex', - "[1:a]apad[a1];[0:a][a1]amerge=inputs=2[aout]", '-map','[aout]','-ac', '2', tmp_a], - ['-y', '-i', videofile, '-i', tmp_a, '-filter_complex', "[1:a]apad", '-c:v', 'copy', '-c:a', 'aac','-shortest', + ['-y', '-i', videofile, '-i', wavfile, '-vn', '-filter_complex', + "[1:a]apad[a1];[0:a][a1]amerge=inputs=2[aout]", '-map', '[aout]', '-ac', '2', tmp_a], + ['-y', '-i', videofile, '-i', tmp_a, '-filter_complex', "[1:a]apad", '-c:v', 'copy', '-c:a', 'aac', + '-shortest', tmpname if srtfile else f'{savedir}/{basename}.mp4'] ] else: cmds = [ - ['-y', '-i', videofile, '-i', wavfile, '-filter_complex', "[1:a]apad", '-c:v', 'copy', '-c:a', 'aac','-shortest', + ['-y', '-i', videofile, '-i', wavfile, '-filter_complex', "[1:a]apad", '-c:v', 'copy', '-c:a', + 'aac', '-shortest', tmpname if srtfile else f'{savedir}/{basename}.mp4']] if srtfile: - #srtfile = srtfile.replace('\\', '/').replace(':', '\\\\:') - basename=os.path.basename(srtfile) + # srtfile = srtfile.replace('\\', '/').replace(':', '\\\\:') + basename = os.path.basename(srtfile) shutil.copy2(srtfile, config.rootdir + f"/{basename}.srt") os.chdir(config.rootdir) cmds.append( - #['-y', '-i', tmpname if wavfile else videofile, "-vf", f"subtitles={basename}.srt", '-c:v', 'libx264', + # ['-y', '-i', tmpname if wavfile else videofile, "-vf", f"subtitles={basename}.srt", '-c:v', 'libx264', # '-c:a', 'copy', f'{savedir}/{basename}.mp4'] [ - "-y", - "-i", - os.path.normpath(tmpname if wavfile else videofile), - - "-c:v", - "libx264", - "-vf", - f"subtitles={basename}.srt", - "-shortest", - f'{savedir}/{basename}.mp4' - ] - - ) - self.ysphb_task = Worker(cmds, "ysphb_end", self) + "-y", + "-i", + os.path.normpath(tmpname if wavfile else videofile), + + "-c:v", + "libx264", + "-vf", + f"subtitles={basename}.srt", + "-shortest", + f'{savedir}/{basename}.mp4' + ]) + self.ysphb_task = Worker(cmds, "ysphb", self) self.ysphb_task.start() self.ysphb_startbtn.setText(config.transobj["running"]) @@ -568,68 +529,67 @@ def check_cuda(self, state): self.is_cuda.setChecked(False) self.is_cuda.setDisabled(True) - - #设定模型类型 + # 设定模型类型 def model_type_change(self): - if self.shibie_model_type.currentIndex()==1: - model_type='openai' + if self.shibie_model_type.currentIndex() == 1: + model_type = 'openai' self.shibie_whisper_type.setDisabled(False) self.shibie_model.setDisabled(False) - elif self.shibie_model_type.currentIndex()==2: - model_type='GoogleSpeech' + elif self.shibie_model_type.currentIndex() == 2: + model_type = 'GoogleSpeech' self.shibie_whisper_type.setDisabled(True) self.shibie_model.setDisabled(True) - elif self.shibie_model_type.currentIndex()==3: - model_type='zh_recogn' + elif self.shibie_model_type.currentIndex() == 3: + model_type = 'zh_recogn' self.shibie_whisper_type.setDisabled(True) self.shibie_model.setDisabled(True) else: self.shibie_whisper_type.setDisabled(False) self.shibie_model.setDisabled(False) - model_type='faster' + model_type = 'faster' # tab-3 语音识别 预执行,检查 def shibie_start_fun(self): model = self.shibie_model.currentText() - split_type_index=self.shibie_whisper_type.currentIndex() - if self.shibie_model_type.currentIndex()==1: - model_type='openai' - elif self.shibie_model_type.currentIndex()==2: - model_type='GoogleSpeech' - elif self.shibie_model_type.currentIndex()==3: - model_type='zh_recogn' + split_type_index = self.shibie_whisper_type.currentIndex() + if self.shibie_model_type.currentIndex() == 1: + model_type = 'openai' + elif self.shibie_model_type.currentIndex() == 2: + model_type = 'GoogleSpeech' + elif self.shibie_model_type.currentIndex() == 3: + model_type = 'zh_recogn' else: - model_type="faster" - is_cuda=self.is_cuda.isChecked() + model_type = "faster" + is_cuda = self.is_cuda.isChecked() if is_cuda and model_type == 'faster': - allow=True + allow = True try: from torch.backends import cudnn if not cudnn.is_available() or not cudnn.is_acceptable(torch.tensor(1.).cuda()): - allow=False + allow = False except: - allow=False + allow = False finally: if not allow: self.is_cuda.setChecked(False) return QMessageBox.critical(self, config.transobj['anerror'], config.transobj["nocudnn"]) - if model_type=='faster': + if model_type == 'faster': file = f'{config.rootdir}/models/models--Systran--faster-whisper-{model}/snapshots' if model.startswith('distil'): - file=f'{config.rootdir}/models/models--Systran--faster-{model}/snapshots' + file = f'{config.rootdir}/models/models--Systran--faster-{model}/snapshots' if not os.path.exists(file): - QMessageBox.critical(self.main, config.transobj['anerror'], - config.transobj['downloadmodel'].replace('{name}', model)) + QMessageBox.critical(self, config.transobj['anerror'], + config.transobj['downloadmodel'].replace('{name}', model)) return - elif model_type=='openai' and not os.path.exists(config.rootdir+f'/models/{model}.pt'): - return QMessageBox.critical(self, config.transobj['anerror'], config.transobj['openaimodelnot'].replace('{name}',model)) + elif model_type == 'openai' and not os.path.exists(config.rootdir + f'/models/{model}.pt'): + return QMessageBox.critical(self, config.transobj['anerror'], + config.transobj['openaimodelnot'].replace('{name}', model)) files = self.shibie_dropbtn.filelist - if not files or len(files)<1: + if not files or len(files) < 1: return QMessageBox.critical(self, config.transobj['anerror'], config.transobj['bixuyinshipin']) - - wait_list=[] + wait_list = [] self.shibie_startbtn.setText(config.transobj["running"]) self.disabled_shibie(True) self.label_shibie10.setText('') @@ -637,22 +597,23 @@ def shibie_start_fun(self): basename = os.path.basename(file) try: - rs,newfile,base=tools.rename_move(file,is_dir=False) + rs, newfile, base = tools.rename_move(file, is_dir=False) if rs: - file=newfile - basename=base + file = newfile + basename = base except Exception as e: print(f"removename {str(e)}") self.shibie_text.clear() - if os.path.splitext(basename)[-1].lower() in [".mp4", ".avi", ".mov",".mp3",".flac",".m4a",".mov",".aac"]: + if os.path.splitext(basename)[-1].lower() in [".mp4", ".avi", ".mov", ".mp3", ".flac", ".m4a", ".mov", + ".aac"]: out_file = f"{config.homedir}/tmp/{basename}.wav" if not os.path.exists(f"{config.homedir}/tmp"): os.makedirs(f"{config.homedir}/tmp") try: self.shibie_ffmpeg_task = Worker([ - ['-y', '-i', file,'-vn','-ac','1','-ar','8000', out_file] - ],"logs", self) + ['-y', '-i', file, '-vn', '-ac', '1', '-ar', '8000', out_file] + ], "logs", self) self.shibie_ffmpeg_task.start() wait_list.append(out_file) except Exception as e: @@ -663,24 +624,23 @@ def shibie_start_fun(self): else: wait_list.append(file) - self.shibie_out_path=config.homedir+f"/recogn" + self.shibie_out_path = config.homedir + f"/recogn" - os.makedirs(self.shibie_out_path,exist_ok=True) + os.makedirs(self.shibie_out_path, exist_ok=True) self.shibie_opendir.setDisabled(False) self.shibie_task = WorkerWhisper( audio_paths=wait_list, model=model, - split_type= ["all","split","avg"][split_type_index], + split_type=["all", "split", "avg"][split_type_index], model_type=model_type, language=translator.get_audio_code(show_source=self.shibie_language.currentText()), - func_name="shibie_end", + func_name="shibie", out_path=self.shibie_out_path, is_cuda=is_cuda, parent=self) self.shibie_task.start() - # 禁用启用按钮 def disabled_shibie(self, type): self.shibie_startbtn.setDisabled(type) @@ -702,23 +662,32 @@ def hecheng_start_fun(self): if language == '-' or role == 'No': return QMessageBox.critical(self, config.transobj['anerror'], config.transobj['yuyanjuesebixuan']) if tts_type == 'openaiTTS' and not config.params['chatgpt_key']: - return QMessageBox.critical(self, config.transobj['anerror'], config.transobj['bixutianxie'] + "chatGPT key") - if tts_type =='GPT-SoVITS' and langcode[:2] not in ['zh','ja','en']: - #除此指望不支持 - config.params['tts_type']='edgeTTS' + return QMessageBox.critical(self, config.transobj['anerror'], + config.transobj['bixutianxie'] + "chatGPT key") + if tts_type == 'GPT-SoVITS' and langcode[:2] not in ['zh', 'ja', 'en']: + # 除此指望不支持 + config.params['tts_type'] = 'edgeTTS' self.tts_type.setCurrentText('edgeTTS') - QMessageBox.critical(self,config.transobj['anerror'],config.transobj['nogptsovitslanguage']) + QMessageBox.critical(self, config.transobj['anerror'], config.transobj['nogptsovitslanguage']) return + if rate >= 0: + rate = f"+{rate}%" + else: + rate = f"{rate}%" + volume=int(self.volume_rate.value()) + pitch=int(self.pitch_rate.value()) + volume=f'+{volume}%' if volume>=0 else f'{volume}%' + pitch=f'+{pitch}Hz' if pitch>=0 else f'{volume}Hz' # 文件名称 filename = self.hecheng_out.text() if os.path.exists(filename): - filename='' + filename = '' if filename and re.search(r'[\\/]+', filename): filename = "" if not filename: - newrole=role.replace('/','-').replace('\\','-') - filename = f"{newrole}-rate{rate}" + newrole = role.replace('/', '-').replace('\\', '-') + filename = f"{newrole}-rate{rate.replace('%','')}-volume{volume.replace('%','')}-pitch{pitch}" else: filename = filename.replace('.wav', '') @@ -726,21 +695,20 @@ def hecheng_start_fun(self): os.makedirs(f"{config.homedir}/tts", exist_ok=True) wavname = f"{config.homedir}/tts/{filename}" - - if rate >= 0: - rate = f"+{rate}%" - else: - rate = f"{rate}%" + + issrt = self.tts_issrt.isChecked() self.hecheng_task = WorkerTTS(self, - files=self.hecheng_files if len(self.hecheng_files)>0 else txt, + files=self.hecheng_files if len(self.hecheng_files) > 0 else txt, role=role, rate=rate, + pitch=pitch, + volume=volume, langcode=langcode, wavname=wavname, tts_type=self.tts_type.currentText(), - func_name="hecheng_end", + func_name="hecheng", voice_autorate=issrt and self.voice_autorate.isChecked(), audio_ajust=issrt and self.audio_ajust.isChecked(), tts_issrt=issrt) @@ -760,6 +728,14 @@ def tts_issrt_change(self, state): # tts类型改变 def tts_type_change(self, type): + if type in ['edgeTTS','AzureTTS']: + self.volume_rate.setDisabled(False) + self.pitch_rate.setDisabled(False) + else: + self.volume_rate.setDisabled(True) + self.pitch_rate.setDisabled(True) + + if type == 'gtts': self.hecheng_role.clear() self.hecheng_role.addItems(['gtts']) @@ -769,41 +745,42 @@ def tts_type_change(self, type): elif type == 'elevenlabsTTS': self.hecheng_role.clear() self.hecheng_role.addItems(config.params['elevenlabstts_role']) - elif type in ['edgeTTS','AzureTTS']: + elif type in ['edgeTTS', 'AzureTTS']: self.hecheng_language_fun(self.hecheng_language.currentText()) - elif type=='clone-voice': + elif type == 'clone-voice': self.hecheng_role.clear() - self.hecheng_role.addItems([it for it in config.clone_voicelist if it !='clone']) - elif type=='TTS-API': + self.hecheng_role.addItems([it for it in config.clone_voicelist if it != 'clone']) + elif type == 'TTS-API': if not config.params['ttsapi_url']: - QMessageBox.critical(self,config.transobj['anerror'],config.transobj['ttsapi_nourl']) + QMessageBox.critical(self, config.transobj['anerror'], config.transobj['ttsapi_nourl']) return self.hecheng_role.clear() self.hecheng_role.addItems(config.params['ttsapi_voice_role'].split(",")) - elif type=='GPT-SoVITS': - code=translator.get_code(show_text=self.hecheng_language.currentText()) - if code and code !='-' and code[:2] not in ['zh','ja','en']: + elif type == 'GPT-SoVITS': + code = translator.get_code(show_text=self.hecheng_language.currentText()) + if code and code != '-' and code[:2] not in ['zh', 'ja', 'en']: self.tts_type.setCurrentText('edgeTTS') - QMessageBox.critical(self,config.transobj['anerror'],config.transobj['nogptsovitslanguage']) + QMessageBox.critical(self, config.transobj['anerror'], config.transobj['nogptsovitslanguage']) return - rolelist=tools.get_gptsovits_role() + rolelist = tools.get_gptsovits_role() self.hecheng_role.clear() self.hecheng_role.addItems(list(rolelist.keys()) if rolelist else ['GPT-SoVITS']) + # 合成语言变化,需要获取到角色 def hecheng_language_fun(self, t): code = translator.get_code(show_text=t) - if code and code !='-' and self.tts_type.currentText()=='GPT-SoVITS' and code[:2] not in ['zh','ja','en']: - #除此指望不支持 + if code and code != '-' and self.tts_type.currentText() == 'GPT-SoVITS' and code[:2] not in ['zh', 'ja', 'en']: + # 除此指望不支持 QMessageBox.critical(self, config.transobj['anerror'], config.transobj['nogptsovitslanguage']) self.tts_type.setCurrentText('edgeTTS') - if self.tts_type.currentText() not in ["edgeTTS","AzureTTS"]: + if self.tts_type.currentText() not in ["edgeTTS", "AzureTTS"]: return self.hecheng_role.clear() if t == '-': self.hecheng_role.addItems(['No']) return - show_rolelist= get_edge_rolelist() if config.params['tts_type']=='edgeTTS' else get_azure_rolelist() + show_rolelist = get_edge_rolelist() if config.params['tts_type'] == 'edgeTTS' else get_azure_rolelist() if not show_rolelist: show_rolelist = get_edge_rolelist() if not show_rolelist: @@ -824,58 +801,6 @@ def hecheng_language_fun(self, t): except: self.hecheng_role.addItems(['No']) - # tab-5 转换 - def geshi_start_fun(self, ext): - filelist = self.geshi_input.toPlainText().strip().split("\n") - filelist_vail = [] - for it in filelist: - if it and os.path.exists(it) and it.split('.')[-1].lower() in ['mp4', 'avi', 'mov', 'mp3', 'wav', 'aac', - 'm4a', 'flac']: - filelist_vail.append(it) - if len(filelist_vail) < 1: - return QMessageBox.critical(self, config.transobj['anerror'], config.transobj['nowenjian']) - self.geshi_input.setPlainText("\n".join(filelist_vail)) - self.disabled_geshi(True) - config.geshi_num = len(filelist_vail) - cmdlist = [] - savedir = f"{config.homedir}/conver" - if not os.path.exists(savedir): - os.makedirs(savedir, exist_ok=True) - for it in filelist_vail: - basename = os.path.basename(it) - ext_this = basename.split('.')[-1].lower() - if ext == ext_this: - config.geshi_num -= 1 - self.geshi_result.insertPlainText(f"{it} -> {ext}") - - continue - if ext_this in ["wav", "mp3", "aac", "m4a", "flac"] and ext in ["mp4", "mov", "avi"]: - self.geshi_result.insertPlainText(f"{it} {config.transobj['yinpinbuke']} {ext} ") - config.geshi_num -= 1 - - continue - - self.geshi_result.insertPlainText(f'{savedir}/{basename}.{ext}') - cmdlist.append(['-y', '-i', f'{it}', f'{savedir}/{basename}.{ext}']) - - if len(cmdlist) < 1: - self.geshi_result.insertPlainText(config.transobj["quanbuend"]) - self.disabled_geshi(False) - return - self.geshi_task = Worker(cmdlist, "geshi_end", self, True) - self.geshi_task.start() - - # 禁用按钮 - def disabled_geshi(self, type): - self.geshi_mp4.setDisabled(type) - self.geshi_avi.setDisabled(type) - self.geshi_mov.setDisabled(type) - self.geshi_mp3.setDisabled(type) - self.geshi_wav.setDisabled(type) - self.geshi_aac.setDisabled(type) - self.geshi_flac.setDisabled(type) - self.geshi_m4a.setDisabled(type) - # 音频混流 def hun_fun(self): out = self.hun_out.text().strip() @@ -905,7 +830,7 @@ def hun_fun(self): cmd = ['-y', '-i', file1, '-i', file2, '-filter_complex', "[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2", '-ac', '2', savename] - self.geshi_task = Worker([cmd], "hun_end", self, True) + self.geshi_task = Worker([cmd], "hunhe", self, True) self.geshi_task.start() self.hun_startbtn.setDisabled(True) self.hun_out.setDisabled(True) @@ -920,18 +845,17 @@ def fanyi_start_fun(self): if proxy: tools.set_proxy(proxy) - self.settings.setValue('proxy',proxy) - config.proxy=proxy + self.settings.setValue('proxy', proxy) + config.proxy = proxy else: proxy = self.settings.value("proxy", "", str) if proxy: tools.set_proxy(proxy) - config.proxy=proxy - self.settings.setValue('proxy',proxy) - if translate_type.lower()==GOOGLE_NAME: + config.proxy = proxy + self.settings.setValue('proxy', proxy) + if translate_type.lower() == GOOGLE_NAME: self.fanyi_proxy.setText(proxy) - config.params["baidu_appid"] = self.settings.value("baidu_appid", "") config.params["baidu_miyue"] = self.settings.value("baidu_miyue", "") config.params["deepl_authkey"] = self.settings.value("deepl_authkey", "") @@ -945,7 +869,7 @@ def fanyi_start_fun(self): config.params["azure_key"] = self.settings.value("azure_key", "") config.params["azure_model"] = self.settings.value("azure_model", "") - rs=translator.is_allow_translate(translate_type=translate_type,show_target=target_language) + rs = translator.is_allow_translate(translate_type=translate_type, show_target=target_language) if rs is not True: QMessageBox.critical(self, config.transobj['anerror'], rs) return @@ -958,8 +882,7 @@ def fanyi_start_fun(self): self.daochu.setDisabled(True) def fanyi_save_fun(self): - target=os.path.join(os.path.dirname(self.fanyi_files[0]),'_translate') - if len(self.fanyi_files)<1 or not os.path.exists(target): + target = os.path.join(os.path.dirname(self.fanyi_files[0]), '_translate') + if len(self.fanyi_files) < 1 or not os.path.exists(target): return QDesktopServices.openUrl(QUrl.fromLocalFile(target)) - diff --git a/videotrans/box/worker.py b/videotrans/box/worker.py index b07f0dcd..794639c2 100644 --- a/videotrans/box/worker.py +++ b/videotrans/box/worker.py @@ -29,23 +29,24 @@ def __init__(self, cmd_list, func_name="logs", parent=None, no_decode=False): self.no_decode = no_decode def run(self): - set_process_box(f'starting ffmpeg...') + set_process_box(f'starting ffmpeg...', type="start") for cmd in self.cmd_list: logger.info(f"[box]Will execute: ffmpeg {cmd=}") try: rs = runffmpeg(cmd, no_decode=self.no_decode, is_box=True) if not rs: - set_process_box(f'exec {cmd=} error', 'error') + set_process_box(f'exec {cmd=} error', type='error') except Exception as e: logger.error("[bxo]FFmepg exec error:" + str(e)) - set_process_box("[bxo]FFmepg exec error:" + str(e)) + set_process_box("[bxo]FFmepg exec error:" + str(e), type="error") return f'[error]{str(e)}' - set_process_box('ffmpeg succeed', "end", func_name=self.func_name) + set_process_box('ffmpeg succeed', type="end", func_name=self.func_name) # 执行语音识别 class WorkerWhisper(QThread): - def __init__(self, *, audio_paths=None, model=None, language=None, func_name=None, model_type='faster', parent=None,out_path=None,is_cuda=False,split_type='split'): + def __init__(self, *, audio_paths=None, model=None, language=None, func_name=None, model_type='faster', parent=None, + out_path=None, is_cuda=False, split_type='split'): super(WorkerWhisper, self).__init__(parent) self.func_name = func_name self.audio_paths = audio_paths @@ -53,11 +54,11 @@ def __init__(self, *, audio_paths=None, model=None, language=None, func_name=Non self.model_type = model_type self.language = language self.out_path = out_path - self.is_cuda=is_cuda - self.split_type=split_type + self.is_cuda = is_cuda + self.split_type = split_type def run(self): - set_process_box(f'start {self.model} ') + set_process_box(f'start {self.model} ', type="logs") errs = [] length = len(self.audio_paths) time_dur = 1 @@ -74,9 +75,9 @@ def run(self): time.sleep(1) time_dur += 1 continue - self.post_message("logs", f'{config.transobj["kaishitiquzimu"]}:{os.path.basename(audio_path)}') - jindu = f'@@@@@@ {int((length - len(self.audio_paths)) * 49 / length)}%' - self.post_message("replace_subtitle", jindu) + self.post_message(type="logs",text=f'{config.transobj["kaishitiquzimu"]}:{os.path.basename(audio_path)}') + jindu = f'{int((length - len(self.audio_paths)) * 49 / length)}%' + self.post_message(type='logs',text=f'{jindu}') srts = run_recogn(type=self.split_type, audio_file=audio_path, model_name=self.model, detect_language=self.language, set_p=False, cache_folder=config.TEMP_DIR, model_type=self.model_type, @@ -87,14 +88,14 @@ def run(self): text = "\n\n".join(text) with open(self.out_path + f"/{os.path.basename(audio_path)}.srt", 'w', encoding='utf-8') as f: f.write(text) - self.post_message("replace_subtitle", f'{text}{jindu}') + self.post_message(type="replace", text=f'{text}') except Exception as e: errs.append(f'失败,{str(e)}') - self.post_message('allend', "" if len(errs) < 1 else "\n".join(errs)) + self.post_message(type='end', text="" if len(errs) < 1 else "\n".join(errs)) config.box_recogn = 'stop' - def post_message(self, type, text): - set_process_box(text, type, func_name=self.func_name) + def post_message(self, type=None, text=None): + set_process_box(text, type=type, func_name=self.func_name) # 合成 @@ -103,6 +104,8 @@ def __init__(self, parent=None, *, files=None, role=None, rate=None, + volume="+0%", + pitch="+0Hz", wavname=None, tts_type=None, func_name=None, @@ -111,7 +114,9 @@ def __init__(self, parent=None, *, audio_ajust=False, tts_issrt=False): super(WorkerTTS, self).__init__(parent) - self.all_text=[] + self.volume=volume + self.pitch=pitch + self.all_text = [] self.func_name = func_name self.files = files self.role = role @@ -119,7 +124,7 @@ def __init__(self, parent=None, *, self.wavname = wavname self.tts_type = tts_type self.tts_issrt = tts_issrt - self.langcode=langcode + self.langcode = langcode self.voice_autorate = voice_autorate self.audio_ajust = audio_ajust self.tmpdir = f'{homedir}/tmp' @@ -130,37 +135,37 @@ def run(self): config.box_tts = 'ing' # 字幕格式 if self.tts_issrt: - self.all_text=[] - if isinstance(self.files,str): - self.all_text.append({"text":self.files.strip(),"file":"srt-voice"}) + self.all_text = [] + if isinstance(self.files, str): + self.all_text.append({"text": self.files.strip(), "file": "srt-voice"}) else: for it in self.files: - content="" + content = "" try: - with open(it,'r',encoding='utf-8') as f: - content=f.read().strip() + with open(it, 'r', encoding='utf-8') as f: + content = f.read().strip() except: - with open(it,'r',encoding='utf-8') as f: - content=f.read().strip() + with open(it, 'r', encoding='utf-8') as f: + content = f.read().strip() finally: if content: - self.all_text.append({"text":content,"file":os.path.basename(it)}) + self.all_text.append({"text": content, "file": os.path.basename(it)}) try: - errs,success=self.before_tts() + errs, success = self.before_tts() config.box_tts = 'stop' - if success==0: - self.post_message('error', f'全部失败了请打开输出目录中txt文件查看失败记录') - elif errs>0: - self.post_message("end", f"失败{errs}个,成功{success}个,请查看输出目录中txt文件查看失败记录") + if success == 0: + self.post_message(type='error', text=f'全部失败了请打开输出目录中txt文件查看失败记录') + elif errs > 0: + self.post_message(type="end", text=f"失败{errs}个,成功{success}个,请查看输出目录中txt文件查看失败记录") else: - self.post_message("end", "Succeed") + self.post_message(type="end", text="Succeed") except Exception as e: config.box_tts = 'stop' - self.post_message('error', f'srt create dubbing error:{str(e)}') + self.post_message(type='error', text=f'srt create dubbing error:{str(e)}') return - mp3 = self.wavname+".mp3" + mp3 = self.wavname + ".mp3" try: text_to_speech( text=self.files, @@ -169,6 +174,8 @@ def run(self): filename=mp3, language=self.langcode, tts_type=self.tts_type, + volume=self.volume, + pitch=self.pitch, set_p=False ) @@ -184,18 +191,17 @@ def run(self): os.unlink(mp3) except Exception as e: config.box_tts = 'stop' - self.post_message('error', f'srt create dubbing error:{str(e)}') + self.post_message(type='error', text=f'srt create dubbing error:{str(e)}') return config.box_tts = 'stop' - self.post_message("end", "Succeed") - + self.post_message(type="end", text="Succeed") # 1. 将每个配音的实际长度加入 dubb_time # def _add_dubb_time(self, queue_tts): for i, it in enumerate(queue_tts): - it['video_add']=0 + it['video_add'] = 0 # 防止开始时间比上个结束时间还小 if i > 0 and it['start_time'] < queue_tts[i - 1]['end_time']: it['start_time'] = queue_tts[i - 1]['end_time'] @@ -205,29 +211,31 @@ def _add_dubb_time(self, queue_tts): # 保存原始 it['start_time_source'] = it['start_time'] it['end_time_source'] = it['end_time'] - #记录原字母区间时长 - it['raw_duration']=it['end_time']-it['start_time'] + # 记录原字母区间时长 + it['raw_duration'] = it['end_time'] - it['start_time'] - if it['end_time'] > it['start_time'] and os.path.exists(it['filename']) and os.path.getsize(it['filename']) > 0: + if it['end_time'] > it['start_time'] and os.path.exists(it['filename']) and os.path.getsize( + it['filename']) > 0: it['dubb_time'] = len(AudioSegment.from_file(it['filename'], format="mp3")) else: - #不存在配音 + # 不存在配音 it['dubb_time'] = 0 queue_tts[i] = it return queue_tts - #2. 移除原字幕多于配音的时长,实际是字幕结束时间向前移动,和下一条之间的空白更加多了 - def _remove_srt_silence(self,queue_tts): - #如果需要移除多出来的静音 - for i,it in enumerate(queue_tts): + # 2. 移除原字幕多于配音的时长,实际是字幕结束时间向前移动,和下一条之间的空白更加多了 + def _remove_srt_silence(self, queue_tts): + # 如果需要移除多出来的静音 + for i, it in enumerate(queue_tts): # 配音小于 原时长,移除默认静音 - if it['dubb_time']>0 and it['dubb_time'] < it['raw_duration']: - diff=it['raw_duration']-it['dubb_time'] - it['end_time']-=diff - it['raw_duration']=it['dubb_time'] - queue_tts[i]=it + if it['dubb_time'] > 0 and it['dubb_time'] < it['raw_duration']: + diff = it['raw_duration'] - it['dubb_time'] + it['end_time'] -= diff + it['raw_duration'] = it['dubb_time'] + queue_tts[i] = it return queue_tts + # 3. 自动后延或前延以对齐 def _auto_ajust(self, queue_tts): max_index = len(queue_tts) - 1 @@ -247,8 +255,8 @@ def _auto_ajust(self, queue_tts): # 如果是最后一个,直接延长 it['end_time'] += diff it['endraw'] = ms_to_time_string(ms=it['end_time']) - #重新设定可用的字幕区间时长 - it['raw_duration']=it['end_time']-it['start_time'] + # 重新设定可用的字幕区间时长 + it['raw_duration'] = it['end_time'] - it['start_time'] queue_tts[i] = it continue @@ -258,7 +266,7 @@ def _auto_ajust(self, queue_tts): # 如果大于0,有空白,添加 it['end_time'] += diff it['endraw'] = ms_to_time_string(ms=it['end_time']) - it['raw_duration']=it['end_time']-it['start_time'] + it['raw_duration'] = it['end_time'] - it['start_time'] queue_tts[i] = it continue @@ -273,39 +281,37 @@ def _auto_ajust(self, queue_tts): # 前面再添加最多 diff - next_diff it['start_time'] -= min(prev_diff, diff - next_diff) it['start_time'] = 0 if it['start_time'] < 0 else it['start_time'] - it['raw_duration']=it['end_time']-it['start_time'] + it['raw_duration'] = it['end_time'] - it['start_time'] it['startraw'] = ms_to_time_string(ms=it['start_time']) it['endraw'] = ms_to_time_string(ms=it['end_time']) queue_tts[i] = it return queue_tts - # 移除2个字幕间的间隔 config.settings[remove_white_ms] ms - def _remove_white_ms(self,queue_tts): - offset=0 - for i,it in enumerate(queue_tts): - if i>0: - it['start_time']-=offset - it['end_time']-=offset + def _remove_white_ms(self, queue_tts): + offset = 0 + for i, it in enumerate(queue_tts): + if i > 0: + it['start_time'] -= offset + it['end_time'] -= offset # 配音小于 原时长,移除默认静音 - dt=it['start_time']-queue_tts[i-1]['end_time'] - if dt>config.settings['remove_white_ms']: - diff=config.settings['remove_white_ms'] - it['end_time']-=diff - it['start_time']-=diff - offset+=diff - queue_tts[i]=it + dt = it['start_time'] - queue_tts[i - 1]['end_time'] + if dt > config.settings['remove_white_ms']: + diff = config.settings['remove_white_ms'] + it['end_time'] -= diff + it['start_time'] -= diff + offset += diff + queue_tts[i] = it return queue_tts - # 2. 先对配音加速,每条字幕信息中写入加速倍数 speed和延长的时间 add_time def _ajust_audio(self, queue_tts): # 遍历所有字幕条, 计算应该的配音加速倍数和延长的时间 max_speed = config.settings['audio_rate'] - if max_speed>=100: - max_speed=99 + if max_speed >= 100: + max_speed = 99 - #设置加速倍数 + # 设置加速倍数 for i, it in enumerate(queue_tts): it['speed'] = 0 # 存在配音时进行处理 没有配音 @@ -323,20 +329,18 @@ def _ajust_audio(self, queue_tts): continue # 是否按照对齐的一半进行,用于音频加速和视频慢速同时起作用 # 倍数上浮 - it['speed'] = round(it['dubb_time'] / raw_duration,2) - if it['speed']<=1: - it['speed']=0 - queue_tts[i]=it + it['speed'] = round(it['dubb_time'] / raw_duration, 2) + if it['speed'] <= 1: + it['speed'] = 0 + queue_tts[i] = it continue - - # # 如果大于限制倍,则最大限制倍 - if max_speed> 1 and max_speed 1 and max_speed < it['speed']: + it['speed'] = max_speed - if it['speed']<1: - it['speed']=0 + if it['speed'] < 1: + it['speed'] = 0 queue_tts[i] = it # 再次遍历,调整字幕开始结束时间对齐实际音频时长 @@ -344,36 +348,35 @@ def _ajust_audio(self, queue_tts): offset = 0 for i, it in enumerate(queue_tts): - #偏移增加 + # 偏移增加 it['start_time'] += offset # 结束时间还需要额外添加 - it['end_time'] +=offset + it['end_time'] += offset - if it['speed']<=1: + if it['speed'] <= 1: # 不需要加速 it['startraw'] = ms_to_time_string(ms=it['start_time']) it['endraw'] = ms_to_time_string(ms=it['end_time']) queue_tts[i] = it continue - - if it['speed'] >1: + if it['speed'] > 1: # 调整音频 tmp_mp3 = os.path.join(self.tmpdir, f'{it["filename"]}-speed.mp3') speed_up_mp3(filename=it['filename'], speed=it['speed'], out=tmp_mp3) # 加速后时间 - mp3_len=len(AudioSegment.from_file(tmp_mp3, format="mp3")) - raw_t=it['raw_duration'] - #加速后如果仍大于原时长,再移除末尾静音 + mp3_len = len(AudioSegment.from_file(tmp_mp3, format="mp3")) + raw_t = it['raw_duration'] + # 加速后如果仍大于原时长,再移除末尾静音 if mp3_len > raw_t: tools.remove_silence_from_end(tmp_mp3) - add_time=len(AudioSegment.from_file(tmp_mp3, format="mp3"))-raw_t - if add_time>0: + add_time = len(AudioSegment.from_file(tmp_mp3, format="mp3")) - raw_t + if add_time > 0: # 需要延长结束时间,以便字幕 声音对齐 - it['end_time']+=add_time + it['end_time'] += add_time offset += add_time - it['video_add']=add_time - it['raw_duration']=it['end_time']-it['start_time'] + it['video_add'] = add_time + it['raw_duration'] = it['end_time'] - it['start_time'] it['filename'] = tmp_mp3 # 更改时间戳 @@ -382,20 +385,19 @@ def _ajust_audio(self, queue_tts): queue_tts[i] = it return queue_tts - # 配音预处理,去掉无效字符,整理开始时间 def before_tts(self): # 所有临时文件均产生在 tmp/无后缀mp4名文件夹 # 如果仅仅生成配音,则不限制时长 # 整合一个队列到 exec_tts 执行 - length=len(self.all_text) - errs=0 - for n,item in enumerate(self.all_text): + length = len(self.all_text) + errs = 0 + for n, item in enumerate(self.all_text): queue_tts = [] # 获取字幕 - percent=round(100*n/length,2) - set_process_box(item['text'],'logs',func_name='hecheng_set') - set_process_box(f'{percent+1}%','ing',func_name=self.func_name) + percent = round(100 * n / length, 2) + set_process_box(text=item['text'], type='replace', func_name=self.func_name) + set_process_box(text=f'{percent + 1}%', type='logs', func_name=self.func_name) subs = get_subtitle_from_srt(item["text"], is_file=False) rate = int(str(self.rate).replace('%', '')) if rate >= 0: @@ -413,30 +415,31 @@ def before_tts(self): "startraw": it['startraw'], "endraw": it['endraw'], "tts_type": self.tts_type, - "language":self.langcode, + "language": self.langcode, + "pitch":self.pitch, + "volume":self.volume, "filename": f"{self.tmpdir}/tts-{it['start_time']}.mp3"}) try: - run_tts(queue_tts=copy.deepcopy(queue_tts), language=self.langcode,set_p=False) + run_tts(queue_tts=copy.deepcopy(queue_tts), language=self.langcode, set_p=False) # 1.首先添加配音时间 queue_tts = self._add_dubb_time(queue_tts) # 2.移除字幕多于配音的时间,实际上是字幕结束时间前移,和下一条字幕空白更多 if config.settings['remove_srt_silence']: - queue_tts=self._remove_srt_silence(queue_tts) + queue_tts = self._remove_srt_silence(queue_tts) # 3.是否需要 前后延展 if self.audio_ajust: queue_tts = self._auto_ajust(queue_tts) - # 4. 如果需要配音加速 if self.voice_autorate: queue_tts = self._ajust_audio(queue_tts) # 5.从字幕间隔移除多余的毫秒数 - if config.settings['remove_white_ms']>0: - queue_tts=self._remove_white_ms(queue_tts) + if config.settings['remove_white_ms'] > 0: + queue_tts = self._remove_white_ms(queue_tts) # 开始合并音频 segments = [] for i, it in enumerate(queue_tts): @@ -444,19 +447,19 @@ def before_tts(self): segments.append(AudioSegment.from_file(it['filename'], format="mp3")) else: segments.append(AudioSegment.silent(duration=it['end_time'] - it['start_time'])) - self.merge_audio_segments(segments=segments,video_time=0,queue_tts=copy.deepcopy(queue_tts),out=f'{self.wavname}-{item["file"]}.wav') + self.merge_audio_segments(segments=segments, video_time=0, queue_tts=copy.deepcopy(queue_tts), + out=f'{self.wavname}-{item["file"]}.wav') except Exception as e: - errs+=1 - with open(f'{self.wavname}-error.txt','w',encoding='utf-8') as f: + errs += 1 + with open(f'{self.wavname}-error.txt', 'w', encoding='utf-8') as f: f.write(f'srt文件 {item["file"]} 合成失败,原因为:{str(e)}\n\n原字幕内容为\n\n{item["text"]}') finally: - percent=round(100*(n+1)/length,2) - set_process_box(f'{percent}%','ing',func_name=self.func_name) - return errs,length-errs + percent = round(100 * (n + 1) / length, 2) + set_process_box(text=f'{percent}%', type='logs', func_name=self.func_name) + return errs, length - errs - - def merge_audio_segments(self, *, segments=None, queue_tts=None, video_time=0,out=None): + def merge_audio_segments(self, *, segments=None, queue_tts=None, video_time=0, out=None): merged_audio = AudioSegment.empty() # start is not 0 if queue_tts[0]['start_time'] > 0: @@ -464,34 +467,33 @@ def merge_audio_segments(self, *, segments=None, queue_tts=None, video_time=0,ou silence = AudioSegment.silent(duration=silence_duration) merged_audio += silence # join - offset=0 - for i,it in enumerate(queue_tts): + offset = 0 + for i, it in enumerate(queue_tts): segment = segments[i] - the_dur=len(segment) - #字幕可用时间 - raw_dur=it['raw_duration'] - it['start_time']+=offset - it['end_time']+=offset + the_dur = len(segment) + # 字幕可用时间 + raw_dur = it['raw_duration'] + it['start_time'] += offset + it['end_time'] += offset - diff=the_dur-raw_dur + diff = the_dur - raw_dur # 配音大于字幕时长,后延,延长时间 - if diff>=0: - it['end_time']+=diff - offset+=diff + if diff >= 0: + it['end_time'] += diff + offset += diff else: - #配音小于原时长,添加静音 + # 配音小于原时长,添加静音 merged_audio += AudioSegment.silent(duration=abs(diff)) - if i > 0: silence_duration = it['start_time'] - queue_tts[i - 1]['end_time'] # 前面一个和当前之间存在静音区间 if silence_duration > 0: silence = AudioSegment.silent(duration=silence_duration) merged_audio += silence - it['startraw']=ms_to_time_string(ms=it['start_time']) - it['endraw']=ms_to_time_string(ms=it['end_time']) - queue_tts[i]=it + it['startraw'] = ms_to_time_string(ms=it['start_time']) + it['endraw'] = ms_to_time_string(ms=it['end_time']) + queue_tts[i] = it merged_audio += segment if video_time > 0 and (len(merged_audio) < video_time): @@ -502,11 +504,11 @@ def merge_audio_segments(self, *, segments=None, queue_tts=None, video_time=0,ou try: merged_audio.export(out, format="wav") except Exception as e: - raise Exception(f'[error]merge_audio:{str(e)}') + raise Exception(f'merge_audio:{str(e)}') return len(merged_audio), queue_tts def post_message(self, type, text=""): - set_process_box(text, type, func_name=self.func_name) + set_process_box(text=text, type=type, func_name=self.func_name) class FanyiWorker(QThread): @@ -517,30 +519,32 @@ def __init__(self, type, target_language, files, parent=None): self.target_language = target_language self.files = files self.srts = "" + self.func_name="fanyi" def run(self): # 开始翻译,从目标文件夹读取原始字幕 - set_process_box(f'start translate') + set_process_box(text=f'start translate',type='logs') config.box_trans = "ing" - target=os.path.join(os.path.dirname(self.files[0]),'_translate') - os.makedirs(target,exist_ok=True) + target = os.path.join(os.path.dirname(self.files[0]), '_translate') + os.makedirs(target, exist_ok=True) for f in self.files: try: rawsrt = get_subtitle_from_srt(f, is_file=True) except Exception as e: - set_process_box(f"\n{config.transobj['srtgeshierror']}:{f}" + str(e), 'error',func_name='fanyi_oneerror') + set_process_box(text=f"\n{config.transobj['srtgeshierror']}:{f}" + str(e), type='error', func_name=self.func_name) continue try: - set_process_box(f'正在翻译字幕{f}',func_name='fanyi_set_source') - srt = run_trans(translate_type=self.type, text_list=rawsrt, target_language_name=self.target_language, set_p=False) + set_process_box(text=f'正在翻译字幕{f}',type='logs', func_name=self.func_name) + srt = run_trans(translate_type=self.type, text_list=rawsrt, target_language_name=self.target_language, + set_p=False) srts_tmp = "" for it in srt: srts_tmp += f"{it['line']}\n{it['time']}\n{it['text']}\n\n" - with open(os.path.join(target,os.path.basename(f)),'w',encoding='utf-8') as f: + with open(os.path.join(target, os.path.basename(f)), 'w', encoding='utf-8') as f: f.write(srts_tmp) - set_process_box(srts_tmp, "end", func_name="fanyi_end") + set_process_box(text=srts_tmp, type="replace", func_name=self.func_name) except Exception as e: - set_process_box(f'翻译字幕{f}出错:{str(e)}', "error", func_name="fanyi_end") + set_process_box(text=f'翻译字幕{f}出错:{str(e)}', type="error", func_name=self.func_name) config.box_trans = "stop" - set_process_box("", "end", func_name="fanyi_all") + set_process_box(text="end", type="end", func_name=self.func_name) diff --git a/videotrans/configure/config.py b/videotrans/configure/config.py index 8b8ff420..6f59e0b5 100644 --- a/videotrans/configure/config.py +++ b/videotrans/configure/config.py @@ -73,7 +73,6 @@ def parse_init(): "remove_white_ms":100, "vsync":"passthrough", "force_edit_srt":True, - "append_video":True, "loop_backaudio":False, "cors_run":True } @@ -199,7 +198,7 @@ def parse_init(): "is_separate":False, "voice_role": "No", - "voice_rate": "+0%", + "voice_rate": "0", "listen_text_zh-cn": "你好啊,我亲爱的朋友,希望你的每一天都是美好愉快的!", "listen_text_zh-tw": "你好啊,我親愛的朋友,希望你的每一天都是美好愉快的!", diff --git a/videotrans/configure/language.py b/videotrans/configure/language.py deleted file mode 100644 index 79fb0cc7..00000000 --- a/videotrans/configure/language.py +++ /dev/null @@ -1,114 +0,0 @@ -lang_code_list = { - "中文简": [ - "zh-cn", - "chi", - "zh", - "ZH", - "zh" - ], - "中文繁": [ - "zh-tw", - "chi", - "cht", - "ZH", - "zh-TW" - ], - "英语": [ - "en", - "eng", - "en", - "EN-US", - "en" - ], - "法语": [ - "fr", - "fre", - "fra", - "FR", - "fr" - ], - "德语": [ - "de", - "ger", - "de", - "DE", - "de" - ], - "日语": [ - "ja", - "jpn", - "jp", - "JA", - "ja" - ], - "韩语": [ - "ko", - "kor", - "kor", - "KO", - "ko" - ], - "俄语": [ - "ru", - "rus", - "ru", - "RU", - "ru" - ], - "西班牙语": [ - "es", - "spa", - "spa", - "ES", - "es" - ], - "泰国语": [ - "th", - "tha", - "th", - "No", - "th" - ], - "意大利语": [ - "it", - "ita", - "it", - "IT", - "it" - ], - "葡萄牙语": [ - "pt", - "por", - "pt", - "PT", - "pt" - ], - "越南语": [ - "vi", - "vie", - "vie", - "No", - "vi" - ], - "阿拉伯语": [ - "ar", - "are", - "ara", - "No", - "ar" - ], - "土耳其语": [ - "tr", - "tur", - "tr", - "tr", - "tr" - ], - "印度语": [ - "hi", - "hin", - "hi", - "No", - "hi" - ] -} diff --git a/videotrans/language/en.json b/videotrans/language/en.json index 7e32a4a7..6ddf8c66 100644 --- a/videotrans/language/en.json +++ b/videotrans/language/en.json @@ -1,8 +1,9 @@ { "translate_language": { + "peiyindayu31": "The number of dubbing errors is greater than 1/3, please check the", + "chaochu255":"The original video path and name is too long, please shorten the video name and move it to a shorter path to avoid subsequent errors.", "teshufuhao":"Please do not include any special symbols such as + & ? : | etc. in the path or name of the original video to avoid subsequent errors.", - "notjson": "Return response is not valid json data", "fanyicuowu2": "The number of translation errors is more than half, please check", diff --git a/videotrans/language/es.json b/videotrans/language/es.json index 97c1acc7..c9522a16 100644 --- a/videotrans/language/es.json +++ b/videotrans/language/es.json @@ -1,5 +1,7 @@ { "translate_language" : { + "peiyindayu31": "The number of dubbing errors is greater than 1/3, please check the", + "chaochu255":"The original video path and name is too long, please shorten the video name and move it to a shorter path to avoid subsequent errors.", "teshufuhao":"Please do not include any special symbols such as + & ? : | etc. in the path or name of the original video to avoid subsequent errors.", diff --git a/videotrans/language/zh.json b/videotrans/language/zh.json index aeea92f4..ef9da67b 100644 --- a/videotrans/language/zh.json +++ b/videotrans/language/zh.json @@ -1,5 +1,7 @@ { "translate_language": { + "peiyindayu31": "配音出错数量大于1/3,请检查", + "chaochu255":"视频路径加名称过长,请缩短视频名称并移动到简短路径下,避免后续出错", "teshufuhao":"视频路径或名称中请勿存在 + & ? : | 等特殊符号,以避免后续出错", @@ -322,7 +324,7 @@ "Recognize the sound in audio or video and output SRT text": "将音频或视频中的声音识别后输出srt字幕文件", "From Text Into Speech": "批量字幕配音", "Generate audio WAV from text or SRT subtitle files": "根据srt字幕文件创建配音文件", - "Extract Srt And Translate": "语音转为字幕", + "Extract Srt And Translate": "视频转为字幕", "Extract SRT subtitles from local videos in the original language and translate them into SRT subtitle files in the target language": "识别视频中的语音为srt字幕", "Separate Video to audio": "从视频取音频", "Separate audio and silent videos from videos": "将视频分离为音频文件和无声mp4视频文件", diff --git a/videotrans/mainwin/secwin.py b/videotrans/mainwin/secwin.py index 8ec03296..b5add0d6 100644 --- a/videotrans/mainwin/secwin.py +++ b/videotrans/mainwin/secwin.py @@ -91,18 +91,14 @@ def check_cuda(self, state): # 配音速度改变时,更改全局 def voice_rate_changed(self, text): - text = str(text).replace('+', '').replace('%', '').strip() - text = 0 if not text else int(text) - text = f'+{text}%' if text >= 0 else f'{text}%' - config.params['voice_rate'] = text + # text = str(text).replace('+', '').replace('%', '').strip() + # text = 0 if not text else int(text) + # text = f'+{text}%' if text >= 0 else f'{text}%' + config.params['voice_rate'] = f'+{text}%' if text>=0 else f'{text}%' # 简单新手模式 def set_xinshoujandann(self): - if config.current_status == 'ing': - self.main.action_xinshoujandan.setChecked(False) - tools.send_notification("该模式执行中不可切换", - '请等待结束后再切换该模式' if config.defaulelang == 'zh' else 'Please wait until the end of the execution before switching modes.') - return + self.main.action_xinshoujandan.setChecked(True) self.main.app_mode = 'biaozhun_jd' self.main.show_tips.setText(config.transobj['xinshoumoshitips']) self.main.startbtn.setText(config.transobj['kaishichuli']) @@ -142,15 +138,20 @@ def set_xinshoujandann(self): # 字幕类型 self.hide_show_element(self.main.layout_subtitle_type, False) + # 隐藏音量 音调变化 + self.hide_show_element(self.main.edge_volume_layout, False) + # 配音语速 self.hide_show_element(self.main.layout_voice_rate, False) # 静音片段 # 配音自动加速 self.main.voice_autorate.setChecked(True) - self.main.video_autorate.setChecked(True) self.main.voice_autorate.hide() + self.main.video_autorate.setChecked(True) self.main.video_autorate.hide() + self.main.append_video.setChecked(True) + self.main.append_video.hide() self.main.splitter.setSizes([self.main.width, 0]) self.hide_show_element(self.main.subtitle_layout, False) @@ -174,11 +175,7 @@ def set_xinshoujandann(self): # 启用标准模式 def set_biaozhun(self): - if config.current_status == 'ing': - self.main.action_biaozhun.setChecked(False) - tools.send_notification("该模式执行中不可切换", - '请等待结束后再切换该模式' if config.defaulelang == 'zh' else 'Please wait until the end of the execution before switching modes.') - return + self.main.action_biaozhun.setChecked(True) self.main.app_mode = 'biaozhun' self.main.show_tips.setText("") self.main.startbtn.setText(config.transobj['kaishichuli']) @@ -207,6 +204,9 @@ def set_biaozhun(self): self.hide_show_element(self.main.layout_voice_role, True) # 试听按钮 + # 显示音量 音调变化 + self.hide_show_element(self.main.edge_volume_layout, True) + self.main.listen_btn.show() # 语音模型 self.hide_show_element(self.main.layout_whisper_model, True) @@ -218,34 +218,36 @@ def set_biaozhun(self): # 配音自动加速 # 视频自动降速 self.main.is_separate.setDisabled(False) + self.main.is_separate.show() + self.main.addbackbtn.setDisabled(False) self.main.only_video.setDisabled(False) self.main.back_audio.setReadOnly(False) self.main.auto_ajust.setDisabled(False) + self.main.auto_ajust.show() + self.main.video_autorate.setDisabled(False) self.main.voice_autorate.setDisabled(False) + self.main.voice_autorate.show() + + self.main.append_video.setDisabled(False) + self.main.append_video.setDisabled(False) self.hide_show_element(self.main.subtitle_layout, True) self.main.splitter.setSizes([self.main.width - 400, 400]) - self.main.voice_autorate.show() - self.main.auto_ajust.show() - self.main.is_separate.show() self.main.addbackbtn.show() self.main.back_audio.show() self.main.only_video.show() self.main.video_autorate.show() + self.main.append_video.show() # cuda self.main.enable_cuda.show() # 视频提取字幕并翻译,无需配音 def set_tiquzimu(self): - if config.current_status == 'ing': - self.main.action_tiquzimu.setChecked(False) - tools.send_notification("该模式执行中不可切换", - '请等待结束后再切换该模式' if config.defaulelang == 'zh' else 'Please wait until the end of the execution before switching modes.') - return + self.main.action_tiquzimu.setChecked(True) self.main.app_mode = 'tiqu' self.main.show_tips.setText(config.transobj['tiquzimu']) self.main.startbtn.setText(config.transobj['kaishitiquhefanyi']) @@ -262,6 +264,9 @@ def set_tiquzimu(self): # 保存目标 self.hide_show_element(self.main.layout_target_dir, True) + # 隐藏音量 音调变化 + self.hide_show_element(self.main.edge_volume_layout, False) + # 翻译渠道 self.hide_show_element(self.main.layout_translate_type, True) # 代理 @@ -295,7 +300,9 @@ def set_tiquzimu(self): self.main.auto_ajust.setDisabled(True) self.main.video_autorate.setDisabled(True) self.main.voice_autorate.setDisabled(True) + self.main.append_video.setDisabled(True) + self.main.append_video.hide() self.main.voice_autorate.hide() self.main.is_separate.hide() self.main.addbackbtn.hide() @@ -310,11 +317,7 @@ def set_tiquzimu(self): # 启用字幕合并模式, 仅显示 选择视频、保存目录、字幕类型、 cuda # 不配音、不识别, def set_zimu_video(self): - if config.current_status == 'ing': - self.main.action_zimu_video.setChecked(False) - tools.send_notification("该模式执行中不可切换", - '请等待结束后再切换该模式' if config.defaulelang == 'zh' else 'Please wait until the end of the execution before switching modes.') - return + self.main.action_zimu_video.setChecked(True) self.main.app_mode = 'hebing' self.main.show_tips.setText(config.transobj['zimu_video']) self.main.startbtn.setText(config.transobj['kaishihebing']) @@ -331,7 +334,9 @@ def set_zimu_video(self): self.hide_show_element(self.main.layout_source_mp4, True) # 保存目标 self.hide_show_element(self.main.layout_target_dir, True) - # self.main.open_targetdir.show() + + # 隐藏音量 音调变化 + self.hide_show_element(self.main.edge_volume_layout, False) # 翻译渠道 self.hide_show_element(self.main.layout_translate_type, False) @@ -364,6 +369,7 @@ def set_zimu_video(self): self.main.back_audio.setReadOnly(True) self.main.auto_ajust.setDisabled(True) self.main.video_autorate.setDisabled(True) + self.main.append_video.setDisabled(True) self.main.voice_autorate.setDisabled(True) self.main.only_video.show() @@ -373,6 +379,7 @@ def set_zimu_video(self): self.main.back_audio.hide() self.main.auto_ajust.hide() self.main.video_autorate.hide() + self.main.append_video.hide() # cuda self.main.enable_cuda.show() @@ -380,11 +387,7 @@ def set_zimu_video(self): # 仅仅对已有字幕配音, # 不翻译不识别 def set_zimu_peiyin(self): - if config.current_status == 'ing': - self.main.action_zimu_peiyin.setChecked(False) - tools.send_notification("该模式执行中不可切换", - '请等待结束后再切换该模式' if config.defaulelang == 'zh' else 'Please wait until the end of the execution before switching modes.') - return + self.main.action_zimu_peiyin.setChecked(True) self.main.show_tips.setText(config.transobj['zimu_peiyin']) self.main.startbtn.setText(config.transobj['kaishipeiyin']) self.main.action_zimu_peiyin.setChecked(True) @@ -401,6 +404,9 @@ def set_zimu_peiyin(self): # 保存目标 self.hide_show_element(self.main.layout_target_dir, True) + # 显示音量 音调变化 + self.hide_show_element(self.main.edge_volume_layout, True) + # 翻译渠道 self.hide_show_element(self.main.layout_translate_type, False) # 代理 openaitts @@ -431,6 +437,7 @@ def set_zimu_peiyin(self): self.main.is_separate.setDisabled(True) self.main.only_video.setDisabled(True) self.main.video_autorate.setDisabled(True) + self.main.append_video.setDisabled(True) self.main.voice_autorate.setDisabled(False) self.main.auto_ajust.setDisabled(False) self.main.back_audio.setReadOnly(False) @@ -439,6 +446,7 @@ def set_zimu_peiyin(self): self.main.voice_autorate.show() self.main.is_separate.hide() self.main.video_autorate.hide() + self.main.append_video.hide() self.main.only_video.hide() self.main.auto_ajust.show() self.main.back_audio.show() @@ -461,6 +469,8 @@ def autorate_changed(self, state, name): config.params['auto_ajust'] = state elif name == 'video': config.params['video_autorate'] = state + elif name=='append_video': + config.params['append_video']=state # 隐藏布局及其元素 def hide_show_element(self, wrap_layout, show_status): @@ -502,6 +512,7 @@ def disabled_widget(self, type): self.main.model_type.setDisabled(type) self.main.voice_autorate.setDisabled(type) self.main.video_autorate.setDisabled(type) + self.main.append_video.setDisabled(type) self.main.voice_role.setDisabled(type) self.main.voice_rate.setDisabled(type) self.main.only_video.setDisabled(True if self.main.app_mode in ['tiqu', 'peiyin'] else type) @@ -703,8 +714,10 @@ def check_whisper_model(self, name): def clearcache(self): if config.defaulelang == 'zh': question = tools.show_popup('清理后需要重启软件', '确认进行清理?') + else: question = tools.show_popup('The software needs to be restarted after cleaning', 'Confirm cleanup?') + if question == QMessageBox.Yes: shutil.rmtree(config.TEMP_DIR, ignore_errors=True) shutil.rmtree(config.homedir + "/tmp", ignore_errors=True) @@ -715,6 +728,7 @@ def clearcache(self): # tts类型改变 def tts_type_change(self, type): + self.hide_show_element(self.main.edge_volume_layout,True if type in ['edgeTTS','AzureTTS'] else False) if self.main.app_mode == 'peiyin' and type == 'clone-voice' and config.params['voice_role'] == 'clone': QMessageBox.critical(self.main, config.transobj['anerror'], config.transobj[ 'Clone voice cannot be used in subtitle dubbing mode as there are no replicable voices']) @@ -742,6 +756,7 @@ def tts_type_change(self, type): if type=='gtts': self.main.voice_role.clear() self.main.current_rolelist = ["gtts"] + self.main.voice_role.addItems(self.main.current_rolelist) elif type == "openaiTTS": self.main.voice_role.clear() @@ -764,8 +779,7 @@ def tts_type_change(self, type): self.main.current_rolelist = config.clone_voicelist self.main.voice_role.addItems(self.main.current_rolelist) threading.Thread(target=tools.get_clone_role).start() - config.params['is_separate'] = True - self.main.is_separate.setChecked(True) + elif type == 'TTS-API': self.main.voice_role.clear() self.main.current_rolelist = config.params['ttsapi_voice_role'].strip().split(',') @@ -776,6 +790,8 @@ def tts_type_change(self, type): self.main.current_rolelist = list(rolelist.keys()) if rolelist else ['GPT-SoVITS'] self.main.voice_role.addItems(self.main.current_rolelist) + + # 试听配音 def listen_voice_fun(self): lang = translator.get_code(show_text=self.main.target_language.currentText()) @@ -791,16 +807,21 @@ def listen_voice_fun(self): if not Path(voice_dir).exists(): Path(voice_dir).mkdir(parents=True,exist_ok=True) lujing_role = role.replace('/', '-') - voice_file = f"{voice_dir}/{config.params['tts_type']}-{lang}-{lujing_role}.mp3" + volume=int(self.main.volume_rate.value()) + pitch=int(self.main.pitch_rate.value()) + voice_file = f"{voice_dir}/{config.params['tts_type']}-{lang}-{lujing_role}-{volume}-{pitch}.mp3" if config.params['tts_type'] == 'GPT-SoVITS': voice_file += '.wav' + obj = { "text": text, "rate": "+0%", "role": role, "voice_file": voice_file, "tts_type": config.params['tts_type'], - "language": lang + "language": lang, + "volume":f'+{volume}%' if volume>0 else f'{volume}%', + "pitch":f'+{pitch}Hz' if pitch>0 else f'{pitch}Hz', } if config.params['tts_type'] == 'clone-voice' and role == 'clone': return @@ -954,6 +975,7 @@ def check_mode(self, *, txt=None): config.params['whisper_type'] = 'all' config.params['is_separate'] = False config.params['video_autorate'] = False + config.params['append_video'] = False return True # 如果是 合并模式,必须有字幕,有视频,有字幕嵌入类型,允许设置视频减速 # 不需要翻译 @@ -968,6 +990,7 @@ def check_mode(self, *, txt=None): config.params['voice_rate'] = '+0%' config.params['voice_autorate'] = False config.params['video_autorate'] = False + config.params['append_video'] = False config.params['whisper_model'] = 'tiny' config.params['whisper_type'] = 'all' config.params['back_audio'] = '' @@ -984,10 +1007,12 @@ def check_mode(self, *, txt=None): config.params['voice_rate'] = '+0%' config.params['voice_autorate'] = False config.params['video_autorate'] = False + config.params['append_video'] = False config.params['back_audio'] = '' if self.main.app_mode == 'biaozhun_jd': config.params['voice_autorate'] = True config.params['video_autorate'] = True + config.params['append_video'] = True config.params['auto_ajust'] = True config.params['is_separate'] = False config.params['back_audio'] = '' @@ -1091,6 +1116,7 @@ def check_start(self): # 配音自动加速 config.params['voice_autorate'] = self.main.voice_autorate.isChecked() config.params['video_autorate'] = self.main.video_autorate.isChecked() + config.params['append_video'] = self.main.append_video.isChecked() # 视频自动减速 # 语音模型 @@ -1108,12 +1134,20 @@ def check_start(self): config.params['subtitle_type'] = int(self.main.subtitle_type.currentIndex()) try: - voice_rate = self.main.voice_rate.text().strip().replace('+', '').replace('%', '') - voice_rate = 0 if not voice_rate else int(voice_rate) + voice_rate = int(self.main.voice_rate.value()) + # voice_rate = 0 if not voice_rate else int(voice_rate) config.params['voice_rate'] = f"+{voice_rate}%" if voice_rate >= 0 else f"{voice_rate}%" - except: + except Exception: config.params['voice_rate'] = '+0%' + try: + volume=int(self.main.volume_rate.value()) + pitch=int(self.main.pitch_rate.value()) + config.params['volume']=f'+{volume}%' if volume>0 else f'{volume}%' + config.params['pitch']=f'+{pitch}Hz' if pitch>0 else f'{pitch}Hz' + except Exception: + config.params['volume']='+0%' + config.params['pitch']='+0Hz' config.params['back_audio'] = self.main.back_audio.text().strip() # 字幕区文字 txt = self.main.subtitle_area.toPlainText().strip() @@ -1223,6 +1257,12 @@ def check_start(self): self.main.task = Worker(parent=self.main, app_mode=self.main.app_mode, txt=txt) self.main.task.start() + + for k,v in self.main.moshis.items(): + if k != self.main.app_mode: + v.setDisabled(True) + + # 设置按钮上的日志信息 def set_process_btn_text(self, text, btnkey="", type="logs"): if not btnkey or btnkey not in self.main.processbtns: @@ -1230,10 +1270,17 @@ def set_process_btn_text(self, text, btnkey="", type="logs"): if not self.main.task: return if btnkey=='srt2wav' and self.main.task and self.main.task.video: - jindu = f' {round(self.main.task.video.precent, 1)}% ' - self.main.processbtns[btnkey].progress_bar.setValue(int(self.main.task.video.precent)) - raw_name = self.main.task.video.raw_basename - self.main.processbtns[btnkey].setText(f'{config.transobj["running"].replace("..", "")} [{jindu}] {raw_name} / {config.transobj["endopendir"]} {text}') + if type=='succeed': + text, basename = text.split('##') + self.main.processbtns[btnkey].setTarget(text) + self.main.processbtns[btnkey].setCursor(Qt.PointingHandCursor) + precent=100 + text=f'{config.transobj["endandopen"].replace("..", "")} {text}' + else: + precent=self.main.task.video.precent + text=f'{config.transobj["running"].replace("..", "")} [{round(precent, 1)}%] / {config.transobj["endopendir"]} {text}' + self.main.processbtns[btnkey].progress_bar.setValue(precent) + self.main.processbtns[btnkey].setText(text) return if type == 'succeed': @@ -1258,7 +1305,7 @@ def set_process_btn_text(self, text, btnkey="", type="logs"): elif btnkey in self.main.task.tasklist: jindu = f' {round(self.main.task.tasklist[btnkey].precent, 1)}% ' self.main.processbtns[btnkey].progress_bar.setValue(int(self.main.task.tasklist[btnkey].precent)) - raw_name = self.main.task.tasklist[btnkey].raw_basename + raw_name = self.main.task.tasklist[btnkey].obj['raw_basename'] self.main.processbtns[btnkey].setToolTip(config.transobj["endopendir"]) self.main.processbtns[btnkey].setText( f'{config.transobj["running"].replace("..", "")} [{jindu}] {raw_name} / {config.transobj["endopendir"]} {text}') @@ -1276,6 +1323,8 @@ def update_status(self, type): self.main.startbtn.setText(config.transobj[type]) # 启用 self.disabled_widget(False) + for k, v in self.main.moshis.items(): + v.setDisabled(False) if type == 'end': # 成功完成 self.main.source_mp4.setText(config.transobj["No select videos"]) @@ -1333,6 +1382,8 @@ def update_data(self, json_data): self.main.continue_compos.hide() self.main.target_dir.clear() self.main.stop_djs.hide() + self.main.export_sub.setDisabled(False) + self.main.set_line_role.setDisabled(False) elif d['type'] == 'succeed': # 本次任务结束 self.set_process_btn_text(d['text'], d['btnkey'], 'succeed') @@ -1347,9 +1398,13 @@ def update_data(self, json_data): elif d['type'] == 'disabled_edit': # 禁止修改字幕 self.main.subtitle_area.setReadOnly(True) + self.main.export_sub.setDisabled(True) + self.main.set_line_role.setDisabled(True) elif d['type'] == 'allow_edit': # 允许修改字幕 self.main.subtitle_area.setReadOnly(False) + self.main.export_sub.setDisabled(False) + self.main.set_line_role.setDisabled(False) elif d['type'] == 'replace_subtitle': # 完全替换字幕区 self.main.subtitle_area.clear() @@ -1362,7 +1417,6 @@ def update_data(self, json_data): elif d['type'] == 'show_djs': self.set_process_btn_text(d['text'], d['btnkey']) elif d['type'] == 'check_soft_update': - print(d) if not self.usetype: self.usetype = QPushButton() self.usetype.setStyleSheet('color:#ffff00;border:0') @@ -1401,21 +1455,23 @@ def update_subtitle(self, step="translate_start", btnkey=""): self.main.continue_compos.setDisabled(True) # 如果当前是等待翻译阶段,则更新原语言字幕,然后清空字幕区 txt = self.main.subtitle_area.toPlainText().strip() - if not btnkey: + config.task_countdown = 0 + if not btnkey or not txt: return - srtfile=None if btnkey == 'srt2wav': - srtfile = self.main.task.video.targetdir_target_sub - elif btnkey in self.main.task.tasklist: + srtfile = self.main.task.video.init['target_sub'] + with open(srtfile, 'w', encoding='utf-8') as f: + f.write(txt) + return + + if not self.main.task.is_batch and btnkey in self.main.task.tasklist: if step == 'translate_start': - srtfile = self.main.task.tasklist[btnkey].targetdir_source_sub + srtfile = self.main.task.tasklist[btnkey].init['source_sub'] else: - srtfile = self.main.task.tasklist[btnkey].targetdir_target_sub - # 不是批量才允许更新字幕 - if not self.main.task.is_batch and srtfile and txt: + srtfile = self.main.task.tasklist[btnkey].init['target_sub'] + # 不是批量才允许更新字幕 with open(srtfile, 'w', encoding='utf-8') as f: f.write(txt) if step == 'translate_start': self.main.subtitle_area.clear() - config.task_countdown = 0 return True diff --git a/videotrans/mainwin/spwin.py b/videotrans/mainwin/spwin.py index 94463261..da13cfac 100644 --- a/videotrans/mainwin/spwin.py +++ b/videotrans/mainwin/spwin.py @@ -42,6 +42,7 @@ def __init__(self, parent=None,width=1200,height=700): self.youw = None self.sepw = None self.util = None + self.moshis=None self.app_mode = "biaozhun_jd" self.processbtns = {} @@ -53,8 +54,7 @@ def __init__(self, parent=None,width=1200,height=700): self.setWindowTitle(self.rawtitle) # 检查窗口是否打开 self.initUI() - QTimer.singleShot(100, self.bind_action) - QTimer.singleShot(500, self.start_box) + def start_box(self): # 打开工具箱 @@ -132,11 +132,15 @@ def initUI(self): self.whisper_type.setDisabled(True) if config.params['only_video']: self.only_video.setChecked(True) + try: + self.voice_rate.setValue(int(config.params['voice_rate'].replace('%',''))) + except Exception: + self.voice_rate.setValue(0) - self.voice_rate.setText(config.params['voice_rate']) self.voice_autorate.setChecked(config.params['voice_autorate']) self.video_autorate.setChecked(config.params['video_autorate']) + self.append_video.setChecked(config.params['append_video']) self.auto_ajust.setChecked(config.params['auto_ajust']) if config.params['cuda']: @@ -180,28 +184,23 @@ def initUI(self): self.processlayout = QVBoxLayout(viewport) # 设置布局管理器的对齐方式为顶部对齐 self.processlayout.setAlignment(Qt.AlignTop) + # 渲染后再执行绑定 + QTimer.singleShot(500, self.bind_action) + QTimer.singleShot(500, self.start_box) + def bind_action(self): # 底部状态栏 self.statusLabel = QPushButton(config.transobj["Open Documents"]) self.statusLabel.setCursor(QtCore.Qt.PointingHandCursor) - # self.statusLabel.setStyleSheet("background-color:#455364;color:#ffff00") - self.statusBar.addWidget(self.statusLabel) self.rightbottom = QPushButton(config.transobj['juanzhu']) - # self.rightbottom.setStyleSheet("background-color:#455364;color:#ffffff;border:0") self.rightbottom.setCursor(QtCore.Qt.PointingHandCursor) self.container = QToolBar() self.container.addWidget(self.rightbottom) self.statusBar.addPermanentWidget(self.container) - # 设置QAction的大小 - self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) - # 设置QToolBar的大小,影响其中的QAction的大小 - self.toolBar.setIconSize(QSize(100, 45)) # 设置图标大小 - - def bind_action(self): # 设置角色类型,如果当前是OPENTTS或 coquiTTS则设置,如果是edgeTTS,则为No from videotrans.mainwin.secwin import SecWindow from videotrans.mainwin.subform import Subform @@ -210,8 +209,6 @@ def bind_action(self): if config.params['tts_type'] == 'clone-voice': self.voice_role.addItems(config.clone_voicelist) threading.Thread(target=tools.get_clone_role).start() - config.params['is_separate'] = True - self.is_separate.setChecked(True) elif config.params['tts_type'] == 'TTS-API': self.voice_role.addItems(config.params['ttsapi_voice_role'].strip().split(',')) elif config.params['tts_type'] == 'GPT-SoVITS': @@ -219,8 +216,9 @@ def bind_action(self): self.voice_role.addItems(list(rolelist.keys()) if rolelist else ['GPT-SoVITS']) if config.params['tts_type']: + if config.params['tts_type'] not in ['edgeTTS','AzureTTS']: + self.voice_role.addItems(['No']) self.util.tts_type_change(config.params['tts_type']) - self.voice_role.addItems(['No']) # 设置 tts_type self.tts_type.addItems(config.params['tts_type_list']) @@ -268,11 +266,11 @@ def bind_action(self): self.whisper_model.currentTextChanged.connect(self.util.check_whisper_model) self.model_type.currentTextChanged.connect(self.util.model_type_change) - self.voice_rate.textChanged.connect(self.util.voice_rate_changed) + self.voice_rate.valueChanged.connect(self.util.voice_rate_changed) self.voice_autorate.stateChanged.connect( lambda: self.util.autorate_changed(self.voice_autorate.isChecked(), "voice")) - self.video_autorate.stateChanged.connect( - lambda: self.util.autorate_changed(self.video_autorate.isChecked(), "video")) + self.video_autorate.stateChanged.connect(lambda: self.util.autorate_changed(self.video_autorate.isChecked(), "video")) + self.append_video.stateChanged.connect(lambda: self.util.autorate_changed(self.video_autorate.isChecked(), "append_video")) self.auto_ajust.stateChanged.connect( lambda: self.util.autorate_changed(self.auto_ajust.isChecked(), "auto_ajust")) # tts_type 改变时,重设角色 @@ -281,6 +279,14 @@ def bind_action(self): self.is_separate.toggled.connect(self.util.is_separate_fun) self.enable_cuda.toggled.connect(self.util.check_cuda) + + + # 设置QAction的大小 + self.toolBar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + # 设置QToolBar的大小,影响其中的QAction的大小 + self.toolBar.setIconSize(QSize(100, 45)) # 设置图标大小 + + self.actionbaidu_key.triggered.connect(self.subform.set_baidu_key) self.actionazure_key.triggered.connect(self.subform.set_azure_key) self.actionazure_tts.triggered.connect(self.subform.set_auzuretts_key) @@ -311,12 +317,14 @@ def bind_action(self): self.action_issue.triggered.connect(lambda: self.util.open_url('issue')) self.action_tool.triggered.connect(lambda: self.util.open_toolbox(0, False)) self.actionyoutube.triggered.connect(self.subform.open_youtube) + self.actionsepar.triggered.connect(self.subform.open_separate) self.action_about.triggered.connect(self.util.about) self.action_xinshoujandan.triggered.connect(self.util.set_xinshoujandann) self.action_biaozhun.triggered.connect(self.util.set_biaozhun) + self.action_tiquzimu.triggered.connect(self.util.set_tiquzimu) self.action_zimu_video.triggered.connect(self.util.set_zimu_video) @@ -333,20 +341,25 @@ def bind_action(self): self.util.set_zimu_video() elif self.app_mode == 'peiyin': self.util.set_zimu_peiyin() + self.moshis={ + "biaozhun_jd":self.action_xinshoujandan, + "biaozhun":self.action_biaozhun, + "tiqu":self.action_tiquzimu, + "hebing":self.action_zimu_video, + "peiyin":self.action_zimu_peiyin + } + self.action_yuyinshibie.triggered.connect(lambda: self.util.open_toolbox(0, False)) - self.action_yuyinshibie.triggered.connect(lambda: self.util.open_toolbox(2, False)) - - self.action_yuyinhecheng.triggered.connect(lambda: self.util.open_toolbox(3, False)) + self.action_yuyinhecheng.triggered.connect(lambda: self.util.open_toolbox(1, False)) - self.action_yinshipinfenli.triggered.connect(lambda: self.util.open_toolbox(0, False)) + self.action_yinshipinfenli.triggered.connect(lambda: self.util.open_toolbox(3, False)) - self.action_yingyinhebing.triggered.connect(lambda: self.util.open_toolbox(1, False)) + self.action_yingyinhebing.triggered.connect(lambda: self.util.open_toolbox(4, False)) - self.action_geshi.triggered.connect(lambda: self.util.open_toolbox(4, False)) self.action_hun.triggered.connect(lambda: self.util.open_toolbox(5, False)) - self.action_fanyi.triggered.connect(lambda: self.util.open_toolbox(6, False)) + self.action_fanyi.triggered.connect(lambda: self.util.open_toolbox(2, False)) self.action_clearcache.triggered.connect(self.util.clearcache) # 禁止随意移动sp.exe @@ -371,7 +384,6 @@ def bind_action(self): try: update_role = GetRoleWorker(parent=self) update_role.start() - self.task_logs = LogsWorker(parent=self) self.task_logs.post_logs.connect(self.util.update_data) self.task_logs.start() @@ -409,6 +421,7 @@ def get_setting(self): config.params["voice_role"] = self.settings.value("voice_role", "") config.params["voice_autorate"] = self.settings.value("voice_autorate", False, bool) config.params["video_autorate"] = self.settings.value("video_autorate", False, bool) + config.params["append_video"] = self.settings.value("append_video", False, bool) config.params["auto_ajust"] = self.settings.value("auto_ajust", True, bool) config.params["baidu_miyue"] = self.settings.value("baidu_miyue", "") @@ -456,7 +469,7 @@ def get_setting(self): config.params['translate_type'] = self.settings.value("translate_type", config.params['translate_type']) config.params['subtitle_type'] = self.settings.value("subtitle_type", config.params['subtitle_type'], int) config.proxy = self.settings.value("proxy", "", str) - config.params['voice_rate'] = self.settings.value("voice_rate", config.params['voice_rate'], str) + config.params['voice_rate'] = self.settings.value("voice_rate", config.params['voice_rate'].replace('%','').replace('+',''), str) config.params['cuda'] = self.settings.value("cuda", False, bool) config.params['only_video'] = self.settings.value("only_video", False, bool) config.params['whisper_model'] = self.settings.value("whisper_model", config.params['whisper_model'], str) @@ -476,7 +489,7 @@ def save_setting(self): self.settings.setValue("whisper_model", config.params['whisper_model']) self.settings.setValue("whisper_type", config.params['whisper_type']) self.settings.setValue("model_type", config.params['model_type']) - self.settings.setValue("voice_rate", config.params['voice_rate']) + self.settings.setValue("voice_rate", config.params['voice_rate'].replace('%','').replace('+','')) self.settings.setValue("voice_role", config.params['voice_role']) self.settings.setValue("zh_recogn_api", config.params['zh_recogn_api']) @@ -489,4 +502,5 @@ def save_setting(self): self.settings.setValue("tts_type", config.params['tts_type']) self.settings.setValue("clone_api", config.params['clone_api']) self.settings.setValue("video_autorate", config.params['video_autorate']) + self.settings.setValue("append_video", config.params['append_video']) self.settings.setValue("clone_voicelist", ','.join(config.clone_voicelist)) diff --git a/videotrans/mainwin/subform.py b/videotrans/mainwin/subform.py index 08fec161..46e62086 100644 --- a/videotrans/mainwin/subform.py +++ b/videotrans/mainwin/subform.py @@ -4,7 +4,10 @@ from PySide6 import QtWidgets from PySide6.QtCore import QThread, Signal from PySide6.QtGui import Qt +from PySide6.QtWidgets import QMessageBox, QFileDialog + from videotrans.configure import config +from videotrans.task.separate_worker import SeparateWorker from videotrans.util import tools @@ -226,8 +229,7 @@ def run(self): tools.get_clone_role(True) if len(config.clone_voicelist) < 2: raise Exception('没有可供测试的声音') - get_voice(text=self.text, language=self.language, role=config.clone_voicelist[1], set_p=False,is_test=True, - filename=config.homedir + "/test.mp3") + get_voice(text=self.text, language=self.language, role=config.clone_voicelist[1], set_p=False, filename=config.homedir + "/test.mp3") self.uito.emit("ok") except Exception as e: @@ -369,7 +371,7 @@ def run(self): from videotrans.translator.chatgpt import trans as trans_chatgpt raw = "你好啊我的朋友" if config.defaulelang != 'zh' else "hello,my friend" text = trans_chatgpt(raw, "English" if config.defaulelang != 'zh' else "Chinese", set_p=False, - inst=None, is_test=True) + inst=None) self.uito.emit(f"ok:{raw}\n{text}") except Exception as e: self.uito.emit(str(e)) @@ -455,7 +457,7 @@ def run(self): from videotrans.tts.ttsapi import get_voice try: - get_voice(text=self.text, language=self.language, rate=self.rate, role=self.role, set_p=False, is_test=True, filename=config.homedir + "/test.mp3") + get_voice(text=self.text, language=self.language, rate=self.rate, role=self.role, set_p=False, filename=config.homedir + "/test.mp3") self.uito.emit("ok") except Exception as e: @@ -528,7 +530,7 @@ def __init__(self, *, parent=None, text=None): def run(self): from videotrans.translator.transapi import trans try: - t = trans(self.text, target_language="en", set_p=False, source_code="zh", is_test=True) + t = trans(self.text, target_language="en", set_p=False, source_code="zh") self.uito.emit(f"ok:{self.text}\n{t}") except Exception as e: self.uito.emit(str(e)) @@ -591,7 +593,7 @@ def run(self): from videotrans.tts.gptsovits import get_voice try: get_voice(text=self.text, language=self.language, set_p=False, role=self.role, - filename=config.homedir + "/test.wav",is_test=True) + filename=config.homedir + "/test.wav") self.uito.emit("ok") except Exception as e: self.uito.emit(str(e)) @@ -719,3 +721,71 @@ def save(): self.main.w.azure_template.setPlainText(config.params["azure_template"]) self.main.w.set_azure.clicked.connect(save) self.main.w.show() + + def open_separate(self): + def get_file(): + fname, _ = QFileDialog.getOpenFileName(self.main.sepw, "Select audio or video", config.last_opendir, + "files(*.wav *.mp3 *.aac *.m4a *.flac *.mp4 *.mov *.mkv)") + if fname: + self.main.sepw.fromfile.setText(fname.replace('file:///', '').replace('\\', '/')) + + def update(d): + #更新 + if d=='succeed': + self.main.sepw.set.setText(config.transobj['Separate End/Restart']) + self.main.sepw.fromfile.setText('') + elif d=='end': + self.main.sepw.set.setText(config.transobj['Start Separate']) + else: + QMessageBox.critical(self.main.sepw,config.transobj['anerror'],d) + + + def start(): + if config.separate_status=='ing': + config.separate_status='stop' + self.main.sepw.set.setText(config.transobj['Start Separate']) + return + #开始处理分离,判断是否选择了源文件 + file=self.main.sepw.fromfile.text() + if not file or not os.path.exists(file): + QMessageBox.critical(self.main.sepw, config.transobj['anerror'], config.transobj['must select audio or video file']) + return + self.main.sepw.set.setText(config.transobj['Start Separate...']) + basename=os.path.basename(file) + #判断名称是否正常 + rs,newfile,base=tools.rename_move(file,is_dir=False) + if rs: + file=newfile + basename=base + #创建文件夹 + out=os.path.join(outdir,basename).replace('\\','/') + os.makedirs(out,exist_ok=True) + self.main.sepw.url.setText(out) + #开始分离 + config.separate_status='ing' + self.main.sepw.task=SeparateWorker(parent=self.main.sepw,out=out,file=file,basename=basename) + self.main.sepw.task.finish_event.connect(update) + self.main.sepw.task.start() + + + + from videotrans.component import SeparateForm + try: + if self.main.sepw is not None: + self.main.sepw.show() + return + self.main.sepw = SeparateForm() + self.main.sepw.set.setText(config.transobj['Start Separate']) + outdir = os.path.join(config.homedir,'separate').replace( '\\', '/') + if not os.path.exists(outdir): + os.makedirs(outdir, exist_ok=True) + # 创建事件过滤器实例并将其安装到 lineEdit 上 + self.main.sepw.url.setText(outdir) + + self.main.sepw.selectfile.clicked.connect(get_file) + + self.main.sepw.set.clicked.connect(start) + self.main.sepw.show() + except: + print('err') + pass \ No newline at end of file diff --git a/videotrans/recognition/__init__.py b/videotrans/recognition/__init__.py index e3d64a98..8eb1d83c 100644 --- a/videotrans/recognition/__init__.py +++ b/videotrans/recognition/__init__.py @@ -1,637 +1,86 @@ -# 语音识别 -import json -import os -import re -import shutil -import threading -import time -from datetime import timedelta import torch -from faster_whisper import WhisperModel -from pydub import AudioSegment -from pydub.silence import detect_nonsilent - from videotrans.configure import config -from videotrans.util import tools import logging +from .all import recogn as all_recogn +from .yuxian import recogn as yuxian_recogn +from .avg import recogn as avg_recogn +from .zh import recogn as zh_recogn +from .openai import recogn as openai_recogn +from .google import recogn as google_recogn + logging.basicConfig() logging.getLogger("faster_whisper").setLevel(logging.DEBUG) # 统一入口 -def run(*, type="all", detect_language=None, audio_file=None, cache_folder=None, model_name=None, set_p=True, inst=None, - model_type='faster', is_cuda=None): - if config.exit_soft : - return False - if config.current_status != 'ing' and config.box_recogn != 'ing': +def run(*, + type="all", + detect_language=None, + audio_file=None, + cache_folder=None, + model_name=None, + set_p=True, + inst=None, + model_type='faster', + is_cuda=None + ): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): return False if model_name.startswith('distil-'): model_name = model_name.replace('-whisper', '') if model_type == 'openai': - rs = split_recogn_openai(detect_language=detect_language, audio_file=audio_file, cache_folder=cache_folder, - model_name=model_name, set_p=set_p, inst=inst, is_cuda=is_cuda) + rs = openai_recogn( + detect_language=detect_language, + audio_file=audio_file, + cache_folder=cache_folder, + model_name=model_name, + set_p=set_p, + inst=inst, + is_cuda=is_cuda) elif model_type == 'GoogleSpeech': - rs = google_recogn(detect_language=detect_language, audio_file=audio_file, cache_folder=cache_folder, set_p=set_p, inst=None) - elif model_type=='zh_recogn': - rs = zh_recogn(audio_file=audio_file, cache_folder=cache_folder, set_p=set_p, inst=None) + rs = google_recogn( + detect_language=detect_language, + audio_file=audio_file, + cache_folder=cache_folder, + set_p=set_p, + inst=None) + elif model_type == 'zh_recogn': + rs = zh_recogn( + audio_file=audio_file, + cache_folder=cache_folder, + set_p=set_p, + inst=None) elif type == "all": - rs = all_recogn(detect_language=detect_language, audio_file=audio_file, cache_folder=cache_folder, - model_name=model_name, set_p=set_p, inst=inst, is_cuda=is_cuda) - elif type == 'avg' or os.path.exists(config.rootdir + "/old.txt"): - rs = split_recogn_old(detect_language=detect_language, audio_file=audio_file, cache_folder=cache_folder, - model_name=model_name, set_p=set_p, inst=inst, is_cuda=is_cuda) + rs = all_recogn( + detect_language=detect_language, + audio_file=audio_file, + cache_folder=cache_folder, + model_name=model_name, + set_p=set_p, + inst=inst, + is_cuda=is_cuda) + elif type == 'avg': + rs = avg_recogn( + detect_language=detect_language, + audio_file=audio_file, + cache_folder=cache_folder, + model_name=model_name, + set_p=set_p, + inst=inst, + is_cuda=is_cuda) else: - rs = split_recogn(detect_language=detect_language, audio_file=audio_file, cache_folder=cache_folder, - model_name=model_name, set_p=set_p, inst=inst, is_cuda=is_cuda) + rs = yuxian_recogn( + detect_language=detect_language, + audio_file=audio_file, + cache_folder=cache_folder, + model_name=model_name, + set_p=set_p, + inst=inst, + is_cuda=is_cuda) try: if torch.cuda.is_available(): torch.cuda.empty_cache() except Exception: pass return rs - - -# 整体识别,全部传给模型 -def all_recogn(*, detect_language=None, audio_file=None, cache_folder=None, model_name="base", set_p=True, inst=None, is_cuda=None): - if config.current_status != 'ing' and config.box_recogn != 'ing': - return False - if set_p: - tools.set_process(f"{config.params['whisper_model']} {config.transobj['kaishishibie']}",btnkey=inst.btnkey if inst else "") - down_root = os.path.normpath(config.rootdir + "/models") - model = None - try: - model = WhisperModel(model_name, device="cuda" if is_cuda else "cpu", - compute_type="float32" if model_name.startswith('distil-') else config.settings[ - 'cuda_com_type'], - download_root=down_root, - num_workers=config.settings['whisper_worker'], - cpu_threads=os.cpu_count() if int(config.settings['whisper_threads']) < 1 else int( - config.settings['whisper_threads']), - local_files_only=False) - if config.current_status != 'ing' and config.box_recogn != 'ing': - return False - if not tools.vail_file(audio_file): - raise Exception(f'[error]not exists {audio_file}') - segments, info = model.transcribe(audio_file, - beam_size=config.settings['beam_size'], - best_of=config.settings['best_of'], - condition_on_previous_text=config.settings['condition_on_previous_text'], - - temperature=0 if config.settings['temperature'] == 0 else [0.0, 0.2, 0.4, 0.6, - 0.8, 1.0], - vad_filter=bool(config.settings['vad']), - vad_parameters=dict( - min_silence_duration_ms=config.settings['overall_silence'], - max_speech_duration_s=config.settings['overall_maxsecs'] - ), - word_timestamps=True, - language=detect_language, - initial_prompt=None if detect_language != 'zh' else config.settings[ - 'initial_prompt_zh']) - - # 保留原始语言的字幕 - raw_subtitles = [] - sidx = -1 - for segment in segments: - if config.exit_soft : - del model - return False - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - return None - sidx += 1 - start = int(segment.words[0].start * 1000) - end = int(segment.words[-1].end * 1000) - # if start == end: - # end += 200 - startTime = tools.ms_to_time_string(ms=start) - endTime = tools.ms_to_time_string(ms=end) - text = segment.text.strip().replace(''', "'") - if detect_language == 'zh' and text == config.settings['initial_prompt_zh']: - continue - text = re.sub(r'&#\d+;', '', text) - # 无有效字符 - if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text) or len(text) <= 1: - continue - # 原语言字幕 - s = {"line": len(raw_subtitles) + 1, "time": f"{startTime} --> {endTime}", "text": text} - raw_subtitles.append(s) - if set_p: - tools.set_process(f'{s["line"]}\n{startTime} --> {endTime}\n{text}\n\n', 'subtitle') - if inst and inst.precent < 55: - inst.precent += round(segment.end * 0.5 / info.duration, 2) - tools.set_process(f'{config.transobj["zimuhangshu"]} {s["line"]}',btnkey=inst.btnkey if inst else "") - else: - tools.set_process_box(f'{s["line"]}\n{startTime} --> {endTime}\n{text}\n\n', func_name="set_subtitle") - return raw_subtitles - except Exception as e: - raise Exception(f'whole all {str(e)}') - finally: - try: - if model: - del model - except Exception: - pass - - -# -def match_target_amplitude(sound, target_dBFS): - change_in_dBFS = target_dBFS - sound.dBFS - return sound.apply_gain(change_in_dBFS) - - -# split audio by silence -def shorten_voice(normalized_sound, max_interval=60000): - normalized_sound = match_target_amplitude(normalized_sound, -20.0) - nonsilent_data = [] - audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.settings['voice_silence']), - silence_thresh=-20 - 25) - for i, chunk in enumerate(audio_chunks): - start_time, end_time = chunk - n = 0 - while end_time - start_time >= max_interval: - n += 1 - # new_end = start_time + max_interval+buffer - new_end = start_time + max_interval - new_start = start_time - nonsilent_data.append((new_start, new_end, True)) - start_time += max_interval - nonsilent_data.append((start_time, end_time, False)) - return nonsilent_data - - -# 预先分割识别 -def split_recogn(*, detect_language=None, audio_file=None, cache_folder=None, model_name="base", set_p=True, inst=None, - is_cuda=None): - if set_p: - tools.set_process(config.transobj['fengeyinpinshuju'],btnkey=inst.btnkey if inst else "") - if config.current_status != 'ing' and config.box_recogn != 'ing': - return False - noextname = os.path.basename(audio_file) - tmp_path = f'{cache_folder}/{noextname}_tmp' - if not os.path.isdir(tmp_path): - try: - os.makedirs(tmp_path, 0o777, exist_ok=True) - except: - raise Exception(config.transobj["createdirerror"]) - if not tools.vail_file(audio_file): - raise Exception(f'[error]not exists {audio_file}') - normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 - nonslient_file = f'{tmp_path}/detected_voice.json' - if tools.vail_file(nonslient_file): - with open(nonslient_file, 'r') as infile: - nonsilent_data = json.load(infile) - else: - if config.current_status != 'ing' and config.box_recogn != 'ing': - raise Exception("stop") - if inst and inst.precent < 55: - inst.precent += 0.1 - tools.set_process(config.transobj['qiegeshujuhaoshi'],btnkey=inst.btnkey if inst else "") - nonsilent_data = shorten_voice(normalized_sound) - with open(nonslient_file, 'w') as outfile: - json.dump(nonsilent_data, outfile) - - raw_subtitles = [] - total_length = len(nonsilent_data) - model = None - try: - model = WhisperModel(model_name, device="cuda" if is_cuda else "cpu", - compute_type="float32" if model_name.startswith('distil-') else config.settings[ - 'cuda_com_type'], - download_root=config.rootdir + "/models", - local_files_only=True) - except Exception as e: - raise Exception(str(e.args)) - for i, duration in enumerate(nonsilent_data): - if config.exit_soft : - del model - return False - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - return None - start_time, end_time, buffered = duration - - chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" - audio_chunk = normalized_sound[start_time:end_time] - audio_chunk.export(chunk_filename, format="wav") - - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - raise Exception("stop") - text = "" - try: - segments, _ = model.transcribe(chunk_filename, - beam_size=config.settings['beam_size'], - best_of=config.settings['best_of'], - condition_on_previous_text=config.settings['condition_on_previous_text'], - temperature=0 if config.settings['temperature'] == 0 else [0.0, 0.2, 0.4, - 0.6, 0.8, 1.0], - vad_filter=bool(config.settings['vad']), - vad_parameters=dict( - min_silence_duration_ms=config.settings['overall_silence'], - max_speech_duration_s=config.settings['overall_maxsecs'] - ), - word_timestamps=True, - language=detect_language, - initial_prompt=None if detect_language != 'zh' else config.settings[ - 'initial_prompt_zh'], ) - for t in segments: - - if detect_language == 'zh' and t.text == config.settings['initial_prompt_zh']: - continue - start_time, end_time, buffered = duration - text = t.text - text = f"{text.capitalize()}. ".replace(''', "'") - text = re.sub(r'&#\d+;', '', text).strip().strip('.') - if detect_language == 'zh' and text == config.settings['initial_prompt_zh']: - continue - if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): - continue - end_time = start_time + t.words[-1].end * 1000 - start_time += t.words[0].start * 1000 - start = timedelta(milliseconds=start_time) - stmp = str(start).split('.') - if len(stmp) == 2: - start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' - end = timedelta(milliseconds=end_time) - etmp = str(end).split('.') - if len(etmp) == 2: - end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' - srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} - raw_subtitles.append(srt_line) - if set_p: - if inst and inst.precent < 55: - inst.precent += 0.1 - tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}",btnkey=inst.btnkey if inst else "") - msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" - tools.set_process(msg, 'subtitle') - else: - tools.set_process_box(f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", - func_name="set_subtitle") - except Exception as e: - del model - raise Exception(str(e.args)) - - if set_p: - tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs',btnkey=inst.btnkey if inst else "") - # 写入原语言字幕到目标文件夹 - return raw_subtitles - - -# split audio by silence -def shorten_voice_old(normalized_sound): - normalized_sound = match_target_amplitude(normalized_sound, -20.0) - max_interval = config.settings['interval_split'] * 1000 - buffer = int(config.settings['voice_silence']) - nonsilent_data = [] - audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.settings['voice_silence']), - silence_thresh=-20 - 25) - # print(audio_chunks) - for i, chunk in enumerate(audio_chunks): - - start_time, end_time = chunk - n = 0 - while end_time - start_time >= max_interval: - n += 1 - # new_end = start_time + max_interval+buffer - new_end = start_time + max_interval + buffer - new_start = start_time - nonsilent_data.append((new_start, new_end, True)) - start_time += max_interval - nonsilent_data.append((start_time, end_time, False)) - return nonsilent_data - - -# openai -def split_recogn_openai(*, detect_language=None, audio_file=None, cache_folder=None, model_name="base", set_p=True, - inst=None, is_cuda=None): - import whisper - if set_p: - tools.set_process(config.transobj['fengeyinpinshuju'],btnkey=inst.btnkey if inst else "") - if config.current_status != 'ing' and config.box_recogn != 'ing': - return False - noextname = os.path.basename(audio_file) - tmp_path = f'{cache_folder}/{noextname}_tmp' - if not os.path.isdir(tmp_path): - try: - os.makedirs(tmp_path, 0o777, exist_ok=True) - except: - raise Exception(config.transobj["createdirerror"]) - if not tools.vail_file(audio_file): - raise Exception(f'[error]not exists {audio_file}') - normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 - nonslient_file = f'{tmp_path}/detected_voice.json' - if tools.vail_file(nonslient_file): - with open(nonslient_file, 'r') as infile: - nonsilent_data = json.load(infile) - else: - if config.current_status != 'ing' and config.box_recogn != 'ing': - raise Exception("stop") - if inst and inst.precent < 55: - inst.precent += 0.1 - tools.set_process(config.transobj['qiegeshujuhaoshi'],btnkey=inst.btnkey if inst else "") - nonsilent_data = shorten_voice_old(normalized_sound) - with open(nonslient_file, 'w') as outfile: - json.dump(nonsilent_data, outfile) - - raw_subtitles = [] - total_length = len(nonsilent_data) - model = None - try: - model = whisper.load_model(model_name, - device="cuda" if is_cuda else "cpu", - download_root=config.rootdir + "/models" - ) - except Exception as e: - raise Exception(str(e.args)) - for i, duration in enumerate(nonsilent_data): - if config.exit_soft : - del model - return False - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - raise Exception("stop") - start_time, end_time, buffered = duration - if start_time == end_time: - end_time += int(config.settings['voice_silence']) - chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" - audio_chunk = normalized_sound[start_time:end_time] - audio_chunk.export(chunk_filename, format="wav") - - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - raise Exception("stop") - text = "" - try: - tr = model.transcribe(chunk_filename, - language=detect_language, - initial_prompt=None if detect_language != 'zh' else config.settings[ - 'initial_prompt_zh'], - condition_on_previous_text=config.settings['condition_on_previous_text'] - ) - for t in tr['segments']: - if detect_language == 'zh' and t['text'].strip() == config.settings['initial_prompt_zh']: - continue - text += t['text'] + " " - except Exception as e: - del model - raise Exception(str(e.args)) - - text = f"{text.capitalize()}. ".replace(''', "'") - text = re.sub(r'&#\d+;', '', text).strip() - if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): - continue - start = timedelta(milliseconds=start_time) - stmp = str(start).split('.') - if len(stmp) == 2: - start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' - end = timedelta(milliseconds=end_time) - etmp = str(end).split('.') - if len(etmp) == 2: - end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' - srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} - raw_subtitles.append(srt_line) - if set_p: - if inst and inst.precent < 55: - inst.precent += round(srt_line['line'] * 5 / total_length, 2) - tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}/{total_length}",btnkey=inst.btnkey if inst else "") - msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" - tools.set_process(msg, 'subtitle') - else: - tools.set_process_box(f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", - func_name="set_subtitle") - if set_p: - tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs',btnkey=inst.btnkey if inst else "") - # 写入原语言字幕到目标文件夹 - return raw_subtitles - - -# 均等分割识别 -def split_recogn_old(*, detect_language=None, audio_file=None, cache_folder=None, model_name="base", set_p=True, - inst=None, is_cuda=None): - if set_p: - tools.set_process(config.transobj['fengeyinpinshuju'],btnkey=inst.btnkey if inst else "") - if config.current_status != 'ing' and config.box_recogn != 'ing': - return False - noextname = os.path.basename(audio_file) - tmp_path = f'{cache_folder}/{noextname}_tmp' - if not os.path.isdir(tmp_path): - try: - os.makedirs(tmp_path, 0o777, exist_ok=True) - except: - raise Exception(config.transobj["createdirerror"]) - if not tools.vail_file(audio_file): - raise Exception(f'[error]not exists {audio_file}') - normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 - nonslient_file = f'{tmp_path}/detected_voice.json' - if tools.vail_file(nonslient_file): - with open(nonslient_file, 'r') as infile: - nonsilent_data = json.load(infile) - else: - if config.current_status != 'ing' and config.box_recogn != 'ing': - raise Exception("stop") - nonsilent_data = shorten_voice_old(normalized_sound) - with open(nonslient_file, 'w') as outfile: - json.dump(nonsilent_data, outfile) - - raw_subtitles = [] - total_length = len(nonsilent_data) - start_t = time.time() - try: - model = WhisperModel(model_name, device="cuda" if config.params['cuda'] else "cpu", - compute_type="float32" if model_name.startswith('distil-') else config.settings[ - 'cuda_com_type'], - download_root=config.rootdir + "/models", - local_files_only=True) - except Exception as e: - raise Exception(str(e.args)) - for i, duration in enumerate(nonsilent_data): - if config.exit_soft: - return False - # config.temp = {} - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - raise Exception("stop") - start_time, end_time, buffered = duration - if start_time == end_time: - end_time += int(config.settings['voice_silence']) - - chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" - audio_chunk = normalized_sound[start_time:end_time] - audio_chunk.export(chunk_filename, format="wav") - - if config.current_status != 'ing' and config.box_recogn != 'ing': - del model - raise Exception("stop") - text = "" - try: - segments, _ = model.transcribe(chunk_filename, - beam_size=5, - best_of=5, - condition_on_previous_text=True, - language=detect_language, - initial_prompt=None if detect_language != 'zh' else config.settings[ - 'initial_prompt_zh'], ) - for t in segments: - text += t.text + " " - except Exception as e: - del model - raise Exception(str(e.args)) - - text = f"{text.capitalize()}. ".replace(''', "'") - text = re.sub(r'&#\d+;', '', text).strip() - if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): - continue - start = timedelta(milliseconds=start_time) - stmp = str(start).split('.') - if len(stmp) == 2: - start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' - end = timedelta(milliseconds=end_time) - etmp = str(end).split('.') - if len(etmp) == 2: - end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' - srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} - raw_subtitles.append(srt_line) - if set_p: - if inst and inst.precent < 55: - inst.precent += round(srt_line['line'] * 5 / total_length, 2) - tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}/{total_length}",btnkey=inst.btnkey if inst else "") - msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" - tools.set_process(msg, 'subtitle') - else: - tools.set_process_box(f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", - func_name="set_subtitle") - if set_p: - tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs',btnkey=inst.btnkey if inst else "") - # 写入原语言字幕到目标文件夹 - return raw_subtitles - - -def google_recogn(*, detect_language=None, audio_file=None, cache_folder=None, set_p=True, inst=None): - if set_p: - tools.set_process(config.transobj['fengeyinpinshuju'],btnkey=inst.btnkey if inst else "") - if config.current_status != 'ing' and config.box_recogn != 'ing': - return False - proxy = tools.set_proxy() - if proxy: - os.environ['http_proxy'] = proxy - os.environ['https_proxy'] = proxy - noextname = os.path.basename(audio_file) - tmp_path = f'{cache_folder}/{noextname}_tmp' - if not os.path.isdir(tmp_path): - try: - os.makedirs(tmp_path, 0o777, exist_ok=True) - except: - raise Exception(config.transobj["createdirerror"]) - if not tools.vail_file(audio_file): - raise Exception(f'[error]not exists {audio_file}') - normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 - nonslient_file = f'{tmp_path}/detected_voice.json' - if tools.vail_file(nonslient_file): - with open(nonslient_file, 'r') as infile: - nonsilent_data = json.load(infile) - else: - if config.current_status != 'ing' and config.box_recogn != 'ing': - raise Exception("stop") - nonsilent_data = shorten_voice_old(normalized_sound) - with open(nonslient_file, 'w') as outfile: - json.dump(nonsilent_data, outfile) - - raw_subtitles = [] - total_length = len(nonsilent_data) - start_t = time.time() - - import speech_recognition as sr - try: - recognizer = sr.Recognizer() - except Exception as e: - raise Exception(f'使用Google识别需要设置代理') - - for i, duration in enumerate(nonsilent_data): - if config.exit_soft : - return False - # config.temp = {} - if config.current_status != 'ing' and config.box_recogn != 'ing': - raise Exception("stop") - start_time, end_time, buffered = duration - if start_time == end_time: - end_time += int(config.settings['voice_silence']) - - chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" - audio_chunk = normalized_sound[start_time:end_time] - audio_chunk.export(chunk_filename, format="wav") - - if config.current_status != 'ing' and config.box_recogn != 'ing': - raise Exception("stop") - text = "" - try: - with sr.AudioFile(chunk_filename) as source: - # Record the audio data - audio_data = recognizer.record(source) - try: - # Recognize the speech - text = recognizer.recognize_google(audio_data, language=detect_language) - except sr.UnknownValueError: - text = "" - print("Speech recognition could not understand the audio.") - except sr.RequestError as e: - raise Exception(f"Google识别出错,请检查代理是否正确:{e}") - except Exception as e: - raise Exception('Google识别出错:' + str(e.args)) - - text = f"{text.capitalize()}. ".replace(''', "'") - text = re.sub(r'&#\d+;', '', text).strip() - if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): - continue - start = timedelta(milliseconds=start_time) - stmp = str(start).split('.') - if len(stmp) == 2: - start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' - end = timedelta(milliseconds=end_time) - etmp = str(end).split('.') - if len(etmp) == 2: - end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' - srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} - raw_subtitles.append(srt_line) - if set_p: - if inst and inst.precent < 55: - inst.precent += round(srt_line['line'] * 5 / total_length, 2) - tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}/{total_length}",btnkey=inst.btnkey if inst else "") - msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" - tools.set_process(msg, 'subtitle') - else: - tools.set_process_box(f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", - func_name="set_subtitle") - if set_p: - tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs',btnkey=inst.btnkey if inst else "") - # 写入原语言字幕到目标文件夹 - return raw_subtitles - - - -# zh_recogn 识别 -def zh_recogn(audio_file=None, cache_folder=None, set_p=None, inst=None): - api_url=config.params['zh_recogn_api'].strip().rstrip('/').lower() - if not api_url: - raise Exception('必须填写地址') - if not api_url.startswith('http'): - api_url=f'http://{api_url}' - if not api_url.endswith('/api'): - api_url+='/api' - files={"audio":open(audio_file,'rb')} - import requests - if set_p: - tools.set_process(f"识别可能较久,请耐心等待,进度可查看zh_recogn终端", 'logs',btnkey=inst.btnkey if inst else "") - try: - res=requests.post(f"{api_url}",files=files,proxies={"http":"","https":""},timeout=3600) - config.logger.info(f'zh_recogn:{res=}') - except Exception as e: - raise Exception(e) - else: - res = res.json() - if "code" not in res or res['code'] != 0: - raise Exception(f'{res["msg"]}') - if "data" not in res or len(res['data'])<1: - raise Exception('识别出错') - return res['data'] \ No newline at end of file diff --git a/videotrans/recognition/all.py b/videotrans/recognition/all.py new file mode 100644 index 00000000..8fbbb0ba --- /dev/null +++ b/videotrans/recognition/all.py @@ -0,0 +1,97 @@ +# 整体识别,全部传给模型 +import os +import re + +from videotrans.configure import config +from videotrans.util import tools +from faster_whisper import WhisperModel + + +def recogn(*, + detect_language=None, + audio_file=None, + cache_folder=None, + model_name="base", + set_p=True, + inst=None, + is_cuda=None): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + if set_p: + tools.set_process(f"{config.transobj['kaishishibie']}", + btnkey=inst.init['btnkey'] if inst else "") + down_root = os.path.normpath(config.rootdir + "/models") + model = None + try: + model = WhisperModel(model_name, + device="cuda" if is_cuda else "cpu", + compute_type="float32" if model_name.startswith('distil-') else config.settings[ + 'cuda_com_type'], + download_root=down_root, + num_workers=config.settings['whisper_worker'], + cpu_threads=os.cpu_count() if int(config.settings['whisper_threads']) < 1 else int( + config.settings['whisper_threads']), + local_files_only=True) + if config.current_status != 'ing' and config.box_recogn != 'ing': + return False + if not tools.vail_file(audio_file): + raise Exception(f'no exists {audio_file}') + segments, info = model.transcribe(audio_file, + beam_size=config.settings['beam_size'], + best_of=config.settings['best_of'], + condition_on_previous_text=config.settings['condition_on_previous_text'], + + temperature=0 if config.settings['temperature'] == 0 else [0.0, 0.2, 0.4, 0.6, + 0.8, 1.0], + vad_filter=bool(config.settings['vad']), + vad_parameters=dict( + min_silence_duration_ms=config.settings['overall_silence'], + max_speech_duration_s=config.settings['overall_maxsecs'] + ), + word_timestamps=True, + language=detect_language, + initial_prompt=None if detect_language != 'zh' else config.settings[ + 'initial_prompt_zh']) + + # 保留原始语言的字幕 + raw_subtitles = [] + sidx = -1 + for segment in segments: + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + del model + return None + sidx += 1 + start = int(segment.words[0].start * 1000) + end = int(segment.words[-1].end * 1000) + # if start == end: + # end += 200 + startTime = tools.ms_to_time_string(ms=start) + endTime = tools.ms_to_time_string(ms=end) + text = segment.text.strip().replace(''', "'") + if detect_language == 'zh' and text == config.settings['initial_prompt_zh']: + continue + text = re.sub(r'&#\d+;', '', text) + # 无有效字符 + if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text) or len(text) <= 1: + continue + # 原语言字幕 + s = {"line": len(raw_subtitles) + 1, "time": f"{startTime} --> {endTime}", "text": text} + raw_subtitles.append(s) + if set_p: + tools.set_process(f'{s["line"]}\n{startTime} --> {endTime}\n{text}\n\n', 'subtitle') + if inst and inst.precent < 55: + inst.precent += round(segment.end * 0.5 / info.duration, 2) + tools.set_process(f'{config.transobj["zimuhangshu"]} {s["line"]}', + btnkey=inst.init['btnkey'] if inst else "") + else: + tools.set_process_box(text=f'{s["line"]}\n{startTime} --> {endTime}\n{text}\n\n', type="set", + func_name="shibie") + return raw_subtitles + except Exception as e: + raise Exception(str(e)+str(e.args)) + finally: + try: + if model: + del model + except Exception: + pass diff --git a/videotrans/recognition/avg.py b/videotrans/recognition/avg.py new file mode 100644 index 00000000..97cb4c8c --- /dev/null +++ b/videotrans/recognition/avg.py @@ -0,0 +1,133 @@ +# 均等分割识别 +import json +import os +import re +import time +from datetime import timedelta + +from faster_whisper import WhisperModel +from pydub import AudioSegment +from pydub.silence import detect_nonsilent + +from videotrans.configure import config +from videotrans.util import tools + + +# split audio by silence +def shorten_voice_old(normalized_sound): + normalized_sound = tools.match_target_amplitude(normalized_sound, -20.0) + max_interval = config.settings['interval_split'] * 1000 + buffer = int(config.settings['voice_silence']) + nonsilent_data = [] + audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.settings['voice_silence']), + silence_thresh=-20 - 25) + # print(audio_chunks) + for i, chunk in enumerate(audio_chunks): + + start_time, end_time = chunk + n = 0 + while end_time - start_time >= max_interval: + n += 1 + # new_end = start_time + max_interval+buffer + new_end = start_time + max_interval + buffer + new_start = start_time + nonsilent_data.append((new_start, new_end, True)) + start_time += max_interval + nonsilent_data.append((start_time, end_time, False)) + return nonsilent_data + + +def recogn(*, + detect_language=None, + audio_file=None, + cache_folder=None, + model_name="base", + set_p=True, + inst=None, + is_cuda=None): + if set_p: + tools.set_process(config.transobj['fengeyinpinshuju'], btnkey=inst.init['btnkey'] if inst else "") + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + noextname = os.path.basename(audio_file) + tmp_path = f'{cache_folder}/{noextname}_tmp' + if not os.path.isdir(tmp_path): + try: + os.makedirs(tmp_path, 0o777, exist_ok=True) + except: + raise Exception(config.transobj["createdirerror"]) + if not tools.vail_file(audio_file): + raise Exception(f'[error]not exists {audio_file}') + normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 + nonslient_file = f'{tmp_path}/detected_voice.json' + if tools.vail_file(nonslient_file): + with open(nonslient_file, 'r') as infile: + nonsilent_data = json.load(infile) + else: + nonsilent_data = shorten_voice_old(normalized_sound) + with open(nonslient_file, 'w') as outfile: + json.dump(nonsilent_data, outfile) + + raw_subtitles = [] + total_length = len(nonsilent_data) + start_t = time.time() + model = WhisperModel( + model_name, + device="cuda" if config.params['cuda'] else "cpu", + compute_type="float32" if model_name.startswith('distil-') else config.settings['cuda_com_type'], + download_root=config.rootdir + "/models", + local_files_only=True) + for i, duration in enumerate(nonsilent_data): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + del model + return False + start_time, end_time, buffered = duration + if start_time == end_time: + end_time += int(config.settings['voice_silence']) + + chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" + audio_chunk = normalized_sound[start_time:end_time] + audio_chunk.export(chunk_filename, format="wav") + + + text = "" + try: + segments, _ = model.transcribe(chunk_filename, + beam_size=5, + best_of=5, + condition_on_previous_text=True, + language=detect_language, + initial_prompt=None if detect_language != 'zh' else config.settings['initial_prompt_zh'], ) + for t in segments: + text += t.text + " " + except Exception as e: + del model + raise Exception(str(e.args)+str(e)) + + text = f"{text.capitalize()}. ".replace(''', "'") + text = re.sub(r'&#\d+;', '', text).strip() + if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): + continue + start = timedelta(milliseconds=start_time) + stmp = str(start).split('.') + if len(stmp) == 2: + start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' + end = timedelta(milliseconds=end_time) + etmp = str(end).split('.') + if len(etmp) == 2: + end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' + srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} + raw_subtitles.append(srt_line) + if set_p: + if inst and inst.precent < 55: + inst.precent += round(srt_line['line'] * 5 / total_length, 2) + tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}/{total_length}", + btnkey=inst.init['btnkey'] if inst else "") + msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" + tools.set_process(msg, 'subtitle') + else: + tools.set_process_box(text=f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", type="set", func_name="shibie") + if set_p: + tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs', btnkey=inst.init['btnkey'] if inst else "") + # 写入原语言字幕到目标文件夹 + return raw_subtitles diff --git a/videotrans/recognition/google.py b/videotrans/recognition/google.py new file mode 100644 index 00000000..0ee69a73 --- /dev/null +++ b/videotrans/recognition/google.py @@ -0,0 +1,136 @@ +import json +import os +import re +import time +from datetime import timedelta + +from pydub import AudioSegment +from pydub.silence import detect_nonsilent + +from videotrans.configure import config +from videotrans.util import tools + + +# split audio by silence +def shorten_voice_old(normalized_sound): + normalized_sound = tools.match_target_amplitude(normalized_sound, -20.0) + max_interval = config.settings['interval_split'] * 1000 + buffer = int(config.settings['voice_silence']) + nonsilent_data = [] + audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.settings['voice_silence']), + silence_thresh=-20 - 25) + # print(audio_chunks) + for i, chunk in enumerate(audio_chunks): + + start_time, end_time = chunk + n = 0 + while end_time - start_time >= max_interval: + n += 1 + # new_end = start_time + max_interval+buffer + new_end = start_time + max_interval + buffer + new_start = start_time + nonsilent_data.append((new_start, new_end, True)) + start_time += max_interval + nonsilent_data.append((start_time, end_time, False)) + return nonsilent_data + + +def recogn(*, + detect_language=None, + audio_file=None, + cache_folder=None, + set_p=True, + inst=None): + if set_p: + tools.set_process(config.transobj['fengeyinpinshuju'], btnkey=inst.init['btnkey'] if inst else "") + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + proxy = tools.set_proxy() + if proxy: + os.environ['http_proxy'] = proxy + os.environ['https_proxy'] = proxy + noextname = os.path.basename(audio_file) + tmp_path = f'{cache_folder}/{noextname}_tmp' + if not os.path.isdir(tmp_path): + try: + os.makedirs(tmp_path, 0o777, exist_ok=True) + except: + raise Exception(config.transobj["createdirerror"]) + if not tools.vail_file(audio_file): + raise Exception(f'[error]not exists {audio_file}') + normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 + nonslient_file = f'{tmp_path}/detected_voice.json' + if tools.vail_file(nonslient_file): + with open(nonslient_file, 'r') as infile: + nonsilent_data = json.load(infile) + else: + nonsilent_data = shorten_voice_old(normalized_sound) + with open(nonslient_file, 'w') as outfile: + json.dump(nonsilent_data, outfile) + + raw_subtitles = [] + total_length = len(nonsilent_data) + start_t = time.time() + + import speech_recognition as sr + try: + recognizer = sr.Recognizer() + except Exception as e: + raise Exception(f'使用Google识别需要设置代理') + + for i, duration in enumerate(nonsilent_data): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + start_time, end_time, buffered = duration + if start_time == end_time: + end_time += int(config.settings['voice_silence']) + + chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" + audio_chunk = normalized_sound[start_time:end_time] + audio_chunk.export(chunk_filename, format="wav") + + text = "" + try: + with sr.AudioFile(chunk_filename) as source: + # Record the audio data + audio_data = recognizer.record(source) + try: + # Recognize the speech + text = recognizer.recognize_google(audio_data, language=detect_language) + except sr.UnknownValueError: + text = "" + print("Speech recognition could not understand the audio.") + except sr.RequestError as e: + raise Exception(f"Google识别出错,请检查代理是否正确:{e}") + except Exception as e: + raise Exception('Google识别出错:' + str(e.args)) + + text = f"{text.capitalize()}. ".replace(''', "'") + text = re.sub(r'&#\d+;', '', text).strip() + if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): + continue + start = timedelta(milliseconds=start_time) + stmp = str(start).split('.') + if len(stmp) == 2: + start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' + end = timedelta(milliseconds=end_time) + etmp = str(end).split('.') + if len(etmp) == 2: + end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' + srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} + raw_subtitles.append(srt_line) + if set_p: + if inst and inst.precent < 55: + inst.precent += round(srt_line['line'] * 5 / total_length, 2) + tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}/{total_length}", + btnkey=inst.init['btnkey'] if inst else "") + msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" + tools.set_process(msg, 'subtitle') + else: + tools.set_process_box(text=f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", type="set", + func_name="shibie") + if set_p: + tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs', + btnkey=inst.init['btnkey'] if inst else "") + # 写入原语言字幕到目标文件夹 + return raw_subtitles diff --git a/videotrans/recognition/openai.py b/videotrans/recognition/openai.py new file mode 100644 index 00000000..ab21dfd0 --- /dev/null +++ b/videotrans/recognition/openai.py @@ -0,0 +1,129 @@ +# openai +import json +import os +import re +from datetime import timedelta + +from pydub import AudioSegment +from pydub.silence import detect_nonsilent + +from videotrans.configure import config +from videotrans.util import tools +import whisper + +# split audio by silence +def shorten_voice_old(normalized_sound): + normalized_sound = tools.match_target_amplitude(normalized_sound, -20.0) + max_interval = config.settings['interval_split'] * 1000 + buffer = int(config.settings['voice_silence']) + nonsilent_data = [] + audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.settings['voice_silence']),silence_thresh=-20 - 25) + # print(audio_chunks) + for i, chunk in enumerate(audio_chunks): + start_time, end_time = chunk + n = 0 + while end_time - start_time >= max_interval: + n += 1 + # new_end = start_time + max_interval+buffer + new_end = start_time + max_interval + buffer + new_start = start_time + nonsilent_data.append((new_start, new_end, True)) + start_time += max_interval + nonsilent_data.append((start_time, end_time, False)) + return nonsilent_data + + +def recogn(*, + detect_language=None, + audio_file=None, + cache_folder=None, + model_name="base", + set_p=True, + inst=None, + is_cuda=None): + if set_p: + tools.set_process(config.transobj['fengeyinpinshuju'], btnkey=inst.init['btnkey'] if inst else "") + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + noextname = os.path.basename(audio_file) + tmp_path = f'{cache_folder}/{noextname}_tmp' + if not os.path.isdir(tmp_path): + try: + os.makedirs(tmp_path, 0o777, exist_ok=True) + except: + raise Exception(config.transobj["createdirerror"]) + if not tools.vail_file(audio_file): + raise Exception(f'[error]not exists {audio_file}') + normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 + nonslient_file = f'{tmp_path}/detected_voice.json' + if tools.vail_file(nonslient_file): + with open(nonslient_file, 'r') as infile: + nonsilent_data = json.load(infile) + else: + if config.current_status != 'ing' and config.box_recogn != 'ing': + return + if inst and inst.precent < 55: + inst.precent += 0.1 + tools.set_process(config.transobj['qiegeshujuhaoshi'], btnkey=inst.init['btnkey'] if inst else "") + nonsilent_data = shorten_voice_old(normalized_sound) + with open(nonslient_file, 'w') as outfile: + json.dump(nonsilent_data, outfile) + + raw_subtitles = [] + total_length = len(nonsilent_data) + model = whisper.load_model( + model_name, + device="cuda" if is_cuda else "cpu", + download_root=config.rootdir + "/models" + ) + for i, duration in enumerate(nonsilent_data): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + del model + return False + start_time, end_time, buffered = duration + if start_time == end_time: + end_time += int(config.settings['voice_silence']) + chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" + audio_chunk = normalized_sound[start_time:end_time] + audio_chunk.export(chunk_filename, format="wav") + + text = "" + try: + tr = model.transcribe(chunk_filename, + language=detect_language, + initial_prompt=None if detect_language != 'zh' else config.settings['initial_prompt_zh'], + condition_on_previous_text=config.settings['condition_on_previous_text'] + ) + for t in tr['segments']: + if detect_language == 'zh' and t['text'].strip() == config.settings['initial_prompt_zh']: + continue + text += t['text'] + " " + except Exception as e: + del model + raise Exception(str(e.args)+str(e)) + text = f"{text.capitalize()}. ".replace(''', "'") + text = re.sub(r'&#\d+;', '', text).strip() + if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): + continue + start = timedelta(milliseconds=start_time) + stmp = str(start).split('.') + if len(stmp) == 2: + start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' + end = timedelta(milliseconds=end_time) + etmp = str(end).split('.') + if len(etmp) == 2: + end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' + srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} + raw_subtitles.append(srt_line) + if set_p: + if inst and inst.precent < 55: + inst.precent += round(srt_line['line'] * 5 / total_length, 2) + tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}/{total_length}", btnkey=inst.init['btnkey'] if inst else "") + msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" + tools.set_process(msg, 'subtitle') + else: + tools.set_process_box(text=f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", type="set", func_name="shibie") + if set_p: + tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs',btnkey=inst.init['btnkey'] if inst else "") + # 写入原语言字幕到目标文件夹 + return raw_subtitles diff --git a/videotrans/recognition/yuxian.py b/videotrans/recognition/yuxian.py new file mode 100644 index 00000000..e1c4bc27 --- /dev/null +++ b/videotrans/recognition/yuxian.py @@ -0,0 +1,144 @@ +# 预先分割识别 +import json +import os +import re +from datetime import timedelta + +from faster_whisper import WhisperModel +from pydub import AudioSegment +from pydub.silence import detect_nonsilent + +from videotrans.configure import config +from videotrans.util import tools + + +# split audio by silence +def shorten_voice(normalized_sound, max_interval=60000): + normalized_sound = tools.match_target_amplitude(normalized_sound, -20.0) + nonsilent_data = [] + audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.settings['voice_silence']), + silence_thresh=-20 - 25) + for i, chunk in enumerate(audio_chunks): + start_time, end_time = chunk + n = 0 + while end_time - start_time >= max_interval: + n += 1 + # new_end = start_time + max_interval+buffer + new_end = start_time + max_interval + new_start = start_time + nonsilent_data.append((new_start, new_end, True)) + start_time += max_interval + nonsilent_data.append((start_time, end_time, False)) + return nonsilent_data + + +def recogn(*, + detect_language=None, + audio_file=None, + cache_folder=None, + model_name="base", + set_p=True, + inst=None, + is_cuda=None): + if set_p: + tools.set_process(config.transobj['fengeyinpinshuju'], btnkey=inst.init['btnkey'] if inst else "") + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + noextname = os.path.basename(audio_file) + tmp_path = f'{cache_folder}/{noextname}_tmp' + if not os.path.isdir(tmp_path): + try: + os.makedirs(tmp_path, 0o777, exist_ok=True) + except: + raise Exception(config.transobj["createdirerror"]) + if not tools.vail_file(audio_file): + raise Exception(f'[error]not exists {audio_file}') + normalized_sound = AudioSegment.from_wav(audio_file) # -20.0 + nonslient_file = f'{tmp_path}/detected_voice.json' + if tools.vail_file(nonslient_file): + with open(nonslient_file, 'r') as infile: + nonsilent_data = json.load(infile) + else: + if inst and inst.precent < 55: + inst.precent += 0.1 + tools.set_process(config.transobj['qiegeshujuhaoshi'], btnkey=inst.init['btnkey'] if inst else "") + nonsilent_data = shorten_voice(normalized_sound) + with open(nonslient_file, 'w') as outfile: + json.dump(nonsilent_data, outfile) + + raw_subtitles = [] + total_length = len(nonsilent_data) + model = WhisperModel( + model_name, + device="cuda" if is_cuda else "cpu", + compute_type="float32" if model_name.startswith('distil-') else config.settings['cuda_com_type'], + download_root=config.rootdir + "/models", + local_files_only=True) + for i, duration in enumerate(nonsilent_data): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + del model + return False + start_time, end_time, buffered = duration + + chunk_filename = tmp_path + f"/c{i}_{start_time // 1000}_{end_time // 1000}.wav" + audio_chunk = normalized_sound[start_time:end_time] + audio_chunk.export(chunk_filename, format="wav") + + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + del model + return False + text = "" + try: + segments, _ = model.transcribe(chunk_filename, + beam_size=config.settings['beam_size'], + best_of=config.settings['best_of'], + condition_on_previous_text=config.settings['condition_on_previous_text'], + temperature=0 if config.settings['temperature'] == 0 else [0.0, 0.2, 0.4, 0.6, 0.8, 1.0], + vad_filter=bool(config.settings['vad']), + vad_parameters=dict( + min_silence_duration_ms=config.settings['overall_silence'], + max_speech_duration_s=config.settings['overall_maxsecs'] + ), + word_timestamps=True, + language=detect_language, + initial_prompt=None if detect_language != 'zh' else config.settings['initial_prompt_zh'], ) + for t in segments: + if detect_language == 'zh' and t.text == config.settings['initial_prompt_zh']: + continue + start_time, end_time, buffered = duration + text = t.text + text = f"{text.capitalize()}. ".replace(''', "'") + text = re.sub(r'&#\d+;', '', text).strip().strip('.') + if detect_language == 'zh' and text == config.settings['initial_prompt_zh']: + continue + if not text or re.match(r'^[,。、?‘’“”;:({}【】):;"\'\s \d`!@#$%^&*()_+=.,?/\\-]*$', text): + continue + end_time = start_time + t.words[-1].end * 1000 + start_time += t.words[0].start * 1000 + start = timedelta(milliseconds=start_time) + stmp = str(start).split('.') + if len(stmp) == 2: + start = f'{stmp[0]},{int(int(stmp[-1]) / 1000)}' + end = timedelta(milliseconds=end_time) + etmp = str(end).split('.') + if len(etmp) == 2: + end = f'{etmp[0]},{int(int(etmp[-1]) / 1000)}' + srt_line = {"line": len(raw_subtitles) + 1, "time": f"{start} --> {end}", "text": text} + raw_subtitles.append(srt_line) + if set_p: + if inst and inst.precent < 55: + inst.precent += 0.1 + tools.set_process(f"{config.transobj['yuyinshibiejindu']} {srt_line['line']}", + btnkey=inst.init['btnkey'] if inst else "") + msg = f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n" + tools.set_process(msg, 'subtitle') + else: + tools.set_process_box(text=f"{srt_line['line']}\n{srt_line['time']}\n{srt_line['text']}\n\n", func_name="shibie", type="set") + except Exception as e: + del model + raise Exception(str(e.args)+str(e)) + + if set_p: + tools.set_process(f"{config.transobj['yuyinshibiewancheng']} / {len(raw_subtitles)}", 'logs',btnkey=inst.init['btnkey'] if inst else "") + # 写入原语言字幕到目标文件夹 + return raw_subtitles diff --git a/videotrans/recognition/zh.py b/videotrans/recognition/zh.py new file mode 100644 index 00000000..8648b944 --- /dev/null +++ b/videotrans/recognition/zh.py @@ -0,0 +1,35 @@ +# zh_recogn 识别 +from videotrans.configure import config +from videotrans.util import tools + + +def recogn(*, + audio_file=None, + cache_folder=None, + set_p=None, + inst=None): + if config.exit_soft or (config.current_status != 'ing' and config.box_recogn != 'ing'): + return False + api_url = config.params['zh_recogn_api'].strip().rstrip('/').lower() + if not api_url: + raise Exception('必须填写地址') + if not api_url.startswith('http'): + api_url = f'http://{api_url}' + if not api_url.endswith('/api'): + api_url += '/api' + files = {"audio": open(audio_file, 'rb')} + import requests + if set_p: + tools.set_process(f"识别可能较久,请耐心等待,进度可查看zh_recogn终端", 'logs', btnkey=inst.init['btnkey'] if inst else "") + try: + res = requests.post(f"{api_url}", files=files, proxies={"http": "", "https": ""}, timeout=3600) + config.logger.info(f'zh_recogn:{res=}') + except Exception as e: + raise Exception(e) + else: + res = res.json() + if "code" not in res or res['code'] != 0: + raise Exception(f'{res["msg"]}') + if "data" not in res or len(res['data']) < 1: + raise Exception('识别出错') + return res['data'] diff --git a/videotrans/set.ini b/videotrans/set.ini index 2dbd9671..0861329a 100644 --- a/videotrans/set.ini +++ b/videotrans/set.ini @@ -176,10 +176,7 @@ other_len=60 ; used for ffmpeg compatibility, if ffmpeg error, the error contains the word vysnc, can be changed to vsync=vfr vsync=passthrough -;当配音长度大于视频长度时,是否延长视频,true=延长,false=不延长,将截断音频 -;If or not extend the video when the dubbing length is greater than the video length, true=extend, false=don't extend, the audio will be truncated. -append_video=true -;true=批量任务时分为 识别、翻译、配音、合并 多阶段交叉执行,加快速度,false=前面全部完成后才开始下一个 +;true=批量任务时分为 识别、翻译、配音、合并 多阶段交叉执行,加快速度但不可暂停编辑字幕,false=前面全部完成后才开始下一个,可暂停编辑字幕 ;true=The batch task is divided into recognition, translation, dubbing, merging, and multi-stage cross-execution to accelerate the speed, false=The next one starts after all the previous ones are completed. cors_run=true \ No newline at end of file diff --git a/videotrans/styles/style.qss b/videotrans/styles/style.qss index 1cc096da..46049252 100644 --- a/videotrans/styles/style.qss +++ b/videotrans/styles/style.qss @@ -1 +1,7 @@ -QToolBar { margin:0; padding:0; border-image:none;} QToolBar QToolButton {background-color: #32414B; height:30px; margin-bottom:0px; margin-top:5px; margin-left:2px; margin-right:0px; text-align: left;}QToolBar QToolButton:hover {border: 1px solid #148CD2;}QToolBar QToolButton:checked {background-color: #19232D; border: 1px solid #148CD2;}QToolBar QToolButton:checked:hover {border: 1px solid #339933;}QLabel{ color: #bbbbbb;}QLineEdit:hover,QComboBox:hover{ border-color: #148cd2;} QLineEdit,QComboBox{ background-color: #161E26; border-color: #32414B;}QLineEdit:disabled { background-color: transparent; border-color: transparent;}QComboBox:disabled{ background-color: transparent; border-color: #273442;}QScrollArea QPushButton{ background-color: rgba(50, 65, 75, 0.5); border-radius: 0; opacity: 0.1; text-align:left; padding-left:5px;}QScrollArea QPushButton:hover{ background-color: #19232D;}QPlainTextEdit:read-only{ color:#999999;}* {padding: 0px; margin: 0px; border: 0px; border-style: none; border-image: none; outline: 0;}QToolBar > * {margin: 0px; padding: 0px;}QWidget {background-color: #19232D; border: 0px solid #455364; padding: 0px; color: #DFE1E2; selection-background-color: #346792; selection-color: #DFE1E2;}QWidget:disabled {background-color: #19232D; color: #788D9C; selection-background-color: #26486B; selection-color: #788D9C;}QWidget::item:selected {background-color: #346792;}QWidget::item:hover:!selected {background-color: #1A72BB;}QMainWindow::separator {background-color: #455364; border: 0px solid #19232D; spacing: 0px; padding: 2px;}QMainWindow::separator:hover {background-color: #60798B; border: 0px solid #1A72BB;}QMainWindow::separator:horizontal {width: 5px; margin-top: 2px; margin-bottom: 2px; image: url(":/qss_icons/dark/rc/toolbar_separator_vertical.png");}QMainWindow::separator:vertical {height: 5px; margin-left: 2px; margin-right: 2px; image: url(":/qss_icons/dark/rc/toolbar_separator_horizontal.png");}QToolTip {background-color: #346792; color: #DFE1E2; border: none; padding: 0px;}QStatusBar {border: 1px solid #455364; background: #455364;}QStatusBar::item {border: none;}QStatusBar QToolTip {background-color: #1A72BB; border: 1px solid #19232D; color: #19232D; padding: 0px; opacity: 230;}QStatusBar QLabel {background: transparent;}QCheckBox {background-color: #19232D; color: #DFE1E2; spacing: 4px; outline: none; padding-top: 4px; padding-bottom: 4px;}QCheckBox:focus {border: none;}QCheckBox QWidget:disabled {background-color: #19232D; color: #788D9C;}QCheckBox::indicator {margin-left: 2px; height: 14px; width: 14px;}QCheckBox::indicator:unchecked {image: url(":/qss_icons/dark/rc/checkbox_unchecked.png");}QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus, QCheckBox::indicator:unchecked:pressed {image: url(":/qss_icons/dark/rc/checkbox_unchecked_focus.png");}QCheckBox::indicator:unchecked:disabled {image: url(":/qss_icons/dark/rc/checkbox_unchecked_disabled.png");}QCheckBox::indicator:checked {image: url(":/qss_icons/dark/rc/checkbox_checked.png");}QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus, QCheckBox::indicator:checked:pressed { image: url(":/qss_icons/dark/rc/checkbox_checked_focus.png");}QCheckBox::indicator:checked:disabled {image: url(":/qss_icons/dark/rc/checkbox_checked_disabled.png");}QCheckBox::indicator:indeterminate {image: url(":/qss_icons/dark/rc/checkbox_indeterminate.png");}QCheckBox::indicator:indeterminate:disabled {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_disabled.png");}QCheckBox::indicator:indeterminate:focus, QCheckBox::indicator:indeterminate:hover, QCheckBox::indicator:indeterminate:pressed {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_focus.png");}QMenuBar {background-color: #455364; padding: 2px; border: 1px solid #19232D; color: #DFE1E2; selection-background-color: #1A72BB;}QMenuBar:focus {border: 1px solid #346792;}QMenuBar::item {background: transparent; padding: 4px;}QMenuBar::item:selected {padding: 4px; background: transparent; border: 0px solid #455364; background-color: #1A72BB;}QMenuBar::item:pressed {padding: 4px; border: 0px solid #455364; background-color: #1A72BB; color: #DFE1E2; margin-bottom: 0px; padding-bottom: 0px;}QMenu {border: 0px solid #455364; color: #DFE1E2; margin: 0px; background-color: #37414F; selection-background-color: #1A72BB;}QMenu::separator {height: 1px; background-color: #60798B; color: #DFE1E2;}QMenu::item {background-color: #37414F; padding: 4px 24px 4px 28px; border: 1px transparent #455364;}QMenu::item:selected {color: #DFE1E2; background-color: #1A72BB;}QMenu::item:pressed {background-color: #1A72BB;}QMenu::icon {padding-left: 10px; width: 14px; height: 14px;}QMenu::indicator {padding-left: 8px; width: 12px; height: 12px;}QMenu::indicator:non-exclusive:unchecked {image: url(":/qss_icons/dark/rc/checkbox_unchecked.png");}QMenu::indicator:non-exclusive:unchecked:hover, QMenu::indicator:non-exclusive:unchecked:focus, QMenu::indicator:non-exclusive:unchecked:pressed {border: none; image: url(":/qss_icons/dark/rc/checkbox_unchecked_focus.png");}QMenu::indicator:non-exclusive:unchecked:disabled {image: url(":/qss_icons/dark/rc/checkbox_unchecked_disabled.png");}QMenu::indicator:non-exclusive:checked {image: url(":/qss_icons/dark/rc/checkbox_checked.png");}QMenu::indicator:non-exclusive:checked:hover, QMenu::indicator:non-exclusive:checked:focus, QMenu::indicator:non-exclusive:checked:pressed {border: none; image: url(":/qss_icons/dark/rc/checkbox_checked_focus.png");}QMenu::indicator:non-exclusive:checked:disabled {image: url(":/qss_icons/dark/rc/checkbox_checked_disabled.png");}QMenu::indicator:non-exclusive:indeterminate {image: url(":/qss_icons/dark/rc/checkbox_indeterminate.png");}QMenu::indicator:non-exclusive:indeterminate:disabled {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_disabled.png");}QMenu::indicator:non-exclusive:indeterminate:focus, QMenu::indicator:non-exclusive:indeterminate:hover, QMenu::indicator:non-exclusive:indeterminate:pressed {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_focus.png");}QMenu::indicator:exclusive:unchecked {image: url(":/qss_icons/dark/rc/radio_unchecked.png");}QMenu::indicator:exclusive:unchecked:hover, QMenu::indicator:exclusive:unchecked:focus, QMenu::indicator:exclusive:unchecked:pressed {border: none; outline: none; image: url(":/qss_icons/dark/rc/radio_unchecked_focus.png");}QMenu::indicator:exclusive:unchecked:disabled {image: url(":/qss_icons/dark/rc/radio_unchecked_disabled.png");}QMenu::indicator:exclusive:checked {border: none; outline: none; image: url(":/qss_icons/dark/rc/radio_checked.png");}QMenu::indicator:exclusive:checked:hover, QMenu::indicator:exclusive:checked:focus, QMenu::indicator:exclusive:checked:pressed {border: none; outline: none; image: url(":/qss_icons/dark/rc/radio_checked_focus.png");}QMenu::indicator:exclusive:checked:disabled {outline: none; image: url(":/qss_icons/dark/rc/radio_checked_disabled.png");}QMenu::right-arrow {margin: 5px; padding-left: 12px; image: url(":/qss_icons/dark/rc/arrow_right.png"); height: 12px; width: 12px;}QScrollArea QWidget QWidget:disabled {background-color: #19232D;}QScrollBar:horizontal {height: 16px; margin: 2px 16px 2px 16px; border: 1px solid #455364; border-radius: 4px; background-color: #19232D;}QScrollBar:vertical {background-color: #19232D; width: 16px; margin: 16px 2px 16px 2px; border: 1px solid #455364; border-radius: 4px;}QScrollBar::handle:horizontal {background-color: #60798B; border: 1px solid #455364; border-radius: 4px; min-width: 8px;}QScrollBar::handle:horizontal:hover {background-color: #346792; border: #346792; border-radius: 4px; min-width: 8px;}QScrollBar::handle:horizontal:focus {border: 1px solid #1A72BB;}QScrollBar::handle:vertical {background-color: #60798B; border: 1px solid #455364; min-height: 8px; border-radius: 4px;}QScrollBar::handle:vertical:hover {background-color: #346792; border: #346792; border-radius: 4px; min-height: 8px;}QScrollBar::handle:vertical:focus {border: 1px solid #1A72BB;}QScrollBar::add-line:horizontal {margin: 0px 0px 0px 0px; border-image: url(":/qss_icons/dark/rc/arrow_right_disabled.png"); height: 12px; width: 12px; subcontrol-position: right; subcontrol-origin: margin;}QScrollBar::add-line:horizontal:hover, QScrollBar::add-line:horizontal:on {border-image: url(":/qss_icons/dark/rc/arrow_right.png"); height: 12px; width: 12px; subcontrol-position: right; subcontrol-origin: margin;}QScrollBar::add-line:vertical {margin: 3px 0px 3px 0px; border-image: url(":/qss_icons/dark/rc/arrow_down_disabled.png"); height: 12px; width: 12px; subcontrol-position: bottom; subcontrol-origin: margin;}QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on {border-image: url(":/qss_icons/dark/rc/arrow_down.png"); height: 12px; width: 12px; subcontrol-position: bottom; subcontrol-origin: margin;}QScrollBar::sub-line:horizontal {margin: 0px 3px 0px 3px; border-image: url(":/qss_icons/dark/rc/arrow_left_disabled.png"); height: 12px; width: 12px; subcontrol-position: left; subcontrol-origin: margin;}QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on {border-image: url(":/qss_icons/dark/rc/arrow_left.png"); height: 12px; width: 12px; subcontrol-position: left; subcontrol-origin: margin;}QScrollBar::sub-line:vertical {margin: 3px 0px 3px 0px; border-image: url(":/qss_icons/dark/rc/arrow_up_disabled.png"); height: 12px; width: 12px; subcontrol-position: top; subcontrol-origin: margin;}QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on {border-image: url(":/qss_icons/dark/rc/arrow_up.png"); height: 12px; width: 12px; subcontrol-position: top; subcontrol-origin: margin;}QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal {background: none;}QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {background: none;}QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {background: none;}QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {background: none;}QTextEdit {background-color: #19232D; color: #DFE1E2; border-radius: 4px; border: 1px solid #455364;}QTextEdit:focus {border: 1px solid #1A72BB;}QTextEdit:selected {background: #346792; color: #455364;}QPlainTextEdit {background-color: #19232D; color: #DFE1E2; border-radius: 4px; border: 1px solid #455364;}QPlainTextEdit:focus {border: 1px solid #1A72BB;}QPlainTextEdit:selected {background: #346792; color: #455364;}QToolBar {background-color: #455364; border-bottom: 1px solid #19232D; padding: 1px; font-weight: bold; spacing: 2px;}QToolBar:disabled {background-color: #455364;}QToolBar::handle:horizontal {width: 16px; image: url(":/qss_icons/dark/rc/toolbar_move_horizontal.png");}QToolBar::handle:vertical {height: 16px; image: url(":/qss_icons/dark/rc/toolbar_move_vertical.png");}QToolBar::separator:horizontal {width: 16px; image: url(":/qss_icons/dark/rc/toolbar_separator_horizontal.png");}QToolBar::separator:vertical {height: 16px; image: url(":/qss_icons/dark/rc/toolbar_separator_vertical.png");}QLabel {background-color: #19232D; border: 0px solid #455364; padding: 2px; margin: 0px; color: #DFE1E2;}QLabel:disabled {background-color: #19232D; border: 0px solid #455364; color: #788D9C;}QTextBrowser {background-color: #19232D; border: 1px solid #455364; color: #DFE1E2; border-radius: 4px;}QTextBrowser:disabled {background-color: #19232D; border: 1px solid #455364; color: #788D9C; border-radius: 4px;}QTextBrowser:hover, QTextBrowser:!hover, QTextBrowser:selected, QTextBrowser:pressed {border: 1px solid #455364;}QProgressBar {background-color: #19232D; border: 1px solid #455364; color: #DFE1E2; border-radius: 4px; text-align: center;}QProgressBar:disabled {background-color: #19232D; border: 1px solid #455364; color: #788D9C; border-radius: 4px; text-align: center;}QProgressBar::chunk {background-color: #346792; color: #19232D; border-radius: 4px;}QProgressBar::chunk:disabled {background-color: #26486B; color: #788D9C; border-radius: 4px;}QPushButton {background-color: #455364; color: #DFE1E2; border-radius: 4px; padding: 2px; outline: none; border: none;}QPushButton:disabled {background-color: #455364; color: #788D9C; border-radius: 4px; padding: 2px;}QPushButton:checked {background-color: #60798B; border-radius: 4px; padding: 2px; outline: none;}QPushButton:checked:disabled {background-color: #60798B; color: #788D9C; border-radius: 4px; padding: 2px; outline: none;}QPushButton:checked:selected {background: #60798B;}QPushButton:hover {background-color: #54687A; color: #DFE1E2;}QPushButton:pressed {background-color: #60798B;}QPushButton:selected {background: #60798B; color: #DFE1E2;}QPushButton::menu-indicator {subcontrol-origin: padding; subcontrol-position: bottom right; bottom: 4px;}QDialogButtonBox QPushButton {min-width: 80px;}QToolButton {background-color: #455364; color: #DFE1E2; border-radius: 4px; padding: 2px; outline: none; border: none;}QToolButton:disabled {background-color: #455364; color: #788D9C; border-radius: 4px; padding: 2px;}QToolButton:checked {background-color: #60798B; border-radius: 4px; padding: 2px; outline: none;}QToolButton:checked:disabled {background-color: #60798B; color: #788D9C; border-radius: 4px; padding: 2px; outline: none;}QToolButton:checked:hover {background-color: #54687A; color: #DFE1E2;}QToolButton:checked:pressed {background-color: #60798B;}QToolButton:checked:selected {background: #60798B; color: #DFE1E2;}QToolButton:hover {background-color: #54687A; color: #DFE1E2;}QToolButton:pressed {background-color: #60798B;}QToolButton:selected {background: #60798B; color: #DFE1E2;}QToolButton[popupMode="0"] {padding-right: 2px;}QToolButton[popupMode="1"] {padding-right: 20px;}QToolButton[popupMode="1"]::menu-button {border: none;}QToolButton[popupMode="1"]::menu-button:hover {border: none; border-left: 1px solid #455364; border-radius: 0;}QToolButton[popupMode="2"] {/* Only for InstantPopup */ padding-right: 2px;}QToolButton::menu-button {padding: 2px; border-radius: 4px; width: 12px; border: none; outline: none;}QToolButton::menu-button:hover {border: 1px solid #346792;}QToolButton::menu-button:checked:hover {border: 1px solid #346792;}QToolButton::menu-indicator {image: url(":/qss_icons/dark/rc/arrow_down.png"); height: 8px; width: 8px; top: 0; left: -2px;}QToolButton::menu-arrow {image: url(":/qss_icons/dark/rc/arrow_down.png"); height: 8px; width: 8px;}QToolButton::menu-arrow:hover {image: url(":/qss_icons/dark/rc/arrow_down_focus.png");}QComboBox {border: 1px solid #455364; border-radius: 4px; selection-background-color: #346792; padding-left: 4px; padding-right: 4px; min-height: 1.5em;}QComboBox QAbstractItemView {border: 1px solid #455364; border-radius: 0; background-color: #19232D; selection-background-color: #346792;}QComboBox QAbstractItemView:hover {background-color: #19232D; color: #DFE1E2;}QComboBox QAbstractItemView:selected {background: #346792; color: #455364;}QComboBox QAbstractItemView:alternate {background: #19232D;}QComboBox:disabled {background-color: #19232D; color: #788D9C;}QComboBox:hover {border: 1px solid #346792;}QComboBox:focus {border: 1px solid #1A72BB;}QComboBox:on {selection-background-color: #346792;}QComboBox::indicator {border: none; border-radius: 0; background-color: transparent; selection-background-color: transparent; color: transparent; selection-color: transparent;}QComboBox::indicator:alternate {background: #19232D;}QComboBox::item:alternate {background: #19232D;}QComboBox::drop-down {subcontrol-origin: padding; subcontrol-position: top right; width: 12px; border-left: 1px solid #455364;}QComboBox::down-arrow {image: url(":/qss_icons/dark/rc/arrow_down_disabled.png"); height: 8px; width: 8px;}QComboBox::down-arrow:on, QComboBox::down-arrow:hover, QComboBox::down-arrow:focus {image: url(":/qss_icons/dark/rc/arrow_down.png");}QLineEdit {background-color: #19232D; padding-top: 2px; padding-bottom: 2px; padding-left: 4px; padding-right: 4px; border-style: solid; border: 1px solid #455364; border-radius: 4px; color: #DFE1E2;}QLineEdit:disabled {background-color: #19232D; color: #788D9C;}QLineEdit:hover {border: 1px solid #346792; color: #DFE1E2;}QLineEdit:focus {border: 1px solid #1A72BB;}QLineEdit:selected {background-color: #346792; color: #455364;}QTabWidget {padding: 2px; selection-background-color: #455364;}QTabWidget QWidget {border-radius: 4px;}QTabWidget::pane {border: 1px solid #455364; border-radius: 4px; margin: 0px; padding: 0px;}QTabWidget::pane:selected {background-color: #455364; border: 1px solid #346792;}QTabBar, QDockWidget QTabBar {qproperty-drawBase: 0; border-radius: 4px; margin: 0px; padding: 2px; border: 0;}QTabBar::close-button, QDockWidget QTabBar::close-button {border: 0; margin: 0; padding: 4px; image: url(":/qss_icons/dark/rc/window_close.png");}QTabBar::close-button:hover, QDockWidget QTabBar::close-button:hover {image: url(":/qss_icons/dark/rc/window_close_focus.png");}QTabBar::close-button:pressed, QDockWidget QTabBar::close-button:pressed {image: url(":/qss_icons/dark/rc/window_close_pressed.png");}QTabBar::tab:top:selected:disabled, QDockWidget QTabBar::tab:top:selected:disabled {border-bottom: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:bottom:selected:disabled, QDockWidget QTabBar::tab:bottom:selected:disabled {border-top: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:left:selected:disabled, QDockWidget QTabBar::tab:left:selected:disabled {border-right: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:right:selected:disabled, QDockWidget QTabBar::tab:right:selected:disabled {border-left: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:top:!selected:disabled, QDockWidget QTabBar::tab:top:!selected:disabled {border-bottom: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:bottom:!selected:disabled, QDockWidget QTabBar::tab:bottom:!selected:disabled {border-top: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:left:!selected:disabled, QDockWidget QTabBar::tab:left:!selected:disabled {border-right: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:right:!selected:disabled, QDockWidget QTabBar::tab:right:!selected:disabled {border-left: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:top:!selected, QDockWidget QTabBar::tab:top:!selected {border-bottom: 2px solid #19232D; margin-top: 2px;}QTabBar::tab:bottom:!selected, QDockWidget QTabBar::tab:bottom:!selected {border-top: 2px solid #19232D; margin-bottom: 2px;}QTabBar::tab:left:!selected, QDockWidget QTabBar::tab:left:!selected {border-left: 2px solid #19232D; margin-right: 2px;}QTabBar::tab:right:!selected, QDockWidget QTabBar::tab:right:!selected {border-right: 2px solid #19232D; margin-left: 2px;}QTabBar::tab:top, QDockWidget QTabBar::tab:top {background-color: #455364; margin-left: 2px; padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; min-width: 5px; border-bottom: 3px solid #455364; border-top-left-radius: 4px; border-top-right-radius: 4px;}QTabBar::tab:top:selected, QDockWidget QTabBar::tab:top:selected {background-color: #54687A; border-bottom: 3px solid #259AE9; border-top-left-radius: 4px; border-top-right-radius: 4px;}QTabBar::tab:top:!selected:hover, QDockWidget QTabBar::tab:top:!selected:hover {border: 1px solid #1A72BB; border-bottom: 3px solid #1A72BB; padding-left: 3px; padding-right: 3px;}QTabBar::tab:bottom, QDockWidget QTabBar::tab:bottom {border-top: 3px solid #455364; background-color: #455364; margin-left: 2px; padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; min-width: 5px;}QTabBar::tab:bottom:selected, QDockWidget QTabBar::tab:bottom:selected {background-color: #54687A; border-top: 3px solid #259AE9; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px;}QTabBar::tab:bottom:!selected:hover, QDockWidget QTabBar::tab:bottom:!selected:hover {border: 1px solid #1A72BB; border-top: 3px solid #1A72BB; padding-left: 3px; padding-right: 3px;}QTabBar::tab:left, QDockWidget QTabBar::tab:left {background-color: #455364; margin-top: 2px; padding-left: 2px; padding-right: 2px; padding-top: 4px; padding-bottom: 4px; border-top-left-radius: 4px; border-bottom-left-radius: 4px; min-height: 5px;}QTabBar::tab:left:selected, QDockWidget QTabBar::tab:left:selected {background-color: #54687A; border-right: 3px solid #259AE9;}QTabBar::tab:left:!selected:hover, QDockWidget QTabBar::tab:left:!selected:hover {border: 1px solid #1A72BB; border-right: 3px solid #1A72BB; margin-right: 0px; padding-right: -1px;}QTabBar::tab:right, QDockWidget QTabBar::tab:right {background-color: #455364; margin-top: 2px; padding-left: 2px; padding-right: 2px; padding-top: 4px; padding-bottom: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; min-height: 5px;}QTabBar::tab:right:selected, QDockWidget QTabBar::tab:right:selected {background-color: #54687A; border-left: 3px solid #259AE9;}QTabBar::tab:right:!selected:hover, QDockWidget QTabBar::tab:right:!selected:hover {border: 1px solid #1A72BB; border-left: 3px solid #1A72BB; margin-left: 0px; padding-left: 0px;}QTabBar QToolButton, QDockWidget QTabBar QToolButton {background-color: #455364; height: 12px; width: 12px;}QTabBar QToolButton:pressed, QDockWidget QTabBar QToolButton:pressed {background-color: #455364;}QTabBar QToolButton:pressed:hover, QDockWidget QTabBar QToolButton:pressed:hover {border: 1px solid #346792;}QTabBar QToolButton::left-arrow:enabled, QDockWidget QTabBar QToolButton::left-arrow:enabled {image: url(":/qss_icons/dark/rc/arrow_left.png");}QTabBar QToolButton::left-arrow:disabled, QDockWidget QTabBar QToolButton::left-arrow:disabled {image: url(":/qss_icons/dark/rc/arrow_left_disabled.png");}QTabBar QToolButton::right-arrow:enabled, QDockWidget QTabBar QToolButton::right-arrow:enabled {image: url(":/qss_icons/dark/rc/arrow_right.png");}QTabBar QToolButton::right-arrow:disabled, QDockWidget QTabBar QToolButton::right-arrow:disabled {image: url(":/qss_icons/dark/rc/arrow_right_disabled.png");}QSplitter {background-color: #455364; spacing: 0px; padding: 0px; margin: 0px;}QSplitter::handle {background-color: #455364; border: 0px solid #19232D; spacing: 0px; padding: 1px; margin: 0px;}QSplitter::handle:hover {background-color: #9DA9B5;}QSplitter::handle:horizontal {width: 5px; image: url(":/qss_icons/dark/rc/line_vertical.png");}QSplitter::handle:vertical {height: 5px; image: url(":/qss_icons/dark/rc/line_horizontal.png");} \ No newline at end of file +QToolBar { margin:0; padding:0; border-image:none;} QToolBar QToolButton {background-color: #32414B; height:30px; margin-bottom:0px; margin-top:5px; margin-left:2px; margin-right:0px; text-align: left;}QToolBar QToolButton:hover {border: 1px solid #148CD2;}QToolBar QToolButton:checked {background-color: #19232D; border: 1px solid #148CD2;}QToolBar QToolButton:checked:hover {border: 1px solid #339933;}QLabel{ color: #bbbbbb;}QLineEdit:hover,QComboBox:hover{ border-color: #148cd2;} QLineEdit,QComboBox{ background-color: #161E26; border-color: #32414B;}QLineEdit:disabled { background-color: transparent; border-color: transparent;}QComboBox:disabled{ background-color: transparent; border-color: #273442;}QScrollArea QPushButton{ background-color: rgba(50, 65, 75, 0.5); border-radius: 0; opacity: 0.1; text-align:left; padding-left:5px;}QScrollArea QPushButton:hover{ background-color: #19232D;}QPlainTextEdit:read-only{ color:#999999;}* {padding: 0px; margin: 0px; border: 0px; border-style: none; border-image: none; outline: 0;}QToolBar > * {margin: 0px; padding: 0px;}QWidget {background-color: #19232D; border: 0px solid #455364; padding: 0px; color: #DFE1E2; selection-background-color: #346792; selection-color: #DFE1E2;}QWidget:disabled {background-color: #19232D; color: #788D9C; selection-background-color: #26486B; selection-color: #788D9C;}QWidget::item:selected {background-color: #346792;}QWidget::item:hover:!selected {background-color: #1A72BB;}QMainWindow::separator {background-color: #455364; border: 0px solid #19232D; spacing: 0px; padding: 2px;}QMainWindow::separator:hover {background-color: #60798B; border: 0px solid #1A72BB;}QMainWindow::separator:horizontal {width: 5px; margin-top: 2px; margin-bottom: 2px; image: url(":/qss_icons/dark/rc/toolbar_separator_vertical.png");}QMainWindow::separator:vertical {height: 5px; margin-left: 2px; margin-right: 2px; image: url(":/qss_icons/dark/rc/toolbar_separator_horizontal.png");}QToolTip {background-color: #346792; color: #DFE1E2; border: none; padding: 0px;}QStatusBar {border: 1px solid #455364; background: #455364;}QStatusBar::item {border: none;}QStatusBar QToolTip {background-color: #1A72BB; border: 1px solid #19232D; color: #19232D; padding: 0px; opacity: 230;}QStatusBar QLabel {background: transparent;}QCheckBox {background-color: #19232D; color: #DFE1E2; spacing: 4px; outline: none; padding-top: 4px; padding-bottom: 4px;}QCheckBox:focus {border: none;}QCheckBox QWidget:disabled {background-color: #19232D; color: #788D9C;}QCheckBox::indicator {margin-left: 2px; height: 14px; width: 14px;}QCheckBox::indicator:unchecked {image: url(":/qss_icons/dark/rc/checkbox_unchecked.png");}QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus, QCheckBox::indicator:unchecked:pressed {image: url(":/qss_icons/dark/rc/checkbox_unchecked_focus.png");}QCheckBox::indicator:unchecked:disabled {image: url(":/qss_icons/dark/rc/checkbox_unchecked_disabled.png");}QCheckBox::indicator:checked {image: url(":/qss_icons/dark/rc/checkbox_checked.png");}QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus, QCheckBox::indicator:checked:pressed { image: url(":/qss_icons/dark/rc/checkbox_checked_focus.png");}QCheckBox::indicator:checked:disabled {image: url(":/qss_icons/dark/rc/checkbox_checked_disabled.png");}QCheckBox::indicator:indeterminate {image: url(":/qss_icons/dark/rc/checkbox_indeterminate.png");}QCheckBox::indicator:indeterminate:disabled {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_disabled.png");}QCheckBox::indicator:indeterminate:focus, QCheckBox::indicator:indeterminate:hover, QCheckBox::indicator:indeterminate:pressed {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_focus.png");}QMenuBar {background-color: #455364; padding: 2px; border: 1px solid #19232D; color: #DFE1E2; selection-background-color: #1A72BB;}QMenuBar:focus {border: 1px solid #346792;}QMenuBar::item {background: transparent; padding: 4px;}QMenuBar::item:selected {padding: 4px; background: transparent; border: 0px solid #455364; background-color: #1A72BB;}QMenuBar::item:pressed {padding: 4px; border: 0px solid #455364; background-color: #1A72BB; color: #DFE1E2; margin-bottom: 0px; padding-bottom: 0px;}QMenu {border: 0px solid #455364; color: #DFE1E2; margin: 0px; background-color: #37414F; selection-background-color: #1A72BB;}QMenu::separator {height: 1px; background-color: #60798B; color: #DFE1E2;}QMenu::item {background-color: #37414F; padding: 4px 24px 4px 28px; border: 1px transparent #455364;}QMenu::item:selected {color: #DFE1E2; background-color: #1A72BB;}QMenu::item:pressed {background-color: #1A72BB;}QMenu::icon {padding-left: 10px; width: 14px; height: 14px;}QMenu::indicator {padding-left: 8px; width: 12px; height: 12px;}QMenu::indicator:non-exclusive:unchecked {image: url(":/qss_icons/dark/rc/checkbox_unchecked.png");}QMenu::indicator:non-exclusive:unchecked:hover, QMenu::indicator:non-exclusive:unchecked:focus, QMenu::indicator:non-exclusive:unchecked:pressed {border: none; image: url(":/qss_icons/dark/rc/checkbox_unchecked_focus.png");}QMenu::indicator:non-exclusive:unchecked:disabled {image: url(":/qss_icons/dark/rc/checkbox_unchecked_disabled.png");}QMenu::indicator:non-exclusive:checked {image: url(":/qss_icons/dark/rc/checkbox_checked.png");}QMenu::indicator:non-exclusive:checked:hover, QMenu::indicator:non-exclusive:checked:focus, QMenu::indicator:non-exclusive:checked:pressed {border: none; image: url(":/qss_icons/dark/rc/checkbox_checked_focus.png");}QMenu::indicator:non-exclusive:checked:disabled {image: url(":/qss_icons/dark/rc/checkbox_checked_disabled.png");}QMenu::indicator:non-exclusive:indeterminate {image: url(":/qss_icons/dark/rc/checkbox_indeterminate.png");}QMenu::indicator:non-exclusive:indeterminate:disabled {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_disabled.png");}QMenu::indicator:non-exclusive:indeterminate:focus, QMenu::indicator:non-exclusive:indeterminate:hover, QMenu::indicator:non-exclusive:indeterminate:pressed {image: url(":/qss_icons/dark/rc/checkbox_indeterminate_focus.png");}QMenu::indicator:exclusive:unchecked {image: url(":/qss_icons/dark/rc/radio_unchecked.png");}QMenu::indicator:exclusive:unchecked:hover, QMenu::indicator:exclusive:unchecked:focus, QMenu::indicator:exclusive:unchecked:pressed {border: none; outline: none; image: url(":/qss_icons/dark/rc/radio_unchecked_focus.png");}QMenu::indicator:exclusive:unchecked:disabled {image: url(":/qss_icons/dark/rc/radio_unchecked_disabled.png");}QMenu::indicator:exclusive:checked {border: none; outline: none; image: url(":/qss_icons/dark/rc/radio_checked.png");}QMenu::indicator:exclusive:checked:hover, QMenu::indicator:exclusive:checked:focus, QMenu::indicator:exclusive:checked:pressed {border: none; outline: none; image: url(":/qss_icons/dark/rc/radio_checked_focus.png");}QMenu::indicator:exclusive:checked:disabled {outline: none; image: url(":/qss_icons/dark/rc/radio_checked_disabled.png");}QMenu::right-arrow {margin: 5px; padding-left: 12px; image: url(":/qss_icons/dark/rc/arrow_right.png"); height: 12px; width: 12px;}QScrollArea QWidget QWidget:disabled {background-color: #19232D;}QScrollBar:horizontal {height: 16px; margin: 2px 16px 2px 16px; border: 1px solid #455364; border-radius: 4px; background-color: #19232D;}QScrollBar:vertical {background-color: #19232D; width: 16px; margin: 16px 2px 16px 2px; border: 1px solid #455364; border-radius: 4px;}QScrollBar::handle:horizontal {background-color: #60798B; border: 1px solid #455364; border-radius: 4px; min-width: 8px;}QScrollBar::handle:horizontal:hover {background-color: #346792; border: #346792; border-radius: 4px; min-width: 8px;}QScrollBar::handle:horizontal:focus {border: 1px solid #1A72BB;}QScrollBar::handle:vertical {background-color: #60798B; border: 1px solid #455364; min-height: 8px; border-radius: 4px;}QScrollBar::handle:vertical:hover {background-color: #346792; border: #346792; border-radius: 4px; min-height: 8px;}QScrollBar::handle:vertical:focus {border: 1px solid #1A72BB;}QScrollBar::add-line:horizontal {margin: 0px 0px 0px 0px; border-image: url(":/qss_icons/dark/rc/arrow_right_disabled.png"); height: 12px; width: 12px; subcontrol-position: right; subcontrol-origin: margin;}QScrollBar::add-line:horizontal:hover, QScrollBar::add-line:horizontal:on {border-image: url(":/qss_icons/dark/rc/arrow_right.png"); height: 12px; width: 12px; subcontrol-position: right; subcontrol-origin: margin;}QScrollBar::add-line:vertical {margin: 3px 0px 3px 0px; border-image: url(":/qss_icons/dark/rc/arrow_down_disabled.png"); height: 12px; width: 12px; subcontrol-position: bottom; subcontrol-origin: margin;}QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on {border-image: url(":/qss_icons/dark/rc/arrow_down.png"); height: 12px; width: 12px; subcontrol-position: bottom; subcontrol-origin: margin;}QScrollBar::sub-line:horizontal {margin: 0px 3px 0px 3px; border-image: url(":/qss_icons/dark/rc/arrow_left_disabled.png"); height: 12px; width: 12px; subcontrol-position: left; subcontrol-origin: margin;}QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on {border-image: url(":/qss_icons/dark/rc/arrow_left.png"); height: 12px; width: 12px; subcontrol-position: left; subcontrol-origin: margin;}QScrollBar::sub-line:vertical {margin: 3px 0px 3px 0px; border-image: url(":/qss_icons/dark/rc/arrow_up_disabled.png"); height: 12px; width: 12px; subcontrol-position: top; subcontrol-origin: margin;}QScrollBar::sub-line:vertical:hover, QScrollBar::sub-line:vertical:on {border-image: url(":/qss_icons/dark/rc/arrow_up.png"); height: 12px; width: 12px; subcontrol-position: top; subcontrol-origin: margin;}QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal {background: none;}QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {background: none;}QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {background: none;}QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {background: none;}QTextEdit {background-color: #19232D; color: #DFE1E2; border-radius: 4px; border: 1px solid #455364;}QTextEdit:focus {border: 1px solid #1A72BB;}QTextEdit:selected {background: #346792; color: #455364;}QPlainTextEdit {background-color: #19232D; color: #DFE1E2; border-radius: 4px; border: 1px solid #455364;}QPlainTextEdit:focus {border: 1px solid #1A72BB;}QPlainTextEdit:selected {background: #346792; color: #455364;}QToolBar {background-color: #455364; border-bottom: 1px solid #19232D; padding: 1px; font-weight: bold; spacing: 2px;}QToolBar:disabled {background-color: #455364;}QToolBar::handle:horizontal {width: 16px; image: url(":/qss_icons/dark/rc/toolbar_move_horizontal.png");}QToolBar::handle:vertical {height: 16px; image: url(":/qss_icons/dark/rc/toolbar_move_vertical.png");}QToolBar::separator:horizontal {width: 16px; image: url(":/qss_icons/dark/rc/toolbar_separator_horizontal.png");}QToolBar::separator:vertical {height: 16px; image: url(":/qss_icons/dark/rc/toolbar_separator_vertical.png");}QLabel {background-color: #19232D; border: 0px solid #455364; padding: 2px; margin: 0px; color: #DFE1E2;}QLabel:disabled {background-color: #19232D; border: 0px solid #455364; color: #788D9C;}QTextBrowser {background-color: #19232D; border: 1px solid #455364; color: #DFE1E2; border-radius: 4px;}QTextBrowser:disabled {background-color: #19232D; border: 1px solid #455364; color: #788D9C; border-radius: 4px;}QTextBrowser:hover, QTextBrowser:!hover, QTextBrowser:selected, QTextBrowser:pressed {border: 1px solid #455364;}QProgressBar {background-color: #19232D; border: 1px solid #455364; color: #DFE1E2; border-radius: 4px; text-align: center;}QProgressBar:disabled {background-color: #19232D; border: 1px solid #455364; color: #788D9C; border-radius: 4px; text-align: center;}QProgressBar::chunk {background-color: #346792; color: #19232D; border-radius: 4px;}QProgressBar::chunk:disabled {background-color: #26486B; color: #788D9C; border-radius: 4px;}QPushButton {background-color: #455364; color: #DFE1E2; border-radius: 4px; padding: 2px; outline: none; border: none;}QPushButton:disabled {background-color: #455364; color: #788D9C; border-radius: 4px; padding: 2px;}QPushButton:checked {background-color: #60798B; border-radius: 4px; padding: 2px; outline: none;}QPushButton:checked:disabled {background-color: #60798B; color: #788D9C; border-radius: 4px; padding: 2px; outline: none;}QPushButton:checked:selected {background: #60798B;}QPushButton:hover {background-color: #54687A; color: #DFE1E2;}QPushButton:pressed {background-color: #60798B;}QPushButton:selected {background: #60798B; color: #DFE1E2;}QPushButton::menu-indicator {subcontrol-origin: padding; subcontrol-position: bottom right; bottom: 4px;}QDialogButtonBox QPushButton {min-width: 80px;}QToolButton {background-color: #455364; color: #DFE1E2; border-radius: 4px; padding: 2px; outline: none; border: none;}QToolButton:disabled {background-color: #455364; color: #788D9C; border-radius: 4px; padding: 2px;}QToolButton:checked {background-color: #60798B; border-radius: 4px; padding: 2px; outline: none;}QToolButton:checked:disabled {background-color: #60798B; color: #788D9C; border-radius: 4px; padding: 2px; outline: none;}QToolButton:checked:hover {background-color: #54687A; color: #DFE1E2;}QToolButton:checked:pressed {background-color: #60798B;}QToolButton:checked:selected {background: #60798B; color: #DFE1E2;}QToolButton:hover {background-color: #54687A; color: #DFE1E2;}QToolButton:pressed {background-color: #60798B;}QToolButton:selected {background: #60798B; color: #DFE1E2;}QToolButton[popupMode="0"] {padding-right: 2px;}QToolButton[popupMode="1"] {padding-right: 20px;}QToolButton[popupMode="1"]::menu-button {border: none;}QToolButton[popupMode="1"]::menu-button:hover {border: none; border-left: 1px solid #455364; border-radius: 0;}QToolButton[popupMode="2"] {/* Only for InstantPopup */ padding-right: 2px;}QToolButton::menu-button {padding: 2px; border-radius: 4px; width: 12px; border: none; outline: none;}QToolButton::menu-button:hover {border: 1px solid #346792;}QToolButton::menu-button:checked:hover {border: 1px solid #346792;}QToolButton::menu-indicator {image: url(":/qss_icons/dark/rc/arrow_down.png"); height: 8px; width: 8px; top: 0; left: -2px;}QToolButton::menu-arrow {image: url(":/qss_icons/dark/rc/arrow_down.png"); height: 8px; width: 8px;}QToolButton::menu-arrow:hover {image: url(":/qss_icons/dark/rc/arrow_down_focus.png");}QComboBox {border: 1px solid #455364; border-radius: 4px; selection-background-color: #346792; padding-left: 4px; padding-right: 4px; min-height: 1.5em;}QComboBox QAbstractItemView {border: 1px solid #455364; border-radius: 0; background-color: #19232D; selection-background-color: #346792;}QComboBox QAbstractItemView:hover {background-color: #19232D; color: #DFE1E2;}QComboBox QAbstractItemView:selected {background: #346792; color: #455364;}QComboBox QAbstractItemView:alternate {background: #19232D;}QComboBox:disabled {background-color: #19232D; color: #788D9C;}QComboBox:hover {border: 1px solid #346792;}QComboBox:focus {border: 1px solid #1A72BB;}QComboBox:on {selection-background-color: #346792;}QComboBox::indicator {border: none; border-radius: 0; background-color: transparent; selection-background-color: transparent; color: transparent; selection-color: transparent;}QComboBox::indicator:alternate {background: #19232D;}QComboBox::item:alternate {background: #19232D;}QComboBox::drop-down {subcontrol-origin: padding; subcontrol-position: top right; width: 12px; border-left: 1px solid #455364;}QComboBox::down-arrow {image: url(":/qss_icons/dark/rc/arrow_down_disabled.png"); height: 8px; width: 8px;}QComboBox::down-arrow:on, QComboBox::down-arrow:hover, QComboBox::down-arrow:focus {image: url(":/qss_icons/dark/rc/arrow_down.png");}QLineEdit {background-color: #19232D; padding-top: 2px; padding-bottom: 2px; padding-left: 4px; padding-right: 4px; border-style: solid; border: 1px solid #455364; border-radius: 4px; color: #DFE1E2;}QLineEdit:disabled {background-color: #19232D; color: #788D9C;}QLineEdit:hover {border: 1px solid #346792; color: #DFE1E2;}QLineEdit:focus {border: 1px solid #1A72BB;}QLineEdit:selected {background-color: #346792; color: #455364;}QTabWidget {padding: 2px; selection-background-color: #455364;}QTabWidget QWidget {border-radius: 4px;}QTabWidget::pane {border: 1px solid #455364; border-radius: 4px; margin: 0px; padding: 0px;}QTabWidget::pane:selected {background-color: #455364; border: 1px solid #346792;}QTabBar, QDockWidget QTabBar {qproperty-drawBase: 0; border-radius: 4px; margin: 0px; padding: 2px; border: 0;}QTabBar::close-button, QDockWidget QTabBar::close-button {border: 0; margin: 0; padding: 4px; image: url(":/qss_icons/dark/rc/window_close.png");}QTabBar::close-button:hover, QDockWidget QTabBar::close-button:hover {image: url(":/qss_icons/dark/rc/window_close_focus.png");}QTabBar::close-button:pressed, QDockWidget QTabBar::close-button:pressed {image: url(":/qss_icons/dark/rc/window_close_pressed.png");}QTabBar::tab:top:selected:disabled, QDockWidget QTabBar::tab:top:selected:disabled {border-bottom: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:bottom:selected:disabled, QDockWidget QTabBar::tab:bottom:selected:disabled {border-top: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:left:selected:disabled, QDockWidget QTabBar::tab:left:selected:disabled {border-right: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:right:selected:disabled, QDockWidget QTabBar::tab:right:selected:disabled {border-left: 3px solid #26486B; color: #788D9C; background-color: #455364;}QTabBar::tab:top:!selected:disabled, QDockWidget QTabBar::tab:top:!selected:disabled {border-bottom: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:bottom:!selected:disabled, QDockWidget QTabBar::tab:bottom:!selected:disabled {border-top: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:left:!selected:disabled, QDockWidget QTabBar::tab:left:!selected:disabled {border-right: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:right:!selected:disabled, QDockWidget QTabBar::tab:right:!selected:disabled {border-left: 3px solid #19232D; color: #788D9C; background-color: #19232D;}QTabBar::tab:top:!selected, QDockWidget QTabBar::tab:top:!selected {border-bottom: 2px solid #19232D; margin-top: 2px;}QTabBar::tab:bottom:!selected, QDockWidget QTabBar::tab:bottom:!selected {border-top: 2px solid #19232D; margin-bottom: 2px;}QTabBar::tab:left:!selected, QDockWidget QTabBar::tab:left:!selected {border-left: 2px solid #19232D; margin-right: 2px;}QTabBar::tab:right:!selected, QDockWidget QTabBar::tab:right:!selected {border-right: 2px solid #19232D; margin-left: 2px;}QTabBar::tab:top, QDockWidget QTabBar::tab:top {background-color: #455364; margin-left: 2px; padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; min-width: 5px; border-bottom: 3px solid #455364; border-top-left-radius: 4px; border-top-right-radius: 4px;}QTabBar::tab:top:selected, QDockWidget QTabBar::tab:top:selected {background-color: #54687A; border-bottom: 3px solid #259AE9; border-top-left-radius: 4px; border-top-right-radius: 4px;}QTabBar::tab:top:!selected:hover, QDockWidget QTabBar::tab:top:!selected:hover {border: 1px solid #1A72BB; border-bottom: 3px solid #1A72BB; padding-left: 3px; padding-right: 3px;}QTabBar::tab:bottom, QDockWidget QTabBar::tab:bottom {border-top: 3px solid #455364; background-color: #455364; margin-left: 2px; padding-left: 4px; padding-right: 4px; padding-top: 2px; padding-bottom: 2px; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; min-width: 5px;}QTabBar::tab:bottom:selected, QDockWidget QTabBar::tab:bottom:selected {background-color: #54687A; border-top: 3px solid #259AE9; border-bottom-left-radius: 4px; border-bottom-right-radius: 4px;}QTabBar::tab:bottom:!selected:hover, QDockWidget QTabBar::tab:bottom:!selected:hover {border: 1px solid #1A72BB; border-top: 3px solid #1A72BB; padding-left: 3px; padding-right: 3px;}QTabBar::tab:left, QDockWidget QTabBar::tab:left {background-color: #455364; margin-top: 2px; padding-left: 2px; padding-right: 2px; padding-top: 4px; padding-bottom: 4px; border-top-left-radius: 4px; border-bottom-left-radius: 4px; min-height: 5px;}QTabBar::tab:left:selected, QDockWidget QTabBar::tab:left:selected {background-color: #54687A; border-right: 3px solid #259AE9;}QTabBar::tab:left:!selected:hover, QDockWidget QTabBar::tab:left:!selected:hover {border: 1px solid #1A72BB; border-right: 3px solid #1A72BB; margin-right: 0px; padding-right: -1px;}QTabBar::tab:right, QDockWidget QTabBar::tab:right {background-color: #455364; margin-top: 2px; padding-left: 2px; padding-right: 2px; padding-top: 4px; padding-bottom: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; min-height: 5px;}QTabBar::tab:right:selected, QDockWidget QTabBar::tab:right:selected {background-color: #54687A; border-left: 3px solid #259AE9;}QTabBar::tab:right:!selected:hover, QDockWidget QTabBar::tab:right:!selected:hover {border: 1px solid #1A72BB; border-left: 3px solid #1A72BB; margin-left: 0px; padding-left: 0px;}QTabBar QToolButton, QDockWidget QTabBar QToolButton {background-color: #455364; height: 12px; width: 12px;}QTabBar QToolButton:pressed, QDockWidget QTabBar QToolButton:pressed {background-color: #455364;}QTabBar QToolButton:pressed:hover, QDockWidget QTabBar QToolButton:pressed:hover {border: 1px solid #346792;}QTabBar QToolButton::left-arrow:enabled, QDockWidget QTabBar QToolButton::left-arrow:enabled {image: url(":/qss_icons/dark/rc/arrow_left.png");}QTabBar QToolButton::left-arrow:disabled, QDockWidget QTabBar QToolButton::left-arrow:disabled {image: url(":/qss_icons/dark/rc/arrow_left_disabled.png");}QTabBar QToolButton::right-arrow:enabled, QDockWidget QTabBar QToolButton::right-arrow:enabled {image: url(":/qss_icons/dark/rc/arrow_right.png");}QTabBar QToolButton::right-arrow:disabled, QDockWidget QTabBar QToolButton::right-arrow:disabled {image: url(":/qss_icons/dark/rc/arrow_right_disabled.png");}QSplitter {background-color: #455364; spacing: 0px; padding: 0px; margin: 0px;}QSplitter::handle {background-color: #455364; border: 0px solid #19232D; spacing: 0px; padding: 1px; margin: 0px;}QSplitter::handle:hover {background-color: #9DA9B5;}QSplitter::handle:horizontal {width: 5px; image: url(":/qss_icons/dark/rc/line_vertical.png");}QSplitter::handle:vertical {height: 5px; image: url(":/qss_icons/dark/rc/line_horizontal.png");} +QAbstractSpinBox{background-color: #19232D;border: 1px solid #455364;color: #DFE1E2; +padding-top: 2px; +padding-bottom: 2px; + padding-left: 4px; + padding-right: 4px; + border-radius: 4px;} \ No newline at end of file diff --git a/videotrans/task/job.py b/videotrans/task/job.py index 4cdf5c21..6ca20c51 100644 --- a/videotrans/task/job.py +++ b/videotrans/task/job.py @@ -17,21 +17,16 @@ def run(self) -> None: time.sleep(0.5) continue trk=config.regcon_queue.pop(0) - is_recogn=trk.is_recogn() - # 不需要识别 - if not is_recogn[0] or is_recogn[1]: - config.trans_queue.append(trk) - continue try: trk.recogn() # 插入翻译队列 config.trans_queue.append(trk) except Exception as e: - if trk.btnkey not in config.unidlist: - config.unidlist.append(trk.btnkey) + if trk.init['btnkey'] not in config.unidlist: + config.unidlist.append(trk.init['btnkey']) msg=f'{config.transobj["shibiechucuo"]}:'+str(e) - set_process(msg,'error',btnkey=trk.btnkey) - config.errorlist[trk.btnkey]=msg + set_process(msg,'error',btnkey=trk.init['btnkey']) + config.errorlist[trk.init['btnkey']]=msg class WorkerTrans(QThread): @@ -45,28 +40,15 @@ def run(self) -> None: time.sleep(0.5) continue trk=config.trans_queue.pop(0) - # 需要识别但识别未完成 - is_recogn=trk.is_recogn() - if is_recogn[0] and not is_recogn[1]: - config.trans_queue.append(trk) - time.sleep(0.5) - continue - - is_trans=trk.is_trans() - if not is_trans[0] or is_trans[1]: - # 不需要翻译,则插入配音队列 - config.dubb_queue.append(trk) - time.sleep(0.5) - continue try: trk.trans() config.dubb_queue.append(trk) except Exception as e: - if trk.btnkey not in config.unidlist: - config.unidlist.append(trk.btnkey) + if trk.init['btnkey'] not in config.unidlist: + config.unidlist.append(trk.init['btnkey']) msg = f'{config.transobj["fanyichucuo"]}:' + str(e) - set_process(msg, 'error', btnkey=trk.btnkey) - config.errorlist[trk.btnkey] = msg + set_process(msg, 'error', btnkey=trk.init['btnkey']) + config.errorlist[trk.init['btnkey']] = msg class WorkerDubb(QThread): def __init__(self, *,parent=None): @@ -79,34 +61,15 @@ def run(self) -> None: time.sleep(0.5) continue trk=config.dubb_queue.pop(0) - # 需要识别但识别未完成 - is_recogn=trk.is_recogn() - if is_recogn[0] and not is_recogn[1]: - config.dubb_queue.append(trk) - time.sleep(0.5) - continue - # 需要翻译但未完成 - is_trans=trk.is_trans() - if is_trans[0] and not is_trans[1]: - config.trans_queue.append(trk) - time.sleep(0.5) - continue - - is_dubb=trk.is_dubb() - if not is_dubb[0] or is_dubb[1]: - # 不需要翻译插入合成 - config.compose_queue.append(trk) - time.sleep(0.5) - continue try: trk.dubbing() config.compose_queue.append(trk) except Exception as e: - if trk.btnkey not in config.unidlist: - config.unidlist.append(trk.btnkey) + if trk.init['btnkey'] not in config.unidlist: + config.unidlist.append(trk.init['btnkey']) msg=f'{config.transobj["peiyinchucuo"]}:'+str(e) - set_process(msg,'error',btnkey=trk.btnkey) - config.errorlist[trk.btnkey]=msg + set_process(msg,'error',btnkey=trk.init['btnkey']) + config.errorlist[trk.init['btnkey']]=msg class WorkerCompose(QThread): def __init__(self, *,parent=None): @@ -119,36 +82,17 @@ def run(self) -> None: time.sleep(0.5) continue trk=config.compose_queue.pop(0) - # 需要识别但识别未完成 - is_recogn = trk.is_recogn() - if is_recogn[0] and not is_recogn[1]: - config.compose_queue.append(trk) - time.sleep(0.5) - continue - # 需要翻译但未完成 - is_trans = trk.is_trans() - if is_trans[0] and not is_trans[1]: - config.compose_queue.append(trk) - time.sleep(0.5) - continue - # 需要配音但未完成 - is_dubb = trk.is_dubb() - if is_dubb[0] and not is_dubb[1]: - config.compose_queue.append(trk) - time.sleep(0.5) - continue - try: trk.hebing() trk.move_at_end() - config.errorlist[trk.btnkey]="" + config.errorlist[trk.init['btnkey']]="" except Exception as e: msg=f'{config.transobj["hebingchucuo"]}:'+str(e) - set_process(msg,'error',btnkey=trk.btnkey) - config.errorlist[trk.btnkey]=msg + set_process(msg,'error',btnkey=trk.init['btnkey']) + config.errorlist[trk.init['btnkey']]=msg finally: - if trk.btnkey not in config.unidlist: - config.unidlist.append(trk.btnkey) + if trk.init['btnkey'] not in config.unidlist: + config.unidlist.append(trk.init['btnkey']) def start_thread(parent): diff --git a/videotrans/task/main_worker.py b/videotrans/task/main_worker.py index 7a9ec876..0afb9561 100644 --- a/videotrans/task/main_worker.py +++ b/videotrans/task/main_worker.py @@ -39,11 +39,12 @@ def srt2audio(self): raise Exception(f'{config.transobj["yuchulichucuo"]}:'+str(e)) try: self.video.dubbing() + except Exception as e: raise Exception(f'{config.transobj["peiyinchucuo"]}:'+str(e)) # 成功完成 config.params['line_roles'] = {} - set_process(f"{self.video.target_dir}##srt2wav", 'succeed', btnkey="srt2wav") + set_process(f"{self.video.init['target_dir']}##srt2wav", 'succeed', btnkey="srt2wav") send_notification(config.transobj["zhixingwc"], f'"subtitles -> audio"') # 全部完成 except Exception as e: @@ -107,19 +108,19 @@ def run(self) -> None: if config.exit_soft or config.current_status != 'ing': return self.stop() try: - set_process(config.transobj['kaishichuli'], btnkey=video.btnkey) + set_process(config.transobj['kaishichuli'], btnkey=video.init['btnkey']) video.prepare() except Exception as e: err=f'{config.transobj["yuchulichucuo"]}:' + str(e) - config.errorlist[video.btnkey]=err - set_process(err, 'error', btnkey=video.btnkey) + config.errorlist[video.init['btnkey']]=err + set_process(err, 'error', btnkey=video.init['btnkey']) if self.is_batch: - self.unidlist.remove(video.btnkey) + self.unidlist.remove(video.init['btnkey']) continue if self.is_batch: # 压入识别队列开始执行 - config.regcon_queue.append(self.tasklist[video.btnkey]) + config.regcon_queue.append(self.tasklist[video.init['btnkey']]) continue # 非批量并发 try: @@ -128,8 +129,8 @@ def run(self) -> None: video.recogn() except Exception as e: err=f'{config.transobj["shibiechucuo"]}:' + str(e) - config.errorlist[video.btnkey]=err - set_process(err, 'error', btnkey=video.btnkey) + config.errorlist[video.init['btnkey']]=err + set_process(err, 'error', btnkey=video.init['btnkey']) continue try: if config.exit_soft or config.current_status != 'ing': @@ -137,8 +138,8 @@ def run(self) -> None: video.trans() except Exception as e: err=f'{config.transobj["fanyichucuo"]}:' + str(e) - config.errorlist[video.btnkey]=err - set_process(err, 'error', btnkey=video.btnkey) + config.errorlist[video.init['btnkey']]=err + set_process(err, 'error', btnkey=video.init['btnkey']) continue try: if config.exit_soft or config.current_status != 'ing': @@ -146,8 +147,8 @@ def run(self) -> None: video.dubbing() except Exception as e: err=f'{config.transobj["peiyinchucuo"]}:' + str(e) - config.errorlist[video.btnkey]=err - set_process(err, 'error', btnkey=video.btnkey) + config.errorlist[video.init['btnkey']]=err + set_process(err, 'error', btnkey=video.init['btnkey']) continue try: if config.exit_soft or config.current_status != 'ing': @@ -155,19 +156,21 @@ def run(self) -> None: video.hebing() except Exception as e: err=f'{config.transobj["hebingchucuo"]}:' + str(e) - config.errorlist[video.btnkey]=err - set_process(err, 'error', btnkey=video.btnkey) + config.errorlist[video.init['btnkey']]=err + set_process(err, 'error', btnkey=video.init['btnkey']) continue try: if config.exit_soft or config.current_status != 'ing': return self.stop() video.move_at_end() except Exception as e: - err=f'{config.transobj["hebingchucuo"]}:' + str(e) - config.errorlist[video.btnkey]=err - set_process(err, 'error', btnkey=video.btnkey) + config.errorlist[video.init['btnkey']]=err + set_process(err, 'error', btnkey=video.init['btnkey']) + send_notification(err, f'{video.obj["raw_basename"]}') continue + else: + send_notification("Succeed", f'{video.obj["raw_basename"]}') # 批量进入等待 if self.is_batch: return self.wait_end() diff --git a/videotrans/task/play_audio.py b/videotrans/task/play_audio.py index e08052bd..2e21421a 100644 --- a/videotrans/task/play_audio.py +++ b/videotrans/task/play_audio.py @@ -17,7 +17,16 @@ def __init__(self,obj,parent=None): def run(self): try: if not tools.vail_file(self.obj['voice_file']): - text_to_speech(text=self.obj['text'],role=self.obj['role'],tts_type=config.params['tts_type'],filename=self.obj['voice_file'], play=True,language=self.obj['language'],is_test=True) + print(self.obj) + text_to_speech( + text=self.obj['text'], + role=self.obj['role'], + tts_type=config.params['tts_type'], + filename=self.obj['voice_file'], + play=True, + volume=self.obj['volume'], + pitch=self.obj['pitch'], + language=self.obj['language']) else: pygameaudio(self.obj['voice_file']) except Exception as e: diff --git a/videotrans/task/step.py b/videotrans/task/step.py new file mode 100644 index 00000000..5acaf073 --- /dev/null +++ b/videotrans/task/step.py @@ -0,0 +1,1177 @@ +import copy +import hashlib +import math +import os +import re +import shutil +import textwrap +import time +from pathlib import Path + +from pydub import AudioSegment + +from videotrans import translator +from videotrans.configure import config +from videotrans.util import tools +from videotrans.recognition import run as run_recogn +from videotrans.translator import run as run_trans +from videotrans.tts import run as run_tts + + +class Runstep(): + + def __init__(self, init=None, obj=None, config_params=None, parent=None): + self.init = init + self.obj = obj + self.config_params = config_params + self.precent = 1 + self.parent=parent + + def _unlink(self, file): + try: + Path(file).unlink(missing_ok=True) + except Exception: + pass + + # 开始识别出字幕 + def recogn(self): + self.precent += 3 + tools.set_process(config.transobj["kaishishibie"], btnkey=self.init['btnkey']) + # 如果不存在视频,或存在已识别过的,或存在目标语言字幕 或合并模式,不需要识别 + if self.config_params['app_mode'] in ['hebing', 'peiyin']: + self._unlink(self.init['shibie_audio']) + return True + if self._srt_vail(self.init['source_sub']): + # 判断已存在的字幕文件中是否存在有效字幕纪录 + if self.obj and self.obj['output'] != self.obj['linshi_output']: + shutil.copy2(self.init['source_sub'], f"{self.obj['output']}/{Path(self.init['source_sub']).name}") + self._unlink(self.init['shibie_audio']) + return True + + # 分离未完成,需等待 + while not tools.vail_file(self.init['source_wav']): + tools.set_process(config.transobj["running"], btnkey=self.init['btnkey']) + time.sleep(1) + # 识别为字幕 + try: + self.precent += 5 + raw_subtitles = run_recogn( + # faster-whisper openai-whisper googlespeech + model_type=self.config_params['model_type'], + # 整体 预先 均等 + type=self.config_params['whisper_type'], + # 模型名 + model_name=self.config_params['whisper_model'], + # 识别音频 + audio_file=self.init['shibie_audio'], + detect_language=self.init['detect_language'], + cache_folder=self.init['cache_folder'], + is_cuda=self.config_params['cuda'], + inst=self) + self._unlink(self.init['shibie_audio']) + except Exception as e: + msg = f'{str(e)}{str(e.args)}' + if re.search(r'cub[a-zA-Z0-9_.-]+?\.dll', msg, re.I | re.M) is not None: + msg = f'【缺少cuBLAS.dll】请点击菜单栏-帮助/支持-下载cublasxx.dll,或者切换为openai模型 ' if config.defaulelang == 'zh' else f'[missing cublasxx.dll] Open menubar Help&Support->Download cuBLASxx.dll or use openai model' + elif re.search(r'out\s+?of.*?memory',msg,re.I): + msg=f'显存不足,请使用较小模型,比如 tiny/base/small' if config.defaulelang=='zh' else 'Insufficient video memory, use a smaller model such as tiny/base/small' + raise Exception(f'{msg}') + else: + if not raw_subtitles or len(raw_subtitles) < 1: + raise Exception( + self.obj['raw_basename'] + config.transobj['recogn result is empty'].replace('{lang}', + self.config_params[ + 'source_language'])) + self._save_srt_target(raw_subtitles, self.init['source_sub']) + + if self.obj and self.obj['output'] != self.obj['linshi_output']: + shutil.copy2(self.init['source_sub'], f"{self.obj['output']}/{Path(self.init['source_sub']).name}") + # 仅提取字幕 + if self.config_params['app_mode'] == 'tiqu': + shutil.copy2(self.init['source_sub'], f"{self.obj['output']}/{self.obj['raw_noextname']}.srt") + + return True + + # 字幕是否存在并且有效 + def _srt_vail(self, file): + if not tools.vail_file(file): + return False + try: + tools.get_subtitle_from_srt(file) + except Exception: + self._unlink(file) + return False + return True + + # 翻译字幕 + def trans(self): + self.precent += 3 + # 是否需要翻译,不是 hebing,存在识别后字幕并且不存在目标语言字幕,并且原语言和目标语言不同,则需要翻译 + if self.config_params['app_mode'] in ['hebing'] or \ + self.config_params['target_language'] == '-' or \ + self.config_params['target_language'] == self.config_params[ + 'source_language'] or not tools.vail_file(self.init['source_sub']): + return True + + config.task_countdown = 0 if self.config_params['app_mode'] == 'biaozhun_jd' else config.settings[ + 'countdown_sec'] + + # 如果存在目标语言字幕,前台直接使用该字幕替换 + if self._srt_vail(self.init['target_sub']): + # 判断已存在的字幕文件中是否存在有效字幕纪录 + # 通知前端替换字幕 + with open(self.init['target_sub'], 'r', encoding="utf-8", errors="ignore") as f: + tools.set_process(f.read().strip(), 'replace_subtitle', btnkey=self.init['btnkey']) + if self.obj and self.obj['output'] != self.obj['linshi_output']: + shutil.copy2(self.init['target_sub'], + f"{self.obj['output']}/{Path(self.init['target_sub']).name}") + return True + + # 批量不允许修改字幕 + if not self.config_params['is_batch']: + # 等待编辑原字幕后翻译,允许修改字幕 + tools.set_process(config.transobj["xiugaiyuanyuyan"], 'edit_subtitle', btnkey=self.init['btnkey']) + while config.task_countdown > 0: + config.task_countdown -= 1 + if config.task_countdown <= config.settings['countdown_sec']: + tools.set_process(f"{config.task_countdown} {config.transobj['jimiaohoufanyi']}", 'show_djs', + btnkey=self.init['btnkey']) + time.sleep(1) + + # 禁止修改字幕 + tools.set_process('translate_start', 'timeout_djs', btnkey=self.init['btnkey']) + time.sleep(2) + + # 如果已存在目标语言字幕则跳过,比如使用已有字幕,无需翻译时 + if self._srt_vail(self.init['target_sub']): + if self.obj and self.obj['output'] != self.obj['linshi_output'] and tools.vail_file( + self.init['target_sub']): + shutil.copy2(self.init['target_sub'], f"{self.obj['output']}/{Path(self.init['target_sub']).name}") + return True + tools.set_process(config.transobj['starttrans'], btnkey=self.init['btnkey']) + # 开始翻译,从目标文件夹读取原始字幕 + rawsrt = tools.get_subtitle_from_srt(self.init['source_sub'], is_file=True) + if not rawsrt or len(rawsrt) < 1: + raise Exception(f'{self.obj["raw_basename"]}' + config.transobj['No subtitles file']) + # 开始翻译,禁止修改字幕 + try: + target_srt = run_trans( + translate_type=self.config_params['translate_type'], + text_list=rawsrt, + target_language_name=self.config_params['target_language'], + set_p=True, + inst=self, + source_code=self.init['source_language_code']) + except Exception as e: + raise Exception(e) + else: + self._save_srt_target(target_srt, self.init['target_sub']) + + if self.obj and self.obj['output'] != self.obj['linshi_output']: + shutil.copy2(self.init['target_sub'], f"{self.obj['output']}/{Path(self.init['target_sub']).name}") + # 仅提取,该名字删原 + if self.config_params['app_mode'] == 'tiqu': + shutil.copy2(self.init['target_sub'], + f"{self.obj['output']}/{self.obj['raw_noextname']}-{self.init['target_language_code']}.srt") + + return True + + # 配音处理 + def dubbing(self): + self.precent += 3 + config.task_countdown = 0 if self.config_params['app_mode'] == 'biaozhun_jd' else config.settings[ + 'countdown_sec'] + if self.config_params['app_mode'] in ['tiqu']: + + return True + + # 不需要配音 + if self.config_params['app_mode'] in ['hebing'] or \ + self.config_params['voice_role'] == 'No' or \ + not tools.vail_file(self.init['target_sub']): + + return True + if tools.vail_file(self.init['target_wav']): + if self.obj and self.obj['output'] != self.obj['linshi_output']: + shutil.copy2(self.init['target_wav'], + f"{self.obj['output']}/{Path(self.init['target_wav']).name}") + return True + # 允许修改字幕 + if not self.config_params['is_batch']: + tools.set_process(config.transobj["xiugaipeiyinzimu"], "edit_subtitle", btnkey=self.init['btnkey']) + while config.task_countdown > 0: + # 其他情况,字幕处理完毕,未超时,等待1s,继续倒计时 + time.sleep(1) + # 倒计时中 + config.task_countdown -= 1 + if config.task_countdown <= config.settings['countdown_sec']: + tools.set_process(f"{config.task_countdown}{config.transobj['zidonghebingmiaohou']}", + 'show_djs', + btnkey=self.init['btnkey']) + # 禁止修改字幕 + tools.set_process('dubbing_start', 'timeout_djs', btnkey=self.init['btnkey']) + tools.set_process(config.transobj['kaishipeiyin'], btnkey=self.init['btnkey']) + time.sleep(3) + try: + self._exec_tts(self._before_tts()) + except Exception as e: + raise Exception(e) + if self.obj and self.obj['output'] != self.obj['linshi_output'] and tools.vail_file( + self.init['target_wav']): + shutil.copy2(self.init['target_wav'], f"{self.obj['output']}/{Path(self.init['target_wav']).name}") + return True + + # 合并操作 + def hebing(self): + self.precent += 3 + # 视频 音频 字幕 合并 + if self.config_params['app_mode'] in ['tiqu', 'peiyin']: + return True + try: + self._compos_video() + except Exception as e: + raise Exception(e) + self.precent = 100 + return True + + def _merge_audio_segments(self, *, queue_tts=None, video_time=0): + merged_audio = AudioSegment.empty() + # start is not 0 + if queue_tts[0]['start_time'] > 0: + silence_duration = queue_tts[0]['start_time'] + silence = AudioSegment.silent(duration=silence_duration) + merged_audio += silence + # join + offset = 0 + for i, it in enumerate(queue_tts): + tools.set_process(text=f"audio concat:{i}",btnkey=self.init['btnkey']) + it['raw_duration'] = it['end_time'] - it['start_time'] + if it['raw_duration'] == 0: + continue + if not tools.vail_file(it['filename']): + merged_audio += AudioSegment.silent(duration=it['raw_duration']) + continue + the_ext=it['filename'].split('.')[-1] + segment = AudioSegment.from_file(it['filename'], format="mp4" if the_ext=='m4a' else the_ext) + the_dur = len(segment) + # 字幕可用时间 + raw_dur = it['raw_duration'] + it['start_time'] += offset + it['end_time'] += offset + + diff = the_dur - raw_dur + # 配音大于字幕时长,后延,延长时间 + if diff > 0: + it['end_time'] += diff + offset += diff + else: + # 配音小于原时长,添加静音 + merged_audio += AudioSegment.silent(duration=abs(diff)) + + if i > 0: + silence_duration = it['start_time'] - queue_tts[i - 1]['end_time'] + # 前面一个和当前之间存在静音区间 + if silence_duration > 0: + silence = AudioSegment.silent(duration=silence_duration) + merged_audio += silence + if config.settings['force_edit_srt']: + it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) + it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) + else: + it['startraw'] = tools.ms_to_time_string(ms=it['start_time_source']) + it['endraw'] = tools.ms_to_time_string(ms=it['end_time_source']) + queue_tts[i] = it + merged_audio += segment + + # 移除尾部静音 + co2 = merged_audio + if config.settings['remove_silence'] or ( + video_time > 0 and merged_audio and (len(merged_audio) > video_time)): + merged_audio = tools.remove_silence_from_end(merged_audio, silence_threshold=-50.0, chunk_size=10, + is_start=False) + if isinstance(merged_audio, str): + merged_audio = co2 + + if video_time > 0 and merged_audio and (len(merged_audio) < video_time): + # 末尾补静音 + silence = AudioSegment.silent(duration=video_time - len(merged_audio)) + merged_audio += silence + + # 创建配音后的文件 + try: + wavfile = self.init['cache_folder'] + "/target.wav" + merged_audio.export(wavfile, format="wav") + + if self.config_params['app_mode']=='peiyin' and tools.vail_file(self.init['background_music']): + cmd = ['-y', '-i', wavfile, '-i', self.init['background_music'], '-filter_complex', + "[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2", '-ac', '2', + self.init['target_wav']] + tools.runffmpeg(cmd) + else: + tools.wav2m4a(wavfile, self.init['target_wav']) + except Exception as e: + raise Exception(f'[error]merged_audio:{str(e)}') + + return len(merged_audio), queue_tts + + # 保存字幕文件 到目标文件夹 + def _save_srt_target(self, srtstr, file): + # 是字幕列表形式,重新组装 + if isinstance(srtstr, list): + txt = "" + for it in srtstr: + startraw, endraw = it['time'].strip().split(" --> ") + startraw = startraw.strip().replace('.', ',') + endraw = endraw.strip().replace('.', ',') + startraw = tools.format_time(startraw, ',') + endraw = tools.format_time(endraw, ',') + txt += f"{it['line']}\n{startraw} --> {endraw}\n{it['text']}\n\n" + with open(file, 'w', encoding="utf-8") as f: + f.write(txt) + time.sleep(1) + tools.set_process(txt, 'replace_subtitle', btnkey=self.init['btnkey']) + return True + + # 配音预处理,去掉无效字符,整理开始时间 + def _before_tts(self): + # 整合一个队列到 exec_tts 执行 + if self.config_params['voice_role'] == 'No': + return True + queue_tts = [] + # 获取字幕 + try: + subs = tools.get_subtitle_from_srt(self.init['target_sub']) + if len(subs) < 1: + raise Exception("字幕格式不正确,请打开查看") + except Exception as e: + raise Exception(f'格式化字幕失败:{str(e)}') + rate = int(str(self.config_params['voice_rate']).replace('%', '')) + if rate >= 0: + rate = f"+{rate}%" + else: + rate = f"{rate}%" + # 取出设置的每行角色 + line_roles = self.config_params["line_roles"] if "line_roles" in self.config_params else None + # 取出每一条字幕,行号\n开始时间 --> 结束时间\n内容 + for i, it in enumerate(subs): + # 判断是否存在单独设置的行角色,如果不存在则使用全局 + voice_role = self.config_params['voice_role'] + if line_roles and f'{it["line"]}' in line_roles: + voice_role = line_roles[f'{it["line"]}'] + newrole = voice_role.replace('/', '-').replace('\\', '/') + filename = f'{i}-{newrole}-{self.config_params["voice_rate"]}-{self.config_params["voice_autorate"]}-{it["text"]}-{self.config_params["volume"].replace("%","")}-{self.config_params["pitch"]}' + md5_hash = hashlib.md5() + md5_hash.update(f"{filename}".encode('utf-8')) + # 要保存到的文件 + # clone-voice同时也是音色复制源 + filename = self.init['cache_folder'] + "/" + md5_hash.hexdigest() + ".mp3" + # 如果是clone-voice类型, 需要截取对应片段 + if it['end_time'] <= it['start_time']: + continue + if self.config_params['tts_type'] == 'clone-voice': + if self.config_params['is_separate'] and not tools.vail_file(self.init['vocal']): + raise Exception(f"背景分离出错 {self.init['vocal']}") + # clone 方式文件为wav格式 + if self.config_params['app_mode'] != 'peiyin' and tools.vail_file(self.init['source_wav']): + tools.cut_from_audio( + audio_file=self.init['vocal'] if self.config_params[ + 'is_separate'] else self.init['source_wav'], + ss=it['startraw'], + to=it['endraw'], + out_file=filename + ) + + queue_tts.append({ + "text": it['text'], + "role": voice_role, + "start_time": it['start_time'], + "end_time": it['end_time'], + "rate": rate, + "startraw": it['startraw'], + "endraw": it['endraw'], + "volume":self.config_params['volume'], + "pitch":self.config_params['pitch'], + "tts_type": self.config_params['tts_type'], + "filename": filename}) + return queue_tts + + # 1. 将每个配音的实际长度加入 dubb_time + def _add_dubb_time(self, queue_tts): + for i, it in enumerate(queue_tts): + it['video_add'] = 0 + tools.set_process(text=f"audio:{i}",btnkey=self.init['btnkey']) + # 防止开始时间比上个结束时间还小 + if i > 0 and it['start_time'] < queue_tts[i - 1]['end_time']: + it['start_time'] = queue_tts[i - 1]['end_time'] + # 防止结束时间小于开始时间 + if it['end_time'] < it['start_time']: + it['end_time'] = it['start_time'] + # 保存原始 + it['start_time_source'] = it['start_time'] + it['end_time_source'] = it['end_time'] + # 记录原字母区间时长 + it['raw_duration'] = it['end_time'] - it['start_time'] + + if it['end_time'] > it['start_time'] and tools.vail_file(it['filename']): + the_ext=it['filename'].split('.')[-1] + it['dubb_time'] = len(AudioSegment.from_file(it['filename'], format="mp4" if the_ext=='m4a' else the_ext)) + else: + # 不存在配音 + it['dubb_time'] = 0 + queue_tts[i] = it + + return queue_tts + + # 2. 移除原字幕多于配音的时长,实际是字幕结束时间向前移动,和下一条之间的空白更加多了 + def _remove_srt_silence(self, queue_tts): + # 如果需要移除多出来的静音 + for i, it in enumerate(queue_tts): + # 配音小于 原时长,移除默认静音 + if it['dubb_time'] > 0 and it['dubb_time'] < it['raw_duration']: + diff = it['raw_duration'] - it['dubb_time'] + it['end_time'] -= diff + it['raw_duration'] = it['dubb_time'] + queue_tts[i] = it + return queue_tts + + # 3. 自动后延或前延以对齐 + def _auto_ajust(self, queue_tts): + max_index = len(queue_tts) - 1 + + for i, it in enumerate(queue_tts): + tools.set_process(text=f"ajust:{i}",btnkey=self.init['btnkey']) + # 如果存在配音文件并且时长大于0,才需要判断是否顺延 + if "dubb_time" not in it and it['dubb_time'] <= 0: + continue + # 配音时长如果大于原时长,才需要两侧延伸 + diff = it['dubb_time'] - it['raw_duration'] + if diff <= 0: + continue + # 需要两侧延伸 + + # 最后一个,直接后延就可以 + if i == max_index: + # 如果是最后一个,直接延长 + it['end_time'] += diff + it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) + # 重新设定可用的字幕区间时长 + it['raw_duration'] = it['end_time'] - it['start_time'] + queue_tts[i] = it + continue + + # 判断后边的开始时间比当前结束时间是否大于 + next_diff = queue_tts[i + 1]['start_time'] - it['end_time'] + if next_diff >= diff: + # 如果大于0,有空白,添加 + it['end_time'] += diff + it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) + it['raw_duration'] = it['end_time'] - it['start_time'] + queue_tts[i] = it + continue + + # 防止出错 + next_diff = 0 if next_diff < 0 else next_diff + # 先向后延伸占完空白,然后再向前添加, + it['end_time'] += next_diff + # 判断是否存在前边偏移 + if it['start_time'] > 0: + # 前面空白 + prev_diff = it['start_time'] if i == 0 else it['start_time'] - queue_tts[i - 1]['end_time'] + # 前面再添加最多 diff - next_diff + it['start_time'] -= min(prev_diff, diff - next_diff) + it['start_time'] = 0 if it['start_time'] < 0 else it['start_time'] + it['raw_duration'] = it['end_time'] - it['start_time'] + it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) + it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) + queue_tts[i] = it + return queue_tts + + # 移除2个字幕间的间隔 config.settings[remove_white_ms] ms + def _remove_white_ms(self, queue_tts): + offset = 0 + for i, it in enumerate(queue_tts): + if i > 0: + it['start_time'] -= offset + it['end_time'] -= offset + # 配音小于 原时长,移除默认静音 + dt = it['start_time'] - queue_tts[i - 1]['end_time'] + if dt > config.settings['remove_white_ms']: + diff = config.settings['remove_white_ms'] + it['end_time'] -= diff + it['start_time'] -= diff + offset += diff + queue_tts[i] = it + return queue_tts + + # 2. 先对配音加速,每条字幕信息中写入加速倍数 speed和延长的时间 add_time + def _ajust_audio(self, queue_tts): + # 遍历所有字幕条, 计算应该的配音加速倍数和延长的时间 + + # 设置加速倍数 + for i, it in enumerate(queue_tts): + it['speed'] = 0 + # 存在配音时进行处理 没有配音 + if it['dubb_time'] <= 0: + queue_tts[i] = it + continue + it['raw_duration'] = it['end_time'] - it['start_time'] + # 配音时长 不大于 原时长,不处理 + if it['raw_duration'] <= 0 or it['dubb_time'] <= it['raw_duration']: + queue_tts[i] = it + continue + it['speed'] = 1 + queue_tts[i] = it + + # 再次遍历,调整字幕开始结束时间对齐实际音频时长 + # 每次 start_time 和 end_time 需要添加的长度 offset 为当前所有 add_time 之和 + offset = 0 + for i, it in enumerate(queue_tts): + jindu = (len(queue_tts) * 10) / (i + 1) + if self.precent + jindu < 95: + self.precent += jindu + # 偏移增加 + it['start_time'] += offset + # 结束时间还需要额外添加 + it['end_time'] += offset + + if it['speed'] < 1 or config.settings['audio_rate'] <= 1: + # 不需要加速 + it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) + it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) + queue_tts[i] = it + continue + + tools.set_process(f"{config.transobj['dubbing speed up']} [{i}]", + btnkey=self.init['btnkey']) + if tools.vail_file(it['filename']): + # 如果同时有视频加速,则配音压缩为原时长 + 差额的一半 + if config.settings['video_rate'] > 1: + half = int((it['dubb_time'] - it['raw_duration']) / 2) + else: + half = 0 + # 调整音频 + + tmp_mp3 = f'{it["filename"]}-speed.mp3' + tools.precise_speed_up_audio(file_path=it['filename'], out=tmp_mp3, + target_duration_ms=it['raw_duration'] + half, + max_rate=min(config.settings['audio_rate'], 100)) + + # 加速后时间 + if tools.vail_file(tmp_mp3): + mp3_len = len(AudioSegment.from_file(tmp_mp3, format="mp3")) + else: + mp3_len = 0 + it['raw_duration'] = it['end_time'] - it['start_time'] + it['filename'] = tmp_mp3 + + # 更改时间戳 + it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) + it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) + queue_tts[i] = it + return queue_tts + + # 视频慢速 在配音加速调整后,根据字幕实际开始结束时间,裁剪视频,慢速播放实现对齐 + def _ajust_video(self, queue_tts): + if not self.config_params['video_autorate'] or config.settings['video_rate'] <= 1: + return queue_tts + # 计算视频应该慢放的倍数,用当前实际的字幕时长/原始字幕时长得到倍数,如果当前时长小于等于原时长,不处理 + # 开始遍历每个时间段,如果需要视频加速,则截取 end_time_source start_time_source 时间段的视频,进行speed_video 处理 + concat_txt_arr = [] + if not tools.is_novoice_mp4(self.init['novoice_mp4'], self.init['noextname']): + raise Exception("not novoice mp4") + last_time = tools.get_video_duration(self.init['novoice_mp4']) + self.parent.status_text=config.transobj['videodown..'] + for i, it in enumerate(queue_tts): + jindu = (len(queue_tts) * 10) / (i + 1) + if self.precent + jindu < 95: + self.precent += jindu + # 如果i==0即第一个视频,前面若是还有片段,需要截取 + if i == 0: + if it['start_time_source'] > 0: + before_dst = self.init['cache_folder'] + f'/{i}-before.mp4' + tools.cut_from_video(ss='00:00:00.000', + to=tools.ms_to_time_string(ms=it['start_time_source']), + source=self.init['novoice_mp4'], + out=before_dst) + concat_txt_arr.append(before_dst) + elif it['start_time_source'] > queue_tts[i - 1]['end_time_source'] and it[ + 'start_time_source'] < last_time: + # 否则如果距离前一个字幕结束之间还有空白,则将此空白视频段截取 + before_dst = self.init['cache_folder'] + f'/{i}-before.mp4' + tools.cut_from_video(ss=tools.ms_to_time_string(ms=queue_tts[i - 1]['end_time_source']), + to=tools.ms_to_time_string(ms=it['start_time_source']), + source=self.init['novoice_mp4'], + out=before_dst) + concat_txt_arr.append(before_dst) + # 当前可用时间段 + duration = it['end_time_source'] - it['start_time_source'] + audio_length = duration + # 实际配音长度 + if tools.vail_file(it['filename']): + audio_length = len(AudioSegment.from_file(it['filename'], format="mp3")) + + # 需要延长视频 + if duration > 0 and audio_length > duration: + filename_video = self.init['cache_folder'] + f'/{i}.mp4' + speed = round(audio_length / duration, 3) + if speed <= 1: + speed = 1 + else: + speed = min(20, config.settings['video_rate'], speed) + + + # 截取原始视频 + if it['end_time_source'] > it['start_time_source'] and it['start_time_source'] < last_time: + tools.cut_from_video(ss=tools.ms_to_time_string(ms=it['start_time']), + to=tools.ms_to_time_string( + ms=it['end_time_source'] if it[ + 'end_time_source'] < last_time else last_time), + source=self.init['novoice_mp4'], + pts="" if speed <= 1 else speed, + out=filename_video) + concat_txt_arr.append(filename_video) + elif it['end_time_source'] > it['start_time_source'] and it['start_time_source'] < last_time: + filename_video = self.init['cache_folder'] + f'/{i}.mp4' + concat_txt_arr.append(filename_video) + # 直接截取原始片段,不慢放 + tools.cut_from_video(ss=tools.ms_to_time_string(ms=it['start_time_source']), + to=tools.ms_to_time_string( + ms=it['end_time_source'] if it[ + 'end_time_source'] < last_time else last_time), + source=self.init['novoice_mp4'], + out=filename_video) + tools.set_process(f"{config.transobj['video speed down']}[{i}]", btnkey=self.init['btnkey']) + if queue_tts[-1]['end_time_source'] < last_time: + last_v = self.init['cache_folder'] + "/last_dur.mp4" + tools.cut_from_video(ss=tools.ms_to_time_string(ms=queue_tts[-1]['end_time_source']), + source=self.init['novoice_mp4'], + out=last_v) + concat_txt_arr.append(last_v) + # 将所有视频片段连接起来 + new_arr = [] + for it in concat_txt_arr: + if tools.vail_file(it): + new_arr.append(it) + if len(new_arr) > 0: + tools.set_process(f"连接视频片段..." if config.defaulelang=='zh' else 'concat multi mp4 ...', btnkey=self.init['btnkey']) + tools.concat_multi_mp4(filelist=concat_txt_arr, out=self.init['novoice_mp4']) + return queue_tts + + def _exec_tts(self, queue_tts): + if not queue_tts or len(queue_tts) < 1: + raise Exception(f'Queue tts length is 0') + # 具体配音操作 + try: + run_tts(queue_tts=copy.deepcopy(queue_tts), language=self.init['target_language_code'], set_p=True, + inst=self) + except Exception as e: + raise Exception(e) + + # 1.首先添加配音时间 + queue_tts = self._add_dubb_time(queue_tts) + + # 2.移除字幕多于配音的时间,实际上是字幕结束时间前移,和下一条字幕空白更多 + if config.settings['remove_srt_silence']: + queue_tts = self._remove_srt_silence(queue_tts) + + # 3.是否需要 前后延展 + if "auto_ajust" in self.config_params and self.config_params['auto_ajust']: + queue_tts = self._auto_ajust(queue_tts) + + # 5.从字幕间隔移除多余的毫秒数 + if config.settings['remove_white_ms'] > 0: + queue_tts = self._remove_white_ms(queue_tts) + + # 4. 如果需要配音加速 + if self.config_params['voice_autorate'] and config.settings['audio_rate'] > 1: + queue_tts = self._ajust_audio(queue_tts) + + # 如果仅需配音 + if self.config_params['app_mode'] == 'peiyin': + segments = [] + start_times = [] + for i, it in enumerate(queue_tts): + if it['dubb_time'] > 0 and tools.vail_file(it['filename']): + the_ext=it['filename'].split('.')[-1] + segments.append(AudioSegment.from_file(it['filename'], format="mp4" if the_ext=='m4a' else the_ext)) + start_times.append(it['start_time']) + else: + segments.append(AudioSegment.silent(duration=it['end_time'] - it['start_time'])) + self._merge_audio_segments(queue_tts=queue_tts) + return True + + # 6.处理视频慢速 + if self.config_params['video_autorate'] and config.settings['video_rate'] > 1: + queue_tts = self._ajust_video(queue_tts) + + # 获取 novoice_mp4的长度 + if not tools.is_novoice_mp4(self.init['novoice_mp4'], self.init['noextname']): + raise Exception("not novoice mp4") + video_time = tools.get_video_duration(self.init['novoice_mp4']) + audio_length, queue_tts = self._merge_audio_segments( + video_time=video_time, + queue_tts=copy.deepcopy(queue_tts)) + + # 更新字幕 + srt = "" + for (idx, it) in enumerate(queue_tts): + srt += f"{idx + 1}\n{it['startraw']} --> {it['endraw']}\n{it['text']}\n\n" + # 字幕保存到目标文件夹 + with open(self.init['target_sub'], 'w', encoding="utf-8", errors="ignore") as f: + f.write(srt.strip()) + + return True + + # 延长 novoice.mp4 duration_ms 毫秒 + def _novoicemp4_add_time(self, duration_ms): + if duration_ms < 100: + return + tools.set_process(f'{config.transobj["shipinmoweiyanchang"]} {duration_ms}ms', btnkey=self.init['btnkey']) + if not tools.is_novoice_mp4(self.init['novoice_mp4'], self.init['noextname']): + raise Exception("not novoice mp4") + + video_time = tools.get_video_duration(self.init['novoice_mp4']) + + # 开始将 novoice_mp4 和 last_clip 合并 + shutil.copy2(self.init['novoice_mp4'], f"{self.init['novoice_mp4']}.raw.mp4") + + tools.cut_from_video( + source=self.init['novoice_mp4'], + ss=tools.ms_to_time_string(ms=video_time - duration_ms).replace(',', '.'), + out=self.init['cache_folder'] + "/last-clip-novoice.mp4", + pts=10, + fps=None if not self.init['video_info'] or not self.init['video_info']['video_fps'] else + self.init['video_info']['video_fps'] + ) + + clip_time = tools.get_video_duration(self.init['cache_folder'] + "/last-clip-novoice.mp4") + + nums = math.ceil(duration_ms / clip_time) + nums += math.ceil(nums / 3) + tools.concat_multi_mp4( + filelist=[self.init['cache_folder'] + "/last-clip-novoice.mp4" for x in range(nums)], + out=self.init['cache_folder'] + "/last-clip-novoice-all.mp4", + fps=None if not self.init['video_info'] or not self.init['video_info']['video_fps'] else + self.init['video_info']['video_fps'] + ) + + tools.concat_multi_mp4( + filelist=[f"{self.init['novoice_mp4']}.raw.mp4", + self.init['cache_folder'] + "/last-clip-novoice-all.mp4"], + out=self.init['novoice_mp4'], + maxsec=math.ceil((video_time + duration_ms) / 1000), + fps=None if not self.init['video_info'] or not self.init['video_info']['video_fps'] else + self.init['video_info']['video_fps'] + ) + Path(f"{self.init['novoice_mp4']}.raw.mp4").unlink(missing_ok=True) + return True + + # 添加背景音乐 + def _back_music(self): + if self.config_params['app_mode'] not in ["hebing", "tiqu", "peiyin"] and self.config_params[ + 'voice_role'] != 'No' and tools.vail_file(self.init['target_wav']) and tools.vail_file( + self.init['background_music']): + try: + # 获取视频长度 + vtime = tools.get_video_info(self.init['novoice_mp4'], video_time=True) + vtime /= 1000 + # 获取音频长度 + atime = tools.get_audio_time(self.init['background_music']) + # 转为m4a + if not self.init['background_music'].lower().endswith('.m4a'): + tmpm4a = self.init['cache_folder'] + f"/background_music-1.m4a" + tools.wav2m4a(self.init['background_music'], tmpm4a) + self.init['background_music'] = tmpm4a + beishu = vtime / atime + if config.settings['loop_backaudio'] and beishu > 1 and vtime - 1 > atime: + beishu = int(beishu) + # 获取延长片段 + # 背景音频连接延长片段 + tools.concat_multi_audio(filelist=[self.init['background_music'] for n in range(beishu + 1)], + out=self.init['cache_folder'] + "/background_music-2.m4a") + self.init['background_music'] = self.init['cache_folder'] + "/background_music-2.m4a" + # 背景音频降低音量 + tools.runffmpeg( + ['-y', '-i', self.init['background_music'], "-filter:a", + f"volume={config.settings['backaudio_volume']}", + '-c:a', 'aac', + self.init['cache_folder'] + f"/background_music-3.m4a"]) + # 背景音频和配音合并 + cmd = ['-y', '-i', self.init['target_wav'], '-i', + self.init['cache_folder'] + f"/background_music-3.m4a", + '-filter_complex', "[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2", '-ac', '2', + self.init['cache_folder'] + f"/lastend.m4a"] + tools.runffmpeg(cmd) + self.init['target_wav'] = self.init['cache_folder'] + f"/lastend.m4a" + except Exception as e: + config.logger.error(f'添加背景音乐失败:{str(e)}') + + def _separate(self): + if self.config_params['is_separate'] and tools.vail_file(self.init['target_wav']): + try: + # 原始背景音乐 wav,和配音后的文件m4a合并 + # 获取视频长度 + vtime = tools.get_video_info(self.init['novoice_mp4'], video_time=True) + vtime /= 1000 + # 获取音频长度 + atime = tools.get_audio_time(self.init['instrument']) + if config.settings['loop_backaudio'] and atime + 1 < vtime: + # 延长背景音 + cmd = ['-y', '-i', self.init['instrument'], '-ss', '00:00:00.000', '-t', + f'{vtime - atime}', self.init['cache_folder'] + "/yanchang.m4a"] + tools.runffmpeg(cmd) + # 背景音连接延长片段 + tools.concat_multi_audio( + filelist=[self.init['instrument'], self.init['cache_folder'] + "/yanchang.m4a"], + out=self.init['cache_folder'] + f"/instrument-2.m4a") + + self.init['instrument'] = self.init['cache_folder'] + f"/instrument-2.m4a" + # 背景音合并配音 + tools.backandvocal(self.init['instrument'], self.init['target_wav']) + except Exception as e: + config.logger.error('合并原始背景失败' + config.transobj['Error merging background and dubbing'] + str(e)) + + # 最终合成视频 source_mp4=原始mp4视频文件,noextname=无扩展名的视频文件名字 + def _compos_video(self): + if self.config_params['app_mode'] in ['tiqu', 'peiyin']: + return True + # 判断novoice_mp4是否完成 + if not tools.is_novoice_mp4(self.init['novoice_mp4'], self.init['noextname']): + raise Exception(config.transobj['fenlinoviceerror']) + + # 需要字幕 + if self.config_params['subtitle_type'] > 0 and not tools.vail_file(self.init['target_sub']): + raise Exception(f"{config.transobj['No subtitles file']}: {self.init['target_sub']}") + + if self.precent < 90: + self.precent = 90 + # 存放目标字幕 + target_sub_list = [] + # 存放原始字幕 + source_sub_list = [] + if self.config_params['subtitle_type'] > 0: + try: + target_sub_list = tools.get_subtitle_from_srt(self.init['target_sub']) + except Exception as e: + raise Exception(f'{config.transobj["Subtitles error"]}-1 :{str(e)}') + if self.config_params['subtitle_type'] in [3, 4] and tools.vail_file(self.init['source_sub']): + try: + source_sub_list = tools.get_subtitle_from_srt(self.init['source_sub']) + except Exception as e: + raise Exception(f'{config.transobj["Subtitles error"]}-1 :{str(e)}') + + # 无声音视频 或 合并模式时原视频 + novoice_mp4_path = Path(self.init['novoice_mp4']) + novoice_mp4 = os.path.normpath(self.init['novoice_mp4']) + # 视频目录,用于硬字幕时进入工作目录 + mp4_dirpath = novoice_mp4_path.parent.resolve() + + # 软字幕 完整路径 + soft_srt = os.path.normpath(self.init['target_sub']) + + # 硬字幕仅名字 需要和视频在一起 + hard_srt = "tmp.srt" + hard_srt_path = Path(mp4_dirpath / hard_srt) + fontsize = f":force_style=Fontsize={config.settings['fontsize']}" if config.settings['fontsize'] > 0 else "" + maxlen = config.settings['cjk_len'] if self.init['target_language_code'][:2] in ["zh", "ja", "jp", + "ko"] else \ + config.settings['other_len'] + maxlen_source = config.settings['cjk_len'] if self.init['source_language_code'][:2] in ["zh", "ja", "jp", + "ko"] else \ + config.settings['other_len'] + + if self.precent < 90: + self.precent = 90 + + # 需要硬字幕 + if self.config_params['subtitle_type'] in [1, 3]: + text = "" + for i, it in enumerate(target_sub_list): + it['text'] = textwrap.fill(it['text'], maxlen) + text += f"{it['line']}\n{it['time']}\n{it['text'].strip()}\n\n" + hard_srt_path.write_text(text, encoding='utf-8', errors="ignore") + os.chdir(mp4_dirpath) + + # 如果是合并字幕模式 + if self.config_params['app_mode'] == 'hebing': + if self.config_params['subtitle_type'] in [1, 3]: + tools.runffmpeg([ + "-y", + "-i", + novoice_mp4, + "-c:v", + "libx264", + "-vf", + f"subtitles={hard_srt}{fontsize}", + '-crf', + f'{config.settings["crf"]}', + '-preset', + 'slow', + os.path.normpath(self.init['targetdir_mp4']), + ], de_format="nv12") + else: + # 软字幕 + tools.runffmpeg([ + "-y", + "-i", + novoice_mp4, + "-i", + soft_srt, + "-c:v", + "copy" if self.init['h264'] else "libx264", + "-c:s", + "mov_text", + "-metadata:s:s:0", + f"language=eng", + os.path.normpath(self.init['targetdir_mp4']) + ]) + self.precent = 100 + try: + novoice_mp4_path.unlink(missing_ok=True) + hard_srt_path.unlink(missing_ok=True) + except Exception: + pass + return True + # 需要配音但没有配音文件 + if self.config_params['voice_role'] != 'No' and not tools.vail_file(self.init['target_wav']): + raise Exception(f"{config.transobj['Dubbing']}{config.transobj['anerror']}:{self.init['target_wav']}") + + # 需要双字幕 + if self.init['source_language_code'] != self.init['target_language_code'] and len(source_sub_list) > 0: + # 双字幕 硬字幕 + if self.config_params['subtitle_type'] == 3: + text = "" + source_length = len(source_sub_list) + for i, it in enumerate(target_sub_list): + it['text'] = textwrap.fill(it['text'], maxlen) + text += f"{it['line']}\n{it['time']}\n{it['text'].strip()}" + if source_length > 0 and i < source_length: + text += "\n" + textwrap.fill(source_sub_list[i]['text'], maxlen_source).strip() + text += "\n\n" + hard_srt_path.write_text(text.strip(), encoding="utf-8", errors="ignore") + os.chdir(mp4_dirpath) + shutil.copy2(hard_srt_path.as_posix(), f"{self.obj['output']}/shuang.srt") + + # 双字幕 软字幕 + elif self.config_params['subtitle_type'] == 4: + text = "" + for i, it in enumerate(target_sub_list): + text += f"{it['line']}\n{it['time']}\n{it['text'].strip()}" + if i < len(source_sub_list): + text += f"\n{source_sub_list[i]['text'].strip()}" + text += "\n\n" + # 软字幕双 + soft_srt = self.obj['linshi_output'] + "/shuang.srt" + shutil.copy2(self.init['target_sub'], soft_srt) + with open(soft_srt, 'w', encoding="utf-8", errors="ignore") as f: + f.write(text.strip()) + soft_srt = os.path.normpath(soft_srt) + + # 分离背景音和添加背景音乐 + self._back_music() + self._separate() + # 有配音 延长视频或音频对齐 + if self.config_params['voice_role'] != 'No' and self.config_params['append_video']: + video_time = tools.get_video_duration(novoice_mp4) + try: + audio_length=int(tools.get_audio_time(self.init['target_wav'])*1000) + except Exception: + audio_length=0 + if audio_length>0 and audio_length > video_time: + try: + # 先对音频末尾移除静音 + tools.remove_silence_from_end(self.init['target_wav'],is_start=False) + audio_length=int(tools.get_audio_time(self.init['target_wav'])*1000) + except Exception: + audio_length=0 + if audio_length>0 and audio_length > video_time: + # 视频末尾延长 + try: + # 对视频末尾定格延长 + self._novoicemp4_add_time(audio_length - video_time) + except Exception as e: + raise Exception(f'{config.transobj["moweiyanchangshibai"]}:{str(e)}') + elif audio_length>0 and video_time > audio_length: + ext=self.init['target_wav'].split('.')[-1] + m = AudioSegment.from_file( + self.init['target_wav'], + format="mp4" if ext=='m4a' else ext) + AudioSegment.silent( + duration=video_time - audio_length) + m.export(self.init['target_wav'], format="mp4" if ext=='m4a' else ext) + try: + subtitle_language = translator.get_subtitle_code(show_target=self.config_params['target_language']) + # 有配音有字幕 + if self.config_params['voice_role'] != 'No' and self.config_params['subtitle_type'] > 0: + if self.config_params['subtitle_type'] in [1, 3]: + tools.set_process(config.transobj['peiyin-yingzimu'], btnkey=self.init['btnkey']) + # 需要配音+硬字幕 + tools.runffmpeg([ + "-y", + "-i", + novoice_mp4, + "-i", + os.path.normpath(self.init['target_wav']), + "-c:v", + "libx264", + "-c:a", + "aac", + "-vf", + f"subtitles={hard_srt}{fontsize}", + '-crf', + f'{config.settings["crf"]}', + '-preset', + 'slow', + os.path.normpath(self.init['targetdir_mp4']), + ], de_format="nv12") + else: + tools.set_process(config.transobj['peiyin-ruanzimu'], btnkey=self.init['btnkey']) + # 配音+软字幕 + tools.runffmpeg([ + "-y", + "-i", + novoice_mp4, + "-i", + os.path.normpath(self.init['target_wav']), + "-i", + soft_srt, + "-c:v", + "copy", + "-c:a", + "aac", + "-c:s", + "mov_text", + "-metadata:s:s:0", + f"language={subtitle_language}", + os.path.normpath(self.init['targetdir_mp4']) + ]) + elif self.config_params['voice_role'] != 'No': + # 有配音无字幕 + tools.set_process(config.transobj['onlypeiyin'], btnkey=self.init['btnkey']) + tools.runffmpeg([ + "-y", + "-i", + novoice_mp4, + "-i", + os.path.normpath(self.init['target_wav']), + "-c:v", + "copy", + "-c:a", + "aac", + os.path.normpath(self.init['targetdir_mp4']) + ]) + # 硬字幕无配音 原始 wav合并 + elif self.config_params['subtitle_type'] in [1, 3]: + tools.set_process(config.transobj['onlyyingzimu'], btnkey=self.init['btnkey']) + cmd = [ + "-y", + "-i", + novoice_mp4 + ] + if tools.vail_file(self.init['source_wav']): + cmd.append('-i') + cmd.append(os.path.normpath(self.init['source_wav'])) + + cmd.append('-c:v') + cmd.append('libx264') + if tools.vail_file(self.init['source_wav']): + cmd.append('-c:a') + cmd.append('aac') + cmd += [ + "-vf", + f"subtitles={hard_srt}{fontsize}", + '-crf', + f'{config.settings["crf"]}', + '-preset', + 'slow', + os.path.normpath(self.init['targetdir_mp4']), + ] + tools.runffmpeg(cmd, de_format="nv12") + elif self.config_params['subtitle_type'] in [2, 4]: + # 软字幕无配音 + tools.set_process(config.transobj['onlyruanzimu'], btnkey=self.init['btnkey']) + # 原视频 + cmd = [ + "-y", + "-i", + novoice_mp4 + ] + # 原配音流 + if tools.vail_file(self.init['source_wav']): + cmd.append("-i") + cmd.append(os.path.normpath(self.init['source_wav'])) + # 目标字幕流 + cmd += [ + "-i", + soft_srt, + "-c:v", + "copy" + ] + if tools.vail_file(self.init['source_wav']): + cmd.append('-c:a') + cmd.append('aac') + cmd += [ + "-c:s", + "mov_text", + "-metadata:s:s:0", + f"language={subtitle_language}", + '-crf', + f'{config.settings["crf"]}', + '-preset', + 'slow', ] + cmd.append(os.path.normpath(self.init['targetdir_mp4'])) + tools.runffmpeg(cmd) + except Exception as e: + raise Exception(f'compose srt + video + audio:{str(e)}') + self.precent = 99 + try: + + if not self.config_params['only_video']: + with open(self.init['target_dir'] + f'/{"readme" if config.defaulelang != "zh" else "文件说明"}.txt', + 'w', encoding="utf-8", errors="ignore") as f: + f.write(f"""以下是可能生成的全部文件, 根据执行时配置的选项不同, 某些文件可能不会生成, 之所以生成这些文件和素材,是为了方便有需要的用户, 进一步使用其他软件进行处理, 而不必再进行语音导出、音视频分离、字幕识别等重复工作 + + +{os.path.basename(self.init['targetdir_mp4'])} = 最终完成的目标视频文件 +{self.init['source_language_code']}.m4a|.wav = 原始视频中的音频文件(包含所有背景音和人声) +{self.init['target_language_code']}.m4a = 配音后的音频文件(若选择了保留背景音乐则已混入) +{self.init['source_language_code']}.srt = 原始视频中根据声音识别出的字幕文件 +{self.init['target_language_code']}.srt = 翻译为目标语言后字幕文件 +shuang.srt = 双语字幕 +vocal.wav = 原始视频中分离出的人声音频文件 +instrument.wav = 原始视频中分离出的背景音乐音频文件 + + +如果觉得该项目对你有价值,并希望该项目能一直稳定持续维护,欢迎各位小额赞助,有了一定资金支持,我将能够持续投入更多时间和精力 +捐助地址:https://github.com/jianchang512/pyvideotrans/issues/80 + +==== + +Here are the descriptions of all possible files that might exist. Depending on the configuration options when executing, some files may not be generated. + +{os.path.basename(self.init['targetdir_mp4'])} = The final completed target video file +{self.init['source_language_code']}.m4a|.wav = The audio file in the original video (containing all sounds) +{self.init['target_language_code']}.m4a = The dubbed audio file (if you choose to keep the background music, it is already mixed in) +{self.init['source_language_code']}.srt = Subtitles recognized in the original video +{self.init['target_language_code']}.srt = Subtitles translated into the target language +shuang.srt = Source language and target language subtitles srt +vocal.wav = The vocal audio file separated from the original video +instrument.wav = The background music audio file separated from the original video + + +If you feel that this project is valuable to you and hope that it can be maintained consistently, we welcome small sponsorships. With some financial support, I will be able to continue to invest more time and energy +Donation address: https://ko-fi.com/jianchang512 + + +==== + +Github: https://github.com/jianchang512/pyvideotrans +Docs: https://pyvideotrans.com + + """) + + novoice_mp4_path.unlink(missing_ok=True) + Path(mp4_dirpath.as_posix() + "/tmp.srt").unlink(missing_ok=True) + except: + pass + self.precent = 100 + return True diff --git a/videotrans/task/trans_create.py b/videotrans/task/trans_create.py index d75a99e2..52278f34 100644 --- a/videotrans/task/trans_create.py +++ b/videotrans/task/trans_create.py @@ -11,6 +11,7 @@ from pydub import AudioSegment from videotrans.configure import config from videotrans.recognition import run as run_recogn +from videotrans.task.step import Runstep from videotrans.tts import run as run_tts from videotrans.translator import run as run_trans, get_audio_code, get_subtitle_code from videotrans.util import tools @@ -46,30 +47,67 @@ class TransCreate(): ''' def __init__(self, config_params=None, obj=None): - self.raw_basename = obj['raw_basename'] if obj else " srt " - self.config_params = config_params - self.app_mode = config_params['app_mode'] - # 原始视频信息 - self.video_info = None + # 视频原始路径 名称等信息 self.obj = obj - - self.h264 = True + # 配置信息 + self.config_params = config_params + + # 识别是否结束 - self.regcon_end = False - # 翻译是否结束 - self.trans_end = False - # 配音是否结束 - self.dubb_end = False - # 合并是否结束 - self.compose_end = False + # self.regcon_end = False + # # 翻译是否结束 + # self.trans_end = False + # # 配音是否结束 + # self.dubb_end = False + # # 合并是否结束 + # self.compose_end = False # 进度 - self.precent = 0 - self.background_music = None - self.detect_language = None - self.subtitle_language = None + self.step_inst=None + self.hasend=False + self.status_text=config.transobj['ing'] + + #初始化后的信息 + self.init={ + 'background_music':None, + 'detect_language':None, + 'subtitle_language':None, + } + # 目标目标。 linshi_out + self.init['target_dir']=None + self.init['btnkey']=None + self.init['noextname']=None - # 原始视频 - self.source_mp4 = self.obj['source_mp4'] if self.obj else "" + # 视频信息 + self.init['video_info']={} + self.init['h264']=False + # 缓存目录 + self.init['cache_folder']=None + + # 原始语言代码 + self.init['source_language_code']=None + # 目标语言代码 + self.init['target_language_code']=None + # 字幕检测语言 + self.init['detect_language']=None + + # 拆分后的无声mp4 + self.init['novoice_mp4']=None + # 原语言字幕 + self.init['source_sub']=None + # 目标语言字幕 + self.init['target_sub']=None + # 原音频 + self.init['source_wav']=None + # 目标语言音频 + self.init['target_wav']=None + # 最终目标生成mp4,在linshioutput下 + self.init['targetdir_mp4']=None + # 分离出的背景音频 + self.init['instrument']=None + # 分离出的人声 + self.init['vocal']=None + # 识别音频 + self.init['shibie_audio']=None # 视频信息 ''' @@ -85,106 +123,101 @@ def __init__(self, config_params=None, obj=None): # 存在添加的背景音乐 if tools.vail_file(self.config_params['back_audio']): - self.background_music = self.config_params['back_audio'] + self.init['background_music'] = self.config_params['back_audio'] # 如果是字幕创建配音模式 - if self.app_mode == 'peiyin': - self.noextname = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") - self.precent = 40 - self.target_dir = self.config_params['target_dir'] if self.config_params['target_dir'] else f"{config.homedir}/only_dubbing" - self.btnkey = "srt2wav" + if self.config_params['app_mode'] == 'peiyin': + self.init['noextname'] = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + self.init['target_dir'] = self.config_params['target_dir'] if self.config_params['target_dir'] else f"{config.homedir}/only_dubbing" + self.init['btnkey'] = "srt2wav" else: # 不带后缀的视频名字 - self.noextname = self.obj['noextname'] + self.init['noextname'] = self.obj['noextname'] # 进度按钮 - self.btnkey = self.obj['unid'] + self.init['btnkey'] = self.obj['unid'] # 临时作为目标目录,最后再根据条件移动 - self.target_dir = self.obj['linshi_output'] + self.init['target_dir'] = self.obj['linshi_output'] # 如果不是仅提取,则获取视频信息 - if self.app_mode not in ['tiqu', 'peiyin']: + if self.config_params['app_mode'] not in ['tiqu', 'peiyin']: # 获取视频信息 try: - self.video_info = tools.get_video_info(self.source_mp4) + tools.set_process("分析视频数据,用时可能较久请稍等.." if config.defaulelang=='zh' else "Hold on a monment",btnkey=self.init['btnkey']) + self.init['video_info'] = tools.get_video_info(self.obj['source_mp4']) except Exception as e: raise Exception(f"{config.transobj['get video_info error']}:{str(e)}") - if not self.video_info: + if not self.init['video_info']: raise Exception(config.transobj['get video_info error']) - if self.video_info['video_codec_name'] != 'h264' or self.obj['ext'].lower() != 'mp4': - self.h264 = False + if self.init['video_info']['video_codec_name'] != 'h264' or self.obj['ext'].lower() != 'mp4': + self.init['h264'] = False # 临时文件夹 - self.cache_folder = f"{config.rootdir}/tmp/{self.noextname}" + self.init['cache_folder'] = f"{config.rootdir}/tmp/{self.init['noextname']}" # 创建文件夹 - Path(self.target_dir).mkdir(parents=True, exist_ok=True) - Path(self.cache_folder).mkdir(parents=True, exist_ok=True) + Path(self.init['target_dir']).mkdir(parents=True, exist_ok=True) + Path(self.init['cache_folder']).mkdir(parents=True, exist_ok=True) # 获取原语言代码和目标语言代码 if "mode" in self.config_params and self.config_params['mode'] == "cli": - self.source_language_code = self.config_params['source_language'] - self.target_language_code = self.config_params['target_language'] + self.init['source_language_code'] = self.config_params['source_language'] + self.init['target_language_code'] = self.config_params['target_language'] else: # 仅作为文件名标识 - self.source_language_code, self.target_language_code = config.rev_langlist[ - self.config_params['source_language']] if \ - self.config_params[ - 'source_language'] != '-' else '-', \ - config.rev_langlist[ - self.config_params['target_language']] if \ - self.config_params[ - 'target_language'] != '-' else '-' + self.init['source_language_code']=config.rev_langlist[self.config_params['source_language']] if self.config_params['source_language'] != '-' else '-' + self.init['target_language_code'] = config.rev_langlist[self.config_params['target_language']] if self.config_params['target_language'] != '-' else '-' # 检测字幕原始语言 if self.config_params['source_language'] != '-': - self.detect_language = get_audio_code(show_source=self.config_params['source_language']) - if self.config_params['target_language'] != '-': - self.subtitle_language = get_subtitle_code(show_target=self.config_params['target_language']) + self.init['detect_language'] = get_audio_code(show_source=self.config_params['source_language']) + # if self.config_params['target_language'] != '-': + # self.init['subtitle_language'] = get_subtitle_code(show_target=self.config_params['target_language']) - self.novoice_mp4 = f"{self.target_dir}/novoice.mp4" - self.targetdir_source_sub = f"{self.target_dir}/{self.source_language_code}.srt" - self.targetdir_target_sub = f"{self.target_dir}/{self.target_language_code}.srt" + self.init['novoice_mp4'] = f"{self.init['target_dir']}/novoice.mp4" + self.init['source_sub'] = f"{self.init['target_dir']}/{self.init['source_language_code']}.srt" + self.init['target_sub'] = f"{self.init['target_dir']}/{self.init['target_language_code']}.srt" # 原wav - self.targetdir_source_wav = f"{self.target_dir}/{self.source_language_code}.m4a" + self.init['source_wav'] = f"{self.init['target_dir']}/{self.init['source_language_code']}.m4a" # 配音后的音频文件 - self.targetdir_target_wav = f"{self.target_dir}/{self.target_language_code}.m4a" + self.init['target_wav'] = f"{self.init['target_dir']}/{self.init['target_language_code']}.m4a" # 如果原语言和目标语言相等,并且存在配音角色,则替换配音 - if self.config_params['voice_role'] != 'No' and self.source_language_code == self.target_language_code: - self.targetdir_target_wav = f"{self.target_dir}/{self.target_language_code}-dubbing.m4a" + if self.config_params['voice_role'] != 'No' and self.init['source_language_code'] == self.init['target_language_code']: + self.init['target_wav'] = f"{self.init['target_dir']}/{self.init['target_language_code']}-dubbing.m4a" # 最终的mp4视频 - self.targetdir_mp4 = f"{self.target_dir}/{self.noextname}.mp4" + self.init['targetdir_mp4'] = f"{self.init['target_dir']}/{self.init['noextname']}.mp4" # 分离出的原始音频文件 if self.config_params['is_separate']: # 背景音乐 - self.targetdir_source_instrument = f"{self.target_dir}/instrument.wav" + self.init['instrument'] = f"{self.init['target_dir']}/instrument.wav" # 转为8k采样率,降低文件 - self.targetdir_source_vocal = f"{self.target_dir}/vocal.wav" + self.init['vocal'] = f"{self.init['target_dir']}/vocal.wav" else: - self.targetdir_source_vocal = None - self.targetdir_source_instrument = None + self.init['vocal'] = None + self.init['instrument'] = None # 作为识别音频 - self.shibie_audio = f"{self.target_dir}/shibie.wav" + self.init['shibie_audio'] = f"{self.init['target_dir']}/shibie.wav" # 如果存在字幕,则视为目标字幕,直接生成,不再识别和翻译 if "subtitles" in self.config_params and self.config_params['subtitles'].strip(): - sub_file = self.targetdir_target_sub + sub_file = self.init['target_sub'] if self.config_params['source_language'] != self.config_params['target_language'] and self.config_params[ 'source_language'] != '-' and self.config_params['target_language'] != '-': # 原始和目标语言都存在,并且不相等,需要翻译,作为待翻译字幕 - sub_file = self.targetdir_source_sub + sub_file = self.init['source_sub'] with open(sub_file, 'w', encoding="utf-8", errors="ignore") as f: f.write(self.config_params['subtitles'].strip()) # 如何名字不合规迁移了,并且存在原语言或目标语言字幕 - if self.app_mode!='peiyin' and self.obj['output'] != self.obj['linshi_output']: - raw_source_srt=self.obj['output']+f'/{self.source_language_code}.srt' + if self.config_params['app_mode']!='peiyin' and self.obj['output'] != self.obj['linshi_output']: + raw_source_srt=self.obj['output']+f"/{self.init['source_language_code']}.srt" if Path(raw_source_srt).is_file(): - shutil.copy2(raw_source_srt,self.targetdir_source_sub) + shutil.copy2(raw_source_srt,self.init['source_sub']) - raw_target_srt=self.obj['output']+f'/{self.target_language_code}.srt' + raw_target_srt=self.obj['output']+f"/{self.init['target_language_code']}.srt" if Path(raw_target_srt).is_file(): - shutil.copy2(raw_target_srt,self.targetdir_target_sub) + shutil.copy2(raw_target_srt,self.init['target_sub']) + # 启动执行入口 @@ -192,151 +225,80 @@ def prepare(self): # 获取set.ini配置 config.settings = config.parse_init() if self.config_params['tts_type'] == 'clone-voice': - tools.set_process(config.transobj['test clone voice'], btnkey=self.btnkey) + tools.set_process(config.transobj['test clone voice'], btnkey=self.init['btnkey']) try: tools.get_clone_role(True) except Exception as e: raise Exception(str(e)) - self.precent += 3 # 禁止修改字幕 - tools.set_process("", "disabled_edit", btnkey=self.btnkey) - self._split_wav_novicemp4() - - # 是否需要识别,识别是否完成 - def is_recogn(self): - shound = True - if self.app_mode in ['hebing', 'peiyin']: - shound = False - return shound, self.regcon_end + tools.set_process("forbid" if self.config_params['is_batch'] else "no", "disabled_edit", btnkey=self.init['btnkey']) + def runing(): + t=0 + while not self.hasend: + time.sleep(2) + t+=2 + tools.set_process(f"{self.status_text} {t}s",btnkey=self.init['btnkey']) + if self.config_params['app_mode'] not in ['peiyin','tiqu']: + threading.Thread(target=runing).start() - # 是否需要翻译,翻译是否完成 - def is_trans(self): - shound = True - if self.app_mode in ['hebing'] or \ - self.config_params['target_language'] == '-' or \ - self.config_params['target_language'] == self.config_params['source_language']: - shound = False - return shound, self.trans_end - - # 是否需要配音,是否完成 - def is_dubb(self): - shound = True - if self.app_mode in ['tiqu', 'hebing'] or \ - self.config_params['voice_role'] == 'No': - shound = False - return shound, self.dubb_end - - # 是否需要合并,合并是否完成 - def is_compose(self): - shound = True - if self.app_mode in ['tiqu', 'peiyin']: - shound = False - if self.config_params['voice_role'] == 'No' and self.config_params['subtitle_type'] == 0: - shound = False - return shound, self.compose_end - - # 收尾,根据 output和 linshi_output是否相同,不相同,则移动 - def move_at_end(self): - output = self.obj['output'] - # 需要移动 - if self.obj and self.obj['output'] != self.obj['linshi_output']: - target_mp4=Path(self.targetdir_mp4) - if target_mp4.exists() and target_mp4.stat().st_size>0: - target_mp4.rename(Path(self.obj['linshi_output'] + f'/{self.obj["raw_noextname"]}.mp4')) - shutil.copytree(self.obj['linshi_output'], self.obj['output'], dirs_exist_ok=True) + self._split_wav_novicemp4() + self.step_inst=Runstep(init=self.init,obj=self.obj,config_params=self.config_params,parent=self) - # 仅保存视频 - if self.config_params['only_video']: - outputpath=Path(self.obj["output"]) - for it in outputpath.iterdir(): - ext = it.suffix.lower() - # 软字幕时也需要保存字幕 - if int(self.config_params['subtitle_type']) in [2, 4]: - if ext not in ['.mp4', '.srt']: - it.unlink(missing_ok=True) - elif int(self.config_params['subtitle_type']) in [1, 3]: - # 硬字幕 移动视频到上一级 - if ext != '.mp4': - it.unlink(missing_ok=True) - else: - try: - it.rename(it.parent/"../"/f'{it.name}') - except Exception: - pass - # 硬字幕删除文件夹 - if int(self.config_params['subtitle_type']) in [1, 3]: - try: - output=outputpath.parent.resolve().as_posix() - shutil.rmtree(self.obj["output"], ignore_errors=True) - outputpath.rmdir() - except Exception: - pass + def __getattr__(self, precent): + return self.step_inst.precent if self.step_inst else 0 - self.precent = 100 - # 如果移动了,删除移动后的文件 - if self.obj and self.obj['output'] != self.obj['linshi_output']: - shutil.rmtree(self.obj['linshi_output'], ignore_errors=True) - # 提取时,删除 - if self.app_mode=='tiqu': - Path(f'{self.obj["output"]}/{self.source_language_code}.srt').unlink(missing_ok=True) - Path(f'{self.obj["output"]}/{self.target_language_code}.srt').unlink(missing_ok=True) - #删除临时文件 - shutil.rmtree(self.cache_folder, ignore_errors=True) - # 批量不允许编辑字幕 - if not self.config_params['is_batch']: - tools.set_process('', 'allow_edit', btnkey=self.btnkey) - tools.set_process(f"{output}##{self.obj['raw_basename']}", - 'succeed', - btnkey=self.btnkey - ) # 分离音频 和 novoice.mp4 def _split_wav_novicemp4(self): # 存在视频 不是peiyin - if self.app_mode == 'peiyin': + if self.config_params['app_mode'] == 'peiyin': return True # 合并字幕时不分离,直接复制 - if self.app_mode == 'hebing': - shutil.copy2(self.source_mp4, self.novoice_mp4) - config.queue_novice[self.noextname] = 'end' + if self.config_params['app_mode'] == 'hebing': + shutil.copy2(self.obj['source_mp4'], self.init['novoice_mp4']) + config.queue_novice[self.init['noextname']] = 'end' return True # 不是 提取字幕时,需要分离出视频 - if self.app_mode not in ['tiqu']: + if self.config_params['app_mode'] not in ['tiqu']: threading.Thread(target=tools.split_novoice_byraw, - args=(self.source_mp4, self.novoice_mp4, self.noextname, - "copy" if self.h264 else "libx264")).start() + args=(self.obj['source_mp4'], + self.init['novoice_mp4'], + self.init['noextname'], + "copy" if self.init['h264'] else "libx264"))\ + .start() else: - config.queue_novice[self.noextname] = 'end' + config.queue_novice[self.init['noextname']] = 'end' # 添加是否保留背景选项 - self.precent += 3 - if self.config_params['is_separate'] and not tools.vail_file(self.targetdir_source_vocal): + if self.config_params['is_separate'] and not tools.vail_file(self.init['vocal']): # 背景分离音 try: - tools.set_process(config.transobj['Separating background music'], btnkey=self.btnkey) - tools.split_audio_byraw(self.source_mp4, self.targetdir_source_wav, True,btnkey=self.btnkey) + tools.set_process(config.transobj['Separating background music'], btnkey=self.init['btnkey']) + self.status_text=config.transobj['Separating background music'] + tools.split_audio_byraw(self.obj['source_mp4'], self.init['source_wav'], True,btnkey=self.init['btnkey']) except Exception as e: pass finally: - if not tools.vail_file(self.targetdir_source_vocal): - self.targetdir_source_instrument = None - self.targetdir_source_vocal = None + if not tools.vail_file(self.init['vocal']): + self.init['instrument'] = None + self.init['vocal'] = None self.config_params['is_separate'] = False else: # 分离成功后转为8k待识别音频 - tools.conver_to_8k(self.targetdir_source_vocal, self.shibie_audio) + tools.conver_to_8k(self.init['vocal'], self.init['shibie_audio']) # 不分离,或分离失败 if not self.config_params['is_separate']: try: - tools.split_audio_byraw(self.source_mp4, self.targetdir_source_wav) - tools.conver_to_8k(self.targetdir_source_wav, self.shibie_audio) + self.status_text=config.transobj['kaishitiquyinpin'] + tools.split_audio_byraw(self.obj['source_mp4'], self.init['source_wav']) + tools.conver_to_8k(self.init['source_wav'], self.init['shibie_audio']) except Exception as e: raise Exception( '从视频中提取声音失败,请检查视频中是否含有音轨,或该视频是否存在编码问题' if config.defaulelang == 'zh' else 'Failed to extract sound from video, please check if the video contains an audio track or if there is an encoding problem with that video') - if self.obj and self.obj['output'] != self.obj['linshi_output'] and tools.vail_file(self.targetdir_source_wav): - shutil.copy2(self.targetdir_source_wav, f'{self.obj["output"]}/{Path(self.targetdir_source_wav).name}') + if self.obj and self.obj['output'] != self.obj['linshi_output'] and tools.vail_file(self.init['source_wav']): + shutil.copy2(self.init['source_wav'], f"{self.obj['output']}/{Path(self.init['source_wav']).name}") return True def _unlink(self,file): @@ -345,1123 +307,101 @@ def _unlink(self,file): except Exception: pass - - # 开始识别出字幕 def recogn(self): - self.precent += 3 - tools.set_process(config.transobj["kaishishibie"], btnkey=self.btnkey) - # 如果不存在视频,或存在已识别过的,或存在目标语言字幕 或合并模式,不需要识别 - if self.app_mode in ['hebing', 'peiyin']: - self.regcon_end = True - self._unlink(self.shibie_audio) - return True - if self._srt_vail(self.targetdir_source_sub): - # 判断已存在的字幕文件中是否存在有效字幕纪录 - if self.obj and self.obj['output']!=self.obj['linshi_output']: - shutil.copy2(self.targetdir_source_sub,f'{self.obj["output"]}/{Path(self.targetdir_source_sub).name}') - self.regcon_end = True - self._unlink(self.shibie_audio) - return True - - # 分离未完成,需等待 - while not tools.vail_file(self.targetdir_source_wav): - tools.set_process(config.transobj["running"], btnkey=self.btnkey) - time.sleep(1) - # 识别为字幕 + self.status_text=config.transobj['kaishitiquzimu'] try: - self.precent += 5 - raw_subtitles = run_recogn( - # faster-whisper openai-whisper googlespeech - model_type=self.config_params['model_type'], - # 整体 预先 均等 - type=self.config_params['whisper_type'], - # 模型名 - model_name=self.config_params['whisper_model'], - # 识别音频 - audio_file=self.shibie_audio, - detect_language=self.detect_language, - cache_folder=self.cache_folder, - is_cuda=self.config_params['cuda'], - inst=self) - self._unlink(self.shibie_audio) + self.step_inst.recogn() except Exception as e: - msg = f'{str(e)}{str(e.args)}' - if re.search(r'cub[a-zA-Z0-9_.-]+?\.dll', msg, re.I | re.M) is not None: - msg = f'【缺少cuBLAS.dll】请点击菜单栏-帮助/支持-下载cublasxx.dll,或者切换为openai模型 ' if config.defaulelang == 'zh' else f'[missing cublasxx.dll] Open menubar Help&Support->Download cuBLASxx.dll or use openai model' - raise Exception(f'{msg}') - else: - if not raw_subtitles or len(raw_subtitles) < 1: - self.regcon_end = True - raise Exception(self.obj['raw_basename'] + config.transobj['recogn result is empty'].replace('{lang}',self.config_params['source_language'])) - self._save_srt_target(raw_subtitles, self.targetdir_source_sub) - - if self.obj and self.obj['output'] != self.obj['linshi_output']: - shutil.copy2(self.targetdir_source_sub, f'{self.obj["output"]}/{Path(self.targetdir_source_sub).name}') - # 仅提取字幕 - if self.app_mode=='tiqu': - shutil.copy2(self.targetdir_source_sub, f'{self.obj["output"]}/{self.obj["raw_noextname"]}.srt') - if self.config_params['target_language'] == '-' or self.config_params['target_language'] == self.config_params['source_language']: - self.compose_end=True - - self.regcon_end = True + self.hasend=True + raise Exception(e) + if self.config_params['app_mode']=='tiqu' and (self.config_params['source_language'] == self.config_params['target_language'] or self.config_params['target_language'] == '-'): + self.step_inst.precent = 100 return True - #字幕是否存在并且有效 - def _srt_vail(self,file): - if not tools.vail_file(file): - return False - try: - tools.get_subtitle_from_srt(file) - except Exception: - self._unlink(file) - return False - return True - # 翻译字幕 def trans(self): - self.precent += 3 - # 是否需要翻译,不是 hebing,存在识别后字幕并且不存在目标语言字幕,并且原语言和目标语言不同,则需要翻译 - if self.app_mode in ['hebing'] or \ - self.config_params['target_language'] == '-' or \ - self.config_params['target_language'] == self.config_params['source_language'] or not tools.vail_file(self.targetdir_source_sub): - self.trans_end = True - return True - - config.task_countdown = 0 if self.app_mode == 'biaozhun_jd' else config.settings['countdown_sec'] - - # 如果存在目标语言字幕,前台直接使用该字幕替换 - if self._srt_vail(self.targetdir_target_sub): - # 判断已存在的字幕文件中是否存在有效字幕纪录 - # 通知前端替换字幕 - with open(self.targetdir_target_sub, 'r', encoding="utf-8", errors="ignore") as f: - tools.set_process(f.read().strip(), 'replace_subtitle', btnkey=self.btnkey) - self.trans_end = True - if self.obj and self.obj['output'] != self.obj['linshi_output']: - shutil.copy2(self.targetdir_target_sub, - f'{self.obj["output"]}/{Path(self.targetdir_target_sub).name}') - self.trans_end = True - return True - - # 批量不允许修改字幕 - if not self.config_params['is_batch']: - # 等待编辑原字幕后翻译,允许修改字幕 - tools.set_process(config.transobj["xiugaiyuanyuyan"], 'edit_subtitle', btnkey=self.btnkey) - while config.task_countdown > 0: - config.task_countdown -= 1 - if config.task_countdown <= config.settings['countdown_sec']: - tools.set_process(f"{config.task_countdown} {config.transobj['jimiaohoufanyi']}", 'show_djs', - btnkey=self.btnkey) - time.sleep(1) - - # 禁止修改字幕 - tools.set_process('translate_start', 'timeout_djs', btnkey=self.btnkey) - time.sleep(2) - - # 如果不存在原字幕,或已存在目标语言字幕则跳过,比如使用已有字幕,无需翻译时 - if self._srt_vail(self.targetdir_target_sub): - self.trans_end = True - if self.app_mode == 'tiqu': - self.compose_end = True - if self.obj and self.obj['output']!=self.obj['linshi_output'] and tools.vail_file(self.targetdir_target_sub): - shutil.copy2(self.targetdir_target_sub,f'{self.obj["output"]}/{Path(self.targetdir_target_sub).name}') - return True - tools.set_process(config.transobj['starttrans'], btnkey=self.btnkey) - # 开始翻译,从目标文件夹读取原始字幕 - rawsrt = tools.get_subtitle_from_srt(self.targetdir_source_sub, is_file=True) - if not rawsrt or len(rawsrt) < 1: - self.trans_end = True - raise Exception(f'{self.obj["raw_basename"]}' + config.transobj['No subtitles file']) - # 开始翻译,禁止修改字幕 + self.status_text=config.transobj['starttrans'] try: - target_srt = run_trans( - translate_type=self.config_params['translate_type'], - text_list=rawsrt, - target_language_name=self.config_params['target_language'], - set_p=True, - inst=self, - source_code=self.source_language_code) + self.step_inst.trans() except Exception as e: - self.trans_end = True + self.hasend=True raise Exception(e) - else: - self._save_srt_target(target_srt, self.targetdir_target_sub) - self.trans_end = True - if self.obj and self.obj['output'] != self.obj['linshi_output']: - shutil.copy2(self.targetdir_target_sub, f'{self.obj["output"]}/{Path(self.targetdir_target_sub).name}') - # 仅提取,该名字删原 - if self.app_mode == 'tiqu': - shutil.copy2(self.targetdir_target_sub,f'{self.obj["output"]}/{self.obj["raw_noextname"]}-{self.target_language_code}.srt') - self.compose_end = True + if self.config_params['app_mode']=='tiqu': + self.step_inst.precent = 100 return True - # 配音处理 def dubbing(self): - self.precent += 3 - config.task_countdown = 0 if self.app_mode == 'biaozhun_jd' else config.settings['countdown_sec'] - if self.app_mode in ['tiqu']: - self.compose_end=True - return True + self.status_text=config.transobj['kaishipeiyin'] - # 不需要配音 - if self.app_mode in ['hebing'] or \ - self.config_params['voice_role'] == 'No' or \ - not tools.vail_file(self.targetdir_target_sub): - self.dubb_end = True - return True - if tools.vail_file(self.targetdir_target_wav): - if self.app_mode == 'peiyin': - self.compose_end = True - self.dubb_end = True - if self.obj and self.obj['output']!=self.obj['linshi_output']: - shutil.copy2(self.targetdir_target_wav,f'{self.obj["output"]}/{Path(self.targetdir_target_wav).name}') - return True - # 允许修改字幕 - if not self.config_params['is_batch']: - tools.set_process(config.transobj["xiugaipeiyinzimu"], "edit_subtitle", btnkey=self.btnkey) - while config.task_countdown > 0: - # 其他情况,字幕处理完毕,未超时,等待1s,继续倒计时 - time.sleep(1) - # 倒计时中 - config.task_countdown -= 1 - if config.task_countdown <= config.settings['countdown_sec']: - tools.set_process(f"{config.task_countdown}{config.transobj['zidonghebingmiaohou']}", 'show_djs', - btnkey=self.btnkey) - # 禁止修改字幕 - tools.set_process('dubbing_start', 'timeout_djs', btnkey=self.btnkey) - tools.set_process(config.transobj['kaishipeiyin'], btnkey=self.btnkey) - time.sleep(3) try: - self._exec_tts(self._before_tts()) + self.step_inst.dubbing() except Exception as e: - self.dubb_end = True + self.hasend=True raise Exception(e) - if self.app_mode == 'peiyin': - self.compose_end = True - self.dubb_end = True - if self.obj and self.obj['output'] != self.obj['linshi_output'] and tools.vail_file(self.targetdir_target_wav): - shutil.copy2(self.targetdir_target_wav, f'{self.obj["output"]}/{Path(self.targetdir_target_wav).name}') + if self.config_params['app_mode']=='peiyin': + self.step_inst.precent=100 return True - # 合并操作 def hebing(self): - self.precent += 3 - # 视频 音频 字幕 合并 - if self.app_mode in ['tiqu', 'peiyin']: - self.compose_end = True - return True - try: - self._compos_video() - except Exception as e: - self.compose_end = True - raise Exception(e) - self.compose_end = True - self.precent = 100 - return True - - def _merge_audio_segments(self, *, queue_tts=None, video_time=0): - merged_audio = AudioSegment.empty() - # start is not 0 - if queue_tts[0]['start_time'] > 0: - silence_duration = queue_tts[0]['start_time'] - silence = AudioSegment.silent(duration=silence_duration) - merged_audio += silence - # join - offset = 0 - for i, it in enumerate(queue_tts): - it['raw_duration'] = it['end_time'] - it['start_time'] - if it['raw_duration'] == 0: - continue - if not tools.vail_file(it['filename']): - merged_audio += AudioSegment.silent(duration=it['raw_duration']) - continue - segment = AudioSegment.from_file(it['filename'], format=it['filename'].split('.')[-1]) - the_dur = len(segment) - # 字幕可用时间 - raw_dur = it['raw_duration'] - it['start_time'] += offset - it['end_time'] += offset - - diff = the_dur - raw_dur - # 配音大于字幕时长,后延,延长时间 - if diff > 0: - it['end_time'] += diff - offset += diff - else: - # 配音小于原时长,添加静音 - merged_audio += AudioSegment.silent(duration=abs(diff)) - - if i > 0: - silence_duration = it['start_time'] - queue_tts[i - 1]['end_time'] - # 前面一个和当前之间存在静音区间 - if silence_duration > 0: - silence = AudioSegment.silent(duration=silence_duration) - merged_audio += silence - if config.settings['force_edit_srt']: - it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) - it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) - else: - it['startraw'] = tools.ms_to_time_string(ms=it['start_time_source']) - it['endraw'] = tools.ms_to_time_string(ms=it['end_time_source']) - queue_tts[i] = it - merged_audio += segment - - # 移除尾部静音 - co2 = merged_audio - if config.settings['remove_silence'] or (video_time > 0 and merged_audio and (len(merged_audio) > video_time)): - merged_audio = tools.remove_silence_from_end(merged_audio, silence_threshold=-50.0, chunk_size=10, - is_start=False) - if isinstance(merged_audio, str): - merged_audio = co2 - - if video_time > 0 and merged_audio and (len(merged_audio) < video_time): - # 末尾补静音 - silence = AudioSegment.silent(duration=video_time - len(merged_audio)) - merged_audio += silence - - # 创建配音后的文件 - try: - wavfile = self.cache_folder + "/target.wav" - merged_audio.export(wavfile, format="wav") - - if not self.source_mp4 and tools.vail_file(self.background_music): - cmd = ['-y', '-i', wavfile, '-i', self.background_music, '-filter_complex', - "[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2", '-ac', '2', - self.targetdir_target_wav] - tools.runffmpeg(cmd) - else: - tools.wav2m4a(wavfile, self.targetdir_target_wav) - except Exception as e: - raise Exception(f'[error]merged_audio:{str(e)}') - - return len(merged_audio), queue_tts - - # 保存字幕文件 到目标文件夹 - def _save_srt_target(self, srtstr, file): - # 是字幕列表形式,重新组装 - if isinstance(srtstr, list): - txt = "" - for it in srtstr: - startraw, endraw = it['time'].strip().split(" --> ") - startraw = startraw.strip().replace('.', ',') - endraw = endraw.strip().replace('.', ',') - startraw = tools.format_time(startraw, ',') - endraw = tools.format_time(endraw, ',') - txt += f"{it['line']}\n{startraw} --> {endraw}\n{it['text']}\n\n" - with open(file, 'w', encoding="utf-8") as f: - f.write(txt) - time.sleep(1) - tools.set_process(txt, 'replace_subtitle', btnkey=self.btnkey) - return True - - # 配音预处理,去掉无效字符,整理开始时间 - def _before_tts(self): - # 整合一个队列到 exec_tts 执行 - if self.config_params['voice_role'] == 'No': - return True - queue_tts = [] - # 获取字幕 - try: - subs = tools.get_subtitle_from_srt(self.targetdir_target_sub) - if len(subs) < 1: - raise Exception("字幕格式不正确,请打开查看") - except Exception as e: - raise Exception(f'格式化字幕失败:{str(e)}') - rate = int(str(self.config_params['voice_rate']).replace('%', '')) - if rate >= 0: - rate = f"+{rate}%" - else: - rate = f"{rate}%" - # 取出设置的每行角色 - line_roles = self.config_params["line_roles"] if "line_roles" in self.config_params else None - # 取出每一条字幕,行号\n开始时间 --> 结束时间\n内容 - for i, it in enumerate(subs): - # 判断是否存在单独设置的行角色,如果不存在则使用全局 - voice_role = self.config_params['voice_role'] - if line_roles and f'{it["line"]}' in line_roles: - voice_role = line_roles[f'{it["line"]}'] - newrole = voice_role.replace('/', '-').replace('\\', '/') - filename = f'{i}-{newrole}-{self.config_params["voice_rate"]}-{self.config_params["voice_autorate"]}-{it["text"]}' - md5_hash = hashlib.md5() - md5_hash.update(f"{filename}".encode('utf-8')) - # 要保存到的文件 - # clone-voice同时也是音色复制源 - filename = self.cache_folder + "/" + md5_hash.hexdigest() + ".mp3" - # 如果是clone-voice类型, 需要截取对应片段 - if it['end_time'] <= it['start_time']: - continue - if self.config_params['tts_type'] == 'clone-voice': - if self.config_params['is_separate'] and not tools.vail_file(self.targetdir_source_vocal): - raise Exception(f'背景分离出错 {self.targetdir_source_vocal}') - # clone 方式文件为wav格式 - if self.app_mode != 'peiyin' and tools.vail_file(self.targetdir_source_wav): - tools.cut_from_audio( - audio_file=self.targetdir_source_vocal if self.config_params[ - 'is_separate'] else self.targetdir_source_wav, - ss=it['startraw'], - to=it['endraw'], - out_file=filename - ) - - queue_tts.append({ - "text": it['text'], - "role": voice_role, - "start_time": it['start_time'], - "end_time": it['end_time'], - "rate": rate, - "startraw": it['startraw'], - "endraw": it['endraw'], - "tts_type": self.config_params['tts_type'], - "filename": filename}) - return queue_tts - - # 1. 将每个配音的实际长度加入 dubb_time - def _add_dubb_time(self, queue_tts): - for i, it in enumerate(queue_tts): - it['video_add'] = 0 - # 防止开始时间比上个结束时间还小 - if i > 0 and it['start_time'] < queue_tts[i - 1]['end_time']: - it['start_time'] = queue_tts[i - 1]['end_time'] - # 防止结束时间小于开始时间 - if it['end_time'] < it['start_time']: - it['end_time'] = it['start_time'] - # 保存原始 - it['start_time_source'] = it['start_time'] - it['end_time_source'] = it['end_time'] - # 记录原字母区间时长 - it['raw_duration'] = it['end_time'] - it['start_time'] - - if it['end_time'] > it['start_time'] and tools.vail_file(it['filename']): - it['dubb_time'] = len(AudioSegment.from_file(it['filename'], format=it['filename'].split('.')[-1])) - else: - # 不存在配音 - it['dubb_time'] = 0 - queue_tts[i] = it - - return queue_tts - - # 2. 移除原字幕多于配音的时长,实际是字幕结束时间向前移动,和下一条之间的空白更加多了 - def _remove_srt_silence(self, queue_tts): - # 如果需要移除多出来的静音 - for i, it in enumerate(queue_tts): - # 配音小于 原时长,移除默认静音 - if it['dubb_time'] > 0 and it['dubb_time'] < it['raw_duration']: - diff = it['raw_duration'] - it['dubb_time'] - it['end_time'] -= diff - it['raw_duration'] = it['dubb_time'] - queue_tts[i] = it - return queue_tts - - # 3. 自动后延或前延以对齐 - def _auto_ajust(self, queue_tts): - max_index = len(queue_tts) - 1 - - for i, it in enumerate(queue_tts): - # 如果存在配音文件并且时长大于0,才需要判断是否顺延 - if "dubb_time" not in it and it['dubb_time'] <= 0: - continue - # 配音时长如果大于原时长,才需要两侧延伸 - diff = it['dubb_time'] - it['raw_duration'] - if diff <= 0: - continue - # 需要两侧延伸 - - # 最后一个,直接后延就可以 - if i == max_index: - # 如果是最后一个,直接延长 - it['end_time'] += diff - it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) - # 重新设定可用的字幕区间时长 - it['raw_duration'] = it['end_time'] - it['start_time'] - queue_tts[i] = it - continue - - # 判断后边的开始时间比当前结束时间是否大于 - next_diff = queue_tts[i + 1]['start_time'] - it['end_time'] - if next_diff >= diff: - # 如果大于0,有空白,添加 - it['end_time'] += diff - it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) - it['raw_duration'] = it['end_time'] - it['start_time'] - queue_tts[i] = it - continue - - # 防止出错 - next_diff = 0 if next_diff < 0 else next_diff - # 先向后延伸占完空白,然后再向前添加, - it['end_time'] += next_diff - # 判断是否存在前边偏移 - if it['start_time'] > 0: - # 前面空白 - prev_diff = it['start_time'] if i == 0 else it['start_time'] - queue_tts[i - 1]['end_time'] - # 前面再添加最多 diff - next_diff - it['start_time'] -= min(prev_diff, diff - next_diff) - it['start_time'] = 0 if it['start_time'] < 0 else it['start_time'] - it['raw_duration'] = it['end_time'] - it['start_time'] - it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) - it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) - queue_tts[i] = it - return queue_tts - - # 移除2个字幕间的间隔 config.settings[remove_white_ms] ms - def _remove_white_ms(self, queue_tts): - offset = 0 - for i, it in enumerate(queue_tts): - if i > 0: - it['start_time'] -= offset - it['end_time'] -= offset - # 配音小于 原时长,移除默认静音 - dt = it['start_time'] - queue_tts[i - 1]['end_time'] - if dt > config.settings['remove_white_ms']: - diff = config.settings['remove_white_ms'] - it['end_time'] -= diff - it['start_time'] -= diff - offset += diff - queue_tts[i] = it - return queue_tts - - # 2. 先对配音加速,每条字幕信息中写入加速倍数 speed和延长的时间 add_time - def _ajust_audio(self, queue_tts): - # 遍历所有字幕条, 计算应该的配音加速倍数和延长的时间 - - # 设置加速倍数 - for i, it in enumerate(queue_tts): - it['speed'] = 0 - # 存在配音时进行处理 没有配音 - if it['dubb_time'] <= 0: - queue_tts[i] = it - continue - it['raw_duration'] = it['end_time'] - it['start_time'] - # 配音时长 不大于 原时长,不处理 - if it['raw_duration'] <= 0 or it['dubb_time'] <= it['raw_duration']: - queue_tts[i] = it - continue - it['speed'] = 1 - queue_tts[i] = it - - # 再次遍历,调整字幕开始结束时间对齐实际音频时长 - # 每次 start_time 和 end_time 需要添加的长度 offset 为当前所有 add_time 之和 - offset = 0 - for i, it in enumerate(queue_tts): - jindu = (len(queue_tts) * 10) / (i + 1) - if self.precent + jindu < 95: - self.precent += jindu - # 偏移增加 - it['start_time'] += offset - # 结束时间还需要额外添加 - it['end_time'] += offset - - if it['speed'] < 1 or config.settings['audio_rate'] <= 1: - # 不需要加速 - it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) - it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) - queue_tts[i] = it - continue - - if tools.vail_file(it['filename']): - # 如果同时有视频加速,则配音压缩为原时长 + 差额的一半 - if config.settings['video_rate'] > 1: - half = int((it['dubb_time'] - it['raw_duration']) / 2) - else: - half = 0 - # 调整音频 - tools.set_process(f"{config.transobj['dubbing speed up']} {it['speed']}", btnkey=self.btnkey) - tmp_mp3 = f'{it["filename"]}-speed.mp3' - tools.precise_speed_up_audio(file_path=it['filename'], out=tmp_mp3, - target_duration_ms=it['raw_duration'] + half, - max_rate=min(config.settings['audio_rate'], 100)) - - # 加速后时间 - if tools.vail_file(tmp_mp3): - mp3_len = len(AudioSegment.from_file(tmp_mp3, format="mp3")) - else: - mp3_len = 0 - it['raw_duration'] = it['end_time'] - it['start_time'] - it['filename'] = tmp_mp3 - - # 更改时间戳 - it['startraw'] = tools.ms_to_time_string(ms=it['start_time']) - it['endraw'] = tools.ms_to_time_string(ms=it['end_time']) - queue_tts[i] = it - return queue_tts - - # 视频慢速 在配音加速调整后,根据字幕实际开始结束时间,裁剪视频,慢速播放实现对齐 - def _ajust_video(self, queue_tts): - if not self.config_params['video_autorate'] or config.settings['video_rate'] <= 1: - return queue_tts - # 计算视频应该慢放的倍数,用当前实际的字幕时长/原始字幕时长得到倍数,如果当前时长小于等于原时长,不处理 - # 开始遍历每个时间段,如果需要视频加速,则截取 end_time_source start_time_source 时间段的视频,进行speed_video 处理 - concat_txt_arr = [] - if not tools.is_novoice_mp4(self.novoice_mp4, self.noextname): - raise Exception("not novoice mp4") - last_time = tools.get_video_duration(self.novoice_mp4) - for i, it in enumerate(queue_tts): - jindu = (len(queue_tts) * 10) / (i + 1) - if self.precent + jindu < 95: - self.precent += jindu - # 如果i==0即第一个视频,前面若是还有片段,需要截取 - if i == 0: - if it['start_time_source'] > 0: - before_dst = self.cache_folder + f'/{i}-before.mp4' - tools.cut_from_video(ss='00:00:00.000', - to=tools.ms_to_time_string(ms=it['start_time_source']), - source=self.novoice_mp4, - out=before_dst) - concat_txt_arr.append(before_dst) - elif it['start_time_source'] > queue_tts[i - 1]['end_time_source'] and it['start_time_source'] 0 and audio_length > duration: - filename_video = self.cache_folder + f'/{i}.mp4' - speed = round(audio_length / duration, 3) - if speed <= 1: - speed = 1 - else: - speed = min(20, config.settings['video_rate'], speed) - - tools.set_process(f"{config.transobj['video speed down']}[{i}] {speed=}", btnkey=self.btnkey) - # 截取原始视频 - if it['end_time_source'] > it['start_time_source'] and it['start_time_source'] it['start_time_source'] and it['start_time_source'] 0: - tools.concat_multi_mp4(filelist=concat_txt_arr, out=self.novoice_mp4) - return queue_tts - - def _exec_tts(self, queue_tts): - if not queue_tts or len(queue_tts) < 1: - raise Exception(f'Queue tts length is 0') - # 具体配音操作 + self.status_text=config.transobj['kaishihebing'] try: - run_tts(queue_tts=copy.deepcopy(queue_tts), language=self.target_language_code, set_p=True, inst=self) + self.step_inst.hebing() except Exception as e: + self.hasend=True raise Exception(e) - - # 1.首先添加配音时间 - queue_tts = self._add_dubb_time(queue_tts) - - # 2.移除字幕多于配音的时间,实际上是字幕结束时间前移,和下一条字幕空白更多 - if config.settings['remove_srt_silence']: - queue_tts = self._remove_srt_silence(queue_tts) - - # 3.是否需要 前后延展 - if "auto_ajust" in self.config_params and self.config_params['auto_ajust']: - queue_tts = self._auto_ajust(queue_tts) - - # 5.从字幕间隔移除多余的毫秒数 - if config.settings['remove_white_ms'] > 0: - queue_tts = self._remove_white_ms(queue_tts) - - # 4. 如果需要配音加速 - if self.config_params['voice_autorate'] and config.settings['audio_rate'] > 1: - queue_tts = self._ajust_audio(queue_tts) - - # 如果仅需配音 - if self.app_mode == 'peiyin': - segments = [] - start_times = [] - for i, it in enumerate(queue_tts): - if it['dubb_time'] > 0 and tools.vail_file(it['filename']): - segments.append(AudioSegment.from_file(it['filename'], format=it['filename'].split('.')[-1])) - start_times.append(it['start_time']) - else: - segments.append(AudioSegment.silent(duration=it['end_time'] - it['start_time'])) - self._merge_audio_segments(queue_tts=queue_tts) - return True - - # 6.处理视频慢速 - if self.config_params['video_autorate'] and config.settings['video_rate'] > 1: - queue_tts = self._ajust_video(queue_tts) - - # 获取 novoice_mp4的长度 - if not tools.is_novoice_mp4(self.novoice_mp4, self.noextname): - raise Exception("not novoice mp4") - video_time = tools.get_video_duration(self.novoice_mp4) - audio_length, queue_tts = self._merge_audio_segments( - video_time=video_time, - queue_tts=copy.deepcopy(queue_tts)) - - # 更新字幕 - srt = "" - for (idx, it) in enumerate(queue_tts): - srt += f"{idx + 1}\n{it['startraw']} --> {it['endraw']}\n{it['text']}\n\n" - # 字幕保存到目标文件夹 - with open(self.targetdir_target_sub, 'w', encoding="utf-8", errors="ignore") as f: - f.write(srt.strip()) - - return True - - # 延长 novoice.mp4 duration_ms 毫秒 - def _novoicemp4_add_time(self, duration_ms): - if duration_ms < 100: - return - tools.set_process(f'{config.transobj["shipinmoweiyanchang"]} {duration_ms}ms', btnkey=self.btnkey) - if not tools.is_novoice_mp4(self.novoice_mp4, self.noextname): - raise Exception("not novoice mp4") - - video_time = tools.get_video_duration(self.novoice_mp4) - - # 开始将 novoice_mp4 和 last_clip 合并 - shutil.copy2(self.novoice_mp4, f'{self.novoice_mp4}.raw.mp4') - - tools.cut_from_video( - source=self.novoice_mp4, - ss=tools.ms_to_time_string(ms=video_time - duration_ms).replace(',', '.'), - out=self.cache_folder + "/last-clip-novoice.mp4", - pts=10, - fps=None if not self.video_info or not self.video_info['video_fps'] else self.video_info['video_fps'] - ) - - clip_time = tools.get_video_duration(self.cache_folder + "/last-clip-novoice.mp4") - - nums = math.ceil(duration_ms / clip_time) - nums += math.ceil(nums / 3) - tools.concat_multi_mp4( - filelist=[self.cache_folder + "/last-clip-novoice.mp4" for x in range(nums)], - out=self.cache_folder + "/last-clip-novoice-all.mp4", - fps=None if not self.video_info or not self.video_info['video_fps'] else self.video_info['video_fps'] - ) - - tools.concat_multi_mp4( - filelist=[f'{self.novoice_mp4}.raw.mp4', self.cache_folder + "/last-clip-novoice-all.mp4"], - out=self.novoice_mp4, - maxsec=math.ceil((video_time + duration_ms) / 1000), - fps=None if not self.video_info or not self.video_info['video_fps'] else self.video_info['video_fps'] - ) - try: - os.unlink(f'{self.novoice_mp4}.raw.mp4') - except Exception as e: - pass + self.step_inst.precent=100 return True - # 添加背景音乐 - def _back_music(self): - if self.app_mode not in ["hebing", "tiqu", "peiyin"] and self.config_params[ - 'voice_role'] != 'No' and tools.vail_file(self.targetdir_target_wav) and tools.vail_file(self.background_music): - try: - # 获取视频长度 - vtime = tools.get_video_info(self.novoice_mp4, video_time=True) - vtime /= 1000 - # 获取音频长度 - atime = tools.get_audio_time(self.background_music) - # 转为m4a - if not self.background_music.lower().endswith('.m4a'): - tmpm4a = self.cache_folder + f"/background_music-1.m4a" - tools.wav2m4a(self.background_music, tmpm4a) - self.background_music = tmpm4a - beishu = vtime / atime - if config.settings['loop_backaudio'] and beishu > 1 and vtime - 1 > atime: - beishu = int(beishu) - # 获取延长片段 - # 背景音频连接延长片段 - tools.concat_multi_audio(filelist=[self.background_music for n in range(beishu + 1)], - out=self.cache_folder + "/background_music-2.m4a") - self.background_music = self.cache_folder + "/background_music-2.m4a" - # 背景音频降低音量 - tools.runffmpeg( - ['-y', '-i', self.background_music, "-filter:a", f"volume={config.settings['backaudio_volume']}", - '-c:a', 'aac', - self.cache_folder + f"/background_music-3.m4a"]) - # 背景音频和配音合并 - cmd = ['-y', '-i', self.targetdir_target_wav, '-i', self.cache_folder + f"/background_music-3.m4a", - '-filter_complex', "[0:a][1:a]amix=inputs=2:duration=first:dropout_transition=2", '-ac', '2', - self.cache_folder + f"/lastend.m4a"] - tools.runffmpeg(cmd) - self.targetdir_target_wav = self.cache_folder + f"/lastend.m4a" - except Exception as e: - config.logger.error(f'添加背景音乐失败:{str(e)}') - - def _separate(self): - if self.config_params['is_separate'] and tools.vail_file(self.targetdir_target_wav): - try: - # 原始背景音乐 wav,和配音后的文件m4a合并 - # 获取视频长度 - vtime = tools.get_video_info(self.novoice_mp4, video_time=True) - vtime /= 1000 - # 获取音频长度 - atime = tools.get_audio_time(self.targetdir_source_instrument) - if config.settings['loop_backaudio'] and atime + 1 < vtime: - # 延长背景音 - cmd = ['-y', '-i', self.targetdir_source_instrument, '-ss', '00:00:00.000', '-t', - f'{vtime - atime}', self.cache_folder + "/yanchang.m4a"] - tools.runffmpeg(cmd) - # 背景音连接延长片段 - tools.concat_multi_audio( - filelist=[self.targetdir_source_instrument, self.cache_folder + "/yanchang.m4a"], - out=self.cache_folder + f"/instrument-2.m4a") - - self.targetdir_source_instrument = self.cache_folder + f"/instrument-2.m4a" - # 背景音合并配音 - tools.backandvocal(self.targetdir_source_instrument, self.targetdir_target_wav) - except Exception as e: - config.logger.error('合并原始背景失败' + config.transobj['Error merging background and dubbing'] + str(e)) - - # 最终合成视频 source_mp4=原始mp4视频文件,noextname=无扩展名的视频文件名字 - def _compos_video(self): - if self.app_mode in ['tiqu', 'peiyin']: - return True - # 判断novoice_mp4是否完成 - if not tools.is_novoice_mp4(self.novoice_mp4, self.noextname): - raise Exception(config.transobj['fenlinoviceerror']) - - # 需要字幕 - if self.config_params['subtitle_type'] > 0 and not tools.vail_file(self.targetdir_target_sub): - raise Exception(f"{config.transobj['No subtitles file']}: {self.targetdir_target_sub}") - - if self.precent < 90: - self.precent = 90 - # 存放目标字幕 - target_sub_list = [] - # 存放原始字幕 - source_sub_list = [] - if self.config_params['subtitle_type'] > 0: - try: - target_sub_list = tools.get_subtitle_from_srt(self.targetdir_target_sub) - except Exception as e: - raise Exception(f'{config.transobj["Subtitles error"]}-1 :{str(e)}') - if self.config_params['subtitle_type'] in [3, 4] and tools.vail_file(self.targetdir_source_sub): - try: - source_sub_list = tools.get_subtitle_from_srt(self.targetdir_source_sub) - except Exception as e: - raise Exception(f'{config.transobj["Subtitles error"]}-1 :{str(e)}') - - # 无声音视频 或 合并模式时原视频 - novoice_mp4_path=Path(self.novoice_mp4) - novoice_mp4 = os.path.normpath(self.novoice_mp4) - # 视频目录,用于硬字幕时进入工作目录 - mp4_dirpath = novoice_mp4_path.parent.resolve() - - # 软字幕 完整路径 - soft_srt = os.path.normpath(self.targetdir_target_sub) - - - # 硬字幕仅名字 需要和视频在一起 - hard_srt = "tmp.srt" - hard_srt_path = Path(mp4_dirpath/hard_srt) - fontsize = f":force_style=Fontsize={config.settings['fontsize']}" if config.settings['fontsize'] > 0 else "" - maxlen = config.settings['cjk_len'] if self.target_language_code[:2] in ["zh", "ja", "jp", "ko"] else \ - config.settings['other_len'] - maxlen_source = config.settings['cjk_len'] if self.source_language_code[:2] in ["zh", "ja", "jp", "ko"] else \ - config.settings['other_len'] - - if self.precent < 90: - self.precent = 90 - - # 需要硬字幕 - if self.config_params['subtitle_type'] in [1, 3]: - text = "" - for i, it in enumerate(target_sub_list): - it['text'] = textwrap.fill(it['text'], maxlen) - text += f"{it['line']}\n{it['time']}\n{it['text'].strip()}\n\n" - hard_srt_path.write_text(text,encoding='utf-8',errors="ignore") - os.chdir(mp4_dirpath) - - - # 如果是合并字幕模式 - if self.app_mode == 'hebing': - if self.config_params['subtitle_type'] in [1, 3]: - tools.runffmpeg([ - "-y", - "-i", - novoice_mp4, - "-c:v", - "libx264", - "-vf", - f"subtitles={hard_srt}{fontsize}", - '-crf', - f'{config.settings["crf"]}', - '-preset', - 'slow', - os.path.normpath(self.targetdir_mp4), - ], de_format="nv12") - else: - # 软字幕 - tools.runffmpeg([ - "-y", - "-i", - novoice_mp4, - "-i", - soft_srt, - "-c:v", - "copy" if self.h264 else "libx264", - "-c:s", - "mov_text", - "-metadata:s:s:0", - f"language={self.subtitle_language}", - os.path.normpath(self.targetdir_mp4) - ]) - self.precent = 100 - try: - novoice_mp4_path.unlink(missing_ok=True) - hard_srt_path.unlink(missing_ok=True) - except Exception: - pass - return True - # 需要配音但没有配音文件 - if self.config_params['voice_role'] != 'No' and not tools.vail_file(self.targetdir_target_wav): - raise Exception(f"{config.transobj['Dubbing']}{config.transobj['anerror']}:{self.targetdir_target_wav}") - - # 需要双字幕 - if self.source_language_code != self.target_language_code and len(source_sub_list) > 0: - # 双字幕 硬字幕 - if self.config_params['subtitle_type'] == 3: - text = "" - source_length = len(source_sub_list) - for i, it in enumerate(target_sub_list): - it['text'] = textwrap.fill(it['text'], maxlen) - text += f"{it['line']}\n{it['time']}\n{it['text'].strip()}" - if source_length > 0 and i < source_length: - text += "\n" + textwrap.fill(source_sub_list[i]['text'], maxlen_source).strip() - text += "\n\n" - hard_srt_path.write_text(text.strip(),encoding="utf-8", errors="ignore") - os.chdir(mp4_dirpath) - shutil.copy2(hard_srt_path.as_posix(),f'{self.obj["output"]}/shuang.srt') - - # 双字幕 软字幕 - elif self.config_params['subtitle_type'] == 4: - text = "" - for i, it in enumerate(target_sub_list): - text += f"{it['line']}\n{it['time']}\n{it['text'].strip()}" - if i < len(source_sub_list): - text += f"\n{source_sub_list[i]['text'].strip()}" - text += "\n\n" - # 软字幕双 - soft_srt=self.obj['linshi_output']+"/shuang.srt" - shutil.copy2(self.targetdir_target_sub,soft_srt) - with open(soft_srt, 'w', encoding="utf-8", errors="ignore") as f: - f.write(text.strip()) - soft_srt=os.path.normpath(soft_srt) + # 收尾,根据 output和 linshi_output是否相同,不相同,则移动 + def move_at_end(self): + self.hasend=True + output = self.obj['output'] + # 需要移动 + if self.obj and self.obj['output'] != self.obj['linshi_output']: + target_mp4=Path(self.init['targetdir_mp4']) + if target_mp4.exists() and target_mp4.stat().st_size>0: + target_mp4.rename(Path(self.obj['linshi_output'] + f'/{self.obj["raw_noextname"]}.mp4')) + shutil.copytree(self.obj['linshi_output'], self.obj['output'], dirs_exist_ok=True) + shutil.rmtree(self.obj['linshi_output'], ignore_errors=True) - # 分离背景音和添加背景音乐 - self._back_music() - self._separate() - # 有配音 延长视频或音频对齐 - if self.config_params['voice_role'] != 'No' and config.settings['append_video']: - video_time = tools.get_video_duration(novoice_mp4) - audio_length = len( - AudioSegment.from_file(self.targetdir_target_wav, format=self.targetdir_target_wav.split('.')[-1])) - if audio_length > video_time: - # 视频末尾延长 + # 仅保存视频 + if self.config_params['only_video']: + outputpath=Path(self.obj['output']) + for it in outputpath.iterdir(): + ext = it.suffix.lower() + # 软字幕时也需要保存字幕 + if int(self.config_params['subtitle_type']) in [2, 4]: + if ext not in ['.mp4', '.srt']: + it.unlink(missing_ok=True) + elif int(self.config_params['subtitle_type']) in [1, 3]: + # 硬字幕 移动视频到上一级 + if ext != '.mp4': + it.unlink(missing_ok=True) + else: + try: + it.rename(it.parent/"../"/f'{it.name}') + except Exception: + pass + # 硬字幕删除文件夹 + if int(self.config_params['subtitle_type']) in [1, 3]: try: - # 对视频末尾定格延长 - self._novoicemp4_add_time(audio_length - video_time) - except Exception as e: - raise Exception(f'{config.transobj["moweiyanchangshibai"]}:{str(e)}') - elif video_time > audio_length: - m = AudioSegment.from_file(self.targetdir_target_wav, - format=self.targetdir_target_wav.split('.')[-1]) + AudioSegment.silent( - duration=video_time - audio_length) - m.export(self.targetdir_target_wav, format=self.targetdir_target_wav.split('.')[-1]) - - try: - # 有配音有字幕 - if self.config_params['voice_role'] != 'No' and self.config_params['subtitle_type'] > 0: - if self.config_params['subtitle_type'] in [1, 3]: - tools.set_process(config.transobj['peiyin-yingzimu'], btnkey=self.btnkey) - # 需要配音+硬字幕 - tools.runffmpeg([ - "-y", - "-i", - novoice_mp4, - "-i", - os.path.normpath(self.targetdir_target_wav), - "-c:v", - "libx264", - "-c:a", - "aac", - "-vf", - f"subtitles={hard_srt}{fontsize}", - '-crf', - f'{config.settings["crf"]}', - '-preset', - 'slow', - os.path.normpath(self.targetdir_mp4), - ], de_format="nv12") - else: - tools.set_process(config.transobj['peiyin-ruanzimu'], btnkey=self.btnkey) - # 配音+软字幕 - tools.runffmpeg([ - "-y", - "-i", - novoice_mp4, - "-i", - os.path.normpath(self.targetdir_target_wav), - "-i", - soft_srt, - "-c:v", - "copy", - "-c:a", - "aac", - "-c:s", - "mov_text", - "-metadata:s:s:0", - f"language={self.subtitle_language}", - os.path.normpath(self.targetdir_mp4) - ]) - elif self.config_params['voice_role'] != 'No': - # 有配音无字幕 - tools.set_process(config.transobj['onlypeiyin'], btnkey=self.btnkey) - tools.runffmpeg([ - "-y", - "-i", - novoice_mp4, - "-i", - os.path.normpath(self.targetdir_target_wav), - "-c:v", - "copy", - "-c:a", - "aac", - os.path.normpath(self.targetdir_mp4) - ]) - # 硬字幕无配音 原始 wav合并 - elif self.config_params['subtitle_type'] in [1, 3]: - tools.set_process(config.transobj['onlyyingzimu'], btnkey=self.btnkey) - cmd = [ - "-y", - "-i", - novoice_mp4 - ] - if tools.vail_file(self.targetdir_source_wav): - cmd.append('-i') - cmd.append(os.path.normpath(self.targetdir_source_wav)) - - cmd.append('-c:v') - cmd.append('libx264') - if tools.vail_file(self.targetdir_source_wav): - cmd.append('-c:a') - cmd.append('aac') - cmd += [ - "-vf", - f"subtitles={hard_srt}{fontsize}", - '-crf', - f'{config.settings["crf"]}', - '-preset', - 'slow', - os.path.normpath(self.targetdir_mp4), - ] - tools.runffmpeg(cmd, de_format="nv12") - elif self.config_params['subtitle_type'] in [2, 4]: - # 软字幕无配音 - tools.set_process(config.transobj['onlyruanzimu'], btnkey=self.btnkey) - # 原视频 - cmd = [ - "-y", - "-i", - novoice_mp4 - ] - # 原配音流 - if tools.vail_file(self.targetdir_source_wav): - cmd.append("-i") - cmd.append(os.path.normpath(self.targetdir_source_wav)) - # 目标字幕流 - cmd += [ - "-i", - soft_srt, - "-c:v", - "copy" - ] - if tools.vail_file(self.targetdir_source_wav): - cmd.append('-c:a') - cmd.append('aac') - cmd += [ - "-c:s", - "mov_text", - "-metadata:s:s:0", - f"language={self.subtitle_language}", - '-crf', - f'{config.settings["crf"]}', - '-preset', - 'slow', ] - cmd.append(os.path.normpath(self.targetdir_mp4)) - tools.runffmpeg(cmd) - except Exception as e: - raise Exception(f'compose srt + video + audio:{str(e)}') - self.precent = 99 - try: - - if not self.config_params['only_video']: - with open(self.target_dir+f'/{"readme" if config.defaulelang != "zh" else "文件说明"}.txt','w', encoding="utf-8", errors="ignore") as f: - f.write(f"""以下是可能生成的全部文件, 根据执行时配置的选项不同, 某些文件可能不会生成, 之所以生成这些文件和素材,是为了方便有需要的用户, 进一步使用其他软件进行处理, 而不必再进行语音导出、音视频分离、字幕识别等重复工作 - - -{os.path.basename(self.targetdir_mp4)} = 最终完成的目标视频文件 -{self.source_language_code}.m4a|.wav = 原始视频中的音频文件(包含所有背景音和人声) -{self.target_language_code}.m4a = 配音后的音频文件(若选择了保留背景音乐则已混入) -{self.source_language_code}.srt = 原始视频中根据声音识别出的字幕文件 -{self.target_language_code}.srt = 翻译为目标语言后字幕文件 -shuang.srt = 双语字幕 -vocal.wav = 原始视频中分离出的人声音频文件 -instrument.wav = 原始视频中分离出的背景音乐音频文件 - - -如果觉得该项目对你有价值,并希望该项目能一直稳定持续维护,欢迎各位小额赞助,有了一定资金支持,我将能够持续投入更多时间和精力 -捐助地址:https://github.com/jianchang512/pyvideotrans/issues/80 - -==== - -Here are the descriptions of all possible files that might exist. Depending on the configuration options when executing, some files may not be generated. - -{os.path.basename(self.targetdir_mp4)} = The final completed target video file -{self.source_language_code}.m4a|.wav = The audio file in the original video (containing all sounds) -{self.target_language_code}.m4a = The dubbed audio file (if you choose to keep the background music, it is already mixed in) -{self.source_language_code}.srt = Subtitles recognized in the original video -{self.target_language_code}.srt = Subtitles translated into the target language -shuang.srt = Source language and target language subtitles srt -vocal.wav = The vocal audio file separated from the original video -instrument.wav = The background music audio file separated from the original video - - -If you feel that this project is valuable to you and hope that it can be maintained consistently, we welcome small sponsorships. With some financial support, I will be able to continue to invest more time and energy -Donation address: https://ko-fi.com/jianchang512 - - -==== - -Github: https://github.com/jianchang512/pyvideotrans -Docs: https://pyvideotrans.com - - """) - - novoice_mp4_path.unlink(missing_ok=True) - Path(mp4_dirpath.as_posix() + "/tmp.srt").unlink(missing_ok=True) - except: - pass - self.precent = 100 - return True + output=outputpath.parent.resolve().as_posix() + shutil.rmtree(self.obj['output'], ignore_errors=True) + outputpath.rmdir() + except Exception: + pass + # 提取时,删除 + if self.config_params['app_mode']=='tiqu': + self._unlink(f"{self.obj['output']}/{self.init['source_language_code']}.srt") + self._unlink(f"{self.obj['output']}/{self.init['target_language_code']}.srt") + #删除临时文件 + shutil.rmtree(self.init['cache_folder'], ignore_errors=True) + # 批量不允许编辑字幕 + if not self.config_params['is_batch']: + tools.set_process('', 'allow_edit', btnkey=self.init['btnkey']) + time.sleep(3) + tools.set_process( + f"{output}##{self.obj['raw_basename']}", + 'succeed', + btnkey=self.init['btnkey'] + ) + self.step_inst.precent=100 diff --git a/videotrans/translator/__init__.py b/videotrans/translator/__init__.py index e259393a..5ab4b2e6 100644 --- a/videotrans/translator/__init__.py +++ b/videotrans/translator/__init__.py @@ -366,8 +366,12 @@ def get_audio_code(*, show_source=None): # 获取嵌入软字幕的3位字母语言代码,根据目标语言确定 def get_subtitle_code(*, show_target=None): - target_list = LANG_CODE[show_target] if show_target in LANG_CODE else LANG_CODE[config.rev_langlist[show_target]] - return target_list[1] + if show_target in LANG_CODE: + return LANG_CODE[show_target][1] + if show_target in config.rev_langlist: + return LANG_CODE[config.rev_langlist[show_target]][1] + + return 'eng' # 翻译,先根据翻译通道和目标语言,取出目标语言代码 diff --git a/videotrans/translator/azure.py b/videotrans/translator/azure.py index 048e0b38..051ed2af 100644 --- a/videotrans/translator/azure.py +++ b/videotrans/translator/azure.py @@ -90,7 +90,7 @@ def trans(text_list, target_language="English", *, set_p=True,inst=None,stop=0,s if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) client = AzureOpenAI( @@ -115,7 +115,7 @@ def trans(text_list, target_language="English", *, set_p=True,inst=None,stop=0,s if not is_srt: target_text["0"].append(result) if not set_p: - tools.set_process_box(result + "\n", func_name="set_fanyi") + tools.set_process_box(text=result + "\n", func_name="fanyi",type="set") continue sep_res = result.strip().split("\n") @@ -131,9 +131,9 @@ def trans(text_list, target_language="English", *, set_p=True,inst=None,stop=0,s target_text["srts"].append(result_item.strip().rstrip(end_point)) if set_p: tools.set_process(result_item + "\n", 'subtitle') - tools.set_process(config.transobj['starttrans'] + f' {i * split_size + x+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans'] + f' {i * split_size + x+1} ',btnkey=inst.init['btnkey'] if inst else "") else: - tools.set_process_box(result_item + "\n", func_name="set_fanyi") + tools.set_process_box(text=result_item + "\n", func_name="fanyi",type="set") if len(sep_res) 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -86,7 +86,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length = len(result) diff --git a/videotrans/translator/chatgpt.py b/videotrans/translator/chatgpt.py index 41cb2a7d..fae34fcb 100644 --- a/videotrans/translator/chatgpt.py +++ b/videotrans/translator/chatgpt.py @@ -117,7 +117,7 @@ def trans(text_list, target_language="English", *, set_p=True,inst=None,stop=0,s if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) client,api_url = create_openai_client() @@ -140,7 +140,7 @@ def trans(text_list, target_language="English", *, set_p=True,inst=None,stop=0,s if not is_srt: target_text["0"].append(result) if not set_p: - tools.set_process_box(result + "\n", func_name="set_fanyi") + tools.set_process_box(text=result + "\n",func_name="fanyi",type="set") continue sep_res = result.strip().split("\n") @@ -157,9 +157,9 @@ def trans(text_list, target_language="English", *, set_p=True,inst=None,stop=0,s target_text["srts"].append(result_item.strip().rstrip(end_point)) if set_p: tools.set_process(result_item + "\n", 'subtitle') - tools.set_process(config.transobj['starttrans'] + f' {i * split_size + x+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans'] + f' {i * split_size + x+1} ',btnkey=inst.init['btnkey'] if inst else "") else: - tools.set_process_box(result_item + "\n", func_name="set_fanyi") + tools.set_process_box(text=result_item + "\n", func_name="fanyi",type="set") if len(sep_res) 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -63,7 +63,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length = len(result) diff --git a/videotrans/translator/deeplx.py b/videotrans/translator/deeplx.py index ce2f5c5d..d89decfb 100644 --- a/videotrans/translator/deeplx.py +++ b/videotrans/translator/deeplx.py @@ -42,7 +42,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -87,7 +87,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length = len(result) diff --git a/videotrans/translator/freegoogle.py b/videotrans/translator/freegoogle.py index 47b10da1..638329ab 100644 --- a/videotrans/translator/freegoogle.py +++ b/videotrans/translator/freegoogle.py @@ -56,7 +56,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] @@ -102,7 +102,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length=len(result) diff --git a/videotrans/translator/gemini.py b/videotrans/translator/gemini.py index 181a5877..44168328 100644 --- a/videotrans/translator/gemini.py +++ b/videotrans/translator/gemini.py @@ -144,7 +144,7 @@ def trans(text_list, target_language="English", *, set_p=True, inst=None, stop=0 if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) response = None @@ -162,7 +162,7 @@ def trans(text_list, target_language="English", *, set_p=True, inst=None, stop=0 if not is_srt: target_text["0"].append(result) if not set_p: - tools.set_process_box(result + "\n", func_name="set_fanyi") + tools.set_process_box(text=result + "\n",func_name="fanyi",type="set") continue sep_res = result.strip().split("\n") @@ -186,9 +186,9 @@ def trans(text_list, target_language="English", *, set_p=True, inst=None, stop=0 target_text["srts"].append(result_item.strip().rstrip(end_point)) if set_p: tools.set_process(result_item + "\n", 'subtitle') - tools.set_process(config.transobj['starttrans'] + f' {i * split_size + x + 1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans'] + f' {i * split_size + x + 1} ',btnkey=inst.init['btnkey'] if inst else "") else: - tools.set_process_box(result_item + "\n", func_name="set_fanyi") + tools.set_process_box(text=result_item + "\n", func_name="fanyi",type="set") if len(sep_res) < len(it): tmp = ["" for x in range(len(it) - len(sep_res))] diff --git a/videotrans/translator/google.py b/videotrans/translator/google.py index 6f8a4e43..e044bfb9 100644 --- a/videotrans/translator/google.py +++ b/videotrans/translator/google.py @@ -45,7 +45,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] @@ -91,7 +91,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length=len(result) diff --git a/videotrans/translator/microsoft.py b/videotrans/translator/microsoft.py index f032840d..93aba20a 100644 --- a/videotrans/translator/microsoft.py +++ b/videotrans/translator/microsoft.py @@ -36,7 +36,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(5) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -91,7 +91,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length=len(result) diff --git a/videotrans/translator/ott.py b/videotrans/translator/ott.py index 3222392a..86e5b448 100644 --- a/videotrans/translator/ott.py +++ b/videotrans/translator/ott.py @@ -43,7 +43,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -94,7 +94,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length = len(result) diff --git a/videotrans/translator/tencent.py b/videotrans/translator/tencent.py index 8a0a3db1..12049229 100644 --- a/videotrans/translator/tencent.py +++ b/videotrans/translator/tencent.py @@ -32,7 +32,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(10) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -82,7 +82,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result[0]}\n\n' if split_size==1 else "\n\n".join(result), 'subtitle') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process("\n\n".join(result), func_name="set_fanyi") result_length = len(result) diff --git a/videotrans/translator/transapi.py b/videotrans/translator/transapi.py index ef52b961..2b92df1c 100644 --- a/videotrans/translator/transapi.py +++ b/videotrans/translator/transapi.py @@ -50,7 +50,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source if iter_num > 1: if set_p: tools.set_process( - f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.btnkey if inst else "") + f"第{iter_num}次出错重试" if config.defaulelang == 'zh' else f'{iter_num} retries after error',btnkey=inst.init['btnkey'] if inst else "") time.sleep(5) # 整理待翻译的文字为 List[str] if isinstance(text_list, str): @@ -103,7 +103,7 @@ def trans(text_list, target_language="en", *, set_p=True,inst=None,stop=0,source inst.precent += round((i + 1) * 5 / len(split_source_text), 2) if set_p: tools.set_process( f'{result}\n\n') - tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.btnkey if inst else "") + tools.set_process(config.transobj['starttrans']+f' {i*split_size+1} ',btnkey=inst.init['btnkey'] if inst else "") else: tools.set_process(result+"\n\n", func_name="set_fanyi") diff --git a/videotrans/tts/__init__.py b/videotrans/tts/__init__.py index af6ad73c..c76979c1 100644 --- a/videotrans/tts/__init__.py +++ b/videotrans/tts/__init__.py @@ -6,33 +6,58 @@ lasterror="" # 文字合成 -def text_to_speech(inst=None,text="", role="", rate='+0%',language=None, filename=None, tts_type=None, play=False, set_p=True,is_test=False): +def text_to_speech( + inst=None, + text="", + role="", + rate='+0%', + pitch="+0Hz", + volume="+0%", + language=None, + filename=None, + tts_type=None, + play=False, + set_p=True): global lasterror + get_voice=None if tts_type == "edgeTTS": from .edgetts import get_voice - lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,inst=inst,volume=volume,pitch=pitch) elif tts_type == "AzureTTS": from .azuretts import get_voice - lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,inst=inst) elif tts_type == "openaiTTS": from .openaitts import get_voice - lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,inst=inst) elif tts_type == "clone-voice": from .clone import get_voice - lasterror=get_voice(text=text, role=role, language=language, filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, language=language, filename=filename,set_p=set_p,inst=inst) elif tts_type=='TTS-API': from .ttsapi import get_voice - lasterror=get_voice(text=text, role=role, language=language, filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, language=language, filename=filename,set_p=set_p,inst=inst) elif tts_type=='GPT-SoVITS': from .gptsovits import get_voice - lasterror=get_voice(text=text, role=role, language=language, filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, language=language, filename=filename,set_p=set_p,inst=inst) elif tts_type == 'elevenlabsTTS': from .elevenlabs import get_voice - lasterror=get_voice(text=text, role=role, rate=rate,language=language, filename=filename,set_p=set_p,is_test=is_test,inst=inst) + # lasterror=get_voice(text=text, role=role, rate=rate,language=language, filename=filename,set_p=set_p,inst=inst) elif tts_type =='gtts': from .gtts import get_voice - lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,is_test=is_test,inst=inst) - + # lasterror=get_voice(text=text, role=role, rate=rate, language=language,filename=filename,set_p=set_p,inst=inst) + if get_voice: + try: + get_voice( + text=text, + volume=volume, + pitch=pitch, + role=role, + rate=rate, + language=language, + filename=filename, + set_p=set_p, + inst=inst) + except Exception as e: + lasterror=str(e) if tools.vail_file(filename): if play: threading.Thread(target=tools.pygameaudio, args=(filename,)).start() @@ -43,31 +68,36 @@ def text_to_speech(inst=None,text="", role="", rate='+0%',language=None, filenam def run(*, queue_tts=None, language=None,set_p=True,inst=None): - def get_item(q): - return {"text": q['text'], "role": q['role'], "rate": q["rate"], - "filename": q["filename"], "tts_type": q['tts_type'], - "language":language - } queue_tts_copy=copy.deepcopy(queue_tts) # 需要并行的数量3 n_total = len(queue_tts) if n_total<1: - raise Exception(f'[error]queue_tts length < 1') + return False + n = 0 dub_nums=config.settings['dubbing_thread'] while len(queue_tts) > 0: - if config.current_status != 'ing' and config.box_tts != 'ing': - raise Exception('stop') + if config.exit_soft or (config.current_status != 'ing' and config.box_tts != 'ing'): + return True try: tolist = [] for i in range(dub_nums): if len(queue_tts) > 0: - p=get_item(queue_tts.pop(0)) + p=queue_tts.pop(0) if p['tts_type']!='clone-voice' and tools.vail_file(p['filename']): continue - p["set_p"]=set_p - p['inst']=inst - tolist.append(threading.Thread(target=text_to_speech, kwargs=p)) + tolist.append(threading.Thread(target=text_to_speech, kwargs={ + "text":p['text'], + "role":p['role'], + "rate":p['rate'], + "pitch":p['pitch'], + "volume":p['volume'], + "filename":p['filename'], + "tts_type":p['tts_type'], + "set_p":set_p, + "inst":inst, + "language":language + })) if len(tolist)<1: continue for t in tolist: @@ -75,7 +105,7 @@ def get_item(q): for t in tolist: n += 1 if set_p and inst: - tools.set_process(f'{config.transobj["kaishipeiyin"]} [{n}/{n_total}]',btnkey=inst.btnkey) + tools.set_process(f'{config.transobj["kaishipeiyin"]} [{n}/{n_total}]',btnkey=inst.init['btnkey']) t.join() except Exception as e: raise Exception(f'runtts:{str(e)}') @@ -84,5 +114,5 @@ def get_item(q): if not tools.vail_file(it['filename']): err+=1 if err>=(n_total/3): - raise Exception(f'配音出错数量大于1/3,请检查:{lasterror if lasterror is not True else ""}') + raise Exception(f'{config.transobj["peiyindayu31"]}:{lasterror if lasterror is not True else ""}') return True diff --git a/videotrans/tts/azuretts.py b/videotrans/tts/azuretts.py index 33b97473..a910e525 100644 --- a/videotrans/tts/azuretts.py +++ b/videotrans/tts/azuretts.py @@ -5,10 +5,8 @@ -def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None, volume="+0%",pitch="+0Hz",rate=None, language=None,filename=None,set_p=True,inst=None): try: - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False if language: language=language.split("-",maxsplit=1) else: @@ -27,20 +25,20 @@ def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set # speech_config.speech_synthesis_voice_name=role audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True,filename=filename+".wav") speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config) - if rate in ['+0%','0%','-0%','0','+0','-0']: - ssml = """ - - {} - - """.format(language,role,text) - else: - ssml = """ - - - {} - - - """.format(language,role,rate,text) + # if rate in ['+0%','0%','-0%','0','+0','-0']: + # ssml = """ + # + # {} + # + # """.format(language,role,text) + # else: + ssml = """ + + + {} + + + """.format(language,role,rate,pitch,volume,text) config.logger.info(f'{ssml=}') speech_synthesis_result = speech_synthesizer.speak_ssml_async(ssml).get() @@ -50,24 +48,23 @@ def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent < 80: inst.precent += 0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.init['btnkey'] if inst else "") elif speech_synthesis_result.reason == speechsdk.ResultReason.Canceled: cancellation_details = speech_synthesis_result.cancellation_details if cancellation_details.reason == speechsdk.CancellationReason.Error: if cancellation_details.error_details: - tools.set_process(f"{config.transobj['azureinfo']}", btnkey=inst.btnkey if inst else "") + tools.set_process(f"{config.transobj['azureinfo']}", btnkey=inst.init['btnkey'] if inst else "") raise Exception(config.transobj['azureinfo']) raise Exception("Speech synthesis canceled: {},text={}".format(cancellation_details.reason,text)) else: raise Exception('配音出错,请检查 Azure TTS') except Exception as e: error=str(e) - if is_test: - raise Exception(error) - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error config.logger.error(f"Azure TTS合成失败" + str(e)) if set_p: - tools.set_process(error,btnkey=inst.btnkey if inst else "") - return error + tools.set_process(error,btnkey=inst.init['btnkey'] if inst else "") + raise Exception(error) + else: + return True \ No newline at end of file diff --git a/videotrans/tts/clone.py b/videotrans/tts/clone.py index 2741a10e..87349192 100644 --- a/videotrans/tts/clone.py +++ b/videotrans/tts/clone.py @@ -2,19 +2,19 @@ import re import shutil import time +from pathlib import Path import requests from videotrans.configure import config from videotrans.util import tools -def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None,rate=None, volume="+0%",pitch="+0Hz", language=None, filename=None,set_p=True,inst=None): try: api_url=config.params['clone_api'].strip().rstrip('/').lower() if not api_url: raise Exception("get_voice:"+config.transobj['bixutianxiecloneapi']) - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False + api_url='http://'+api_url.replace('http://','') config.logger.info(f'clone-voice:api={api_url}') splits = {",", "。", "?", "!", ",", ".", "?", "!", "~", ":", ":", "—", "…", } @@ -37,10 +37,7 @@ def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set res=res.json() if "code" not in res or res['code']!=0: if "msg" in res and res['msg'].find("non-empty")>0: - try: - os.unlink(filename) - except: - pass + Path(filename).unlink(missing_ok=True) return True raise Exception(f'{res}') if api_url.find('127.0.0.1')>-1 or api_url.find('localhost')>-1: @@ -60,15 +57,14 @@ def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent < 80: inst.precent += 0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.init['btnkey'] if inst else "") except Exception as e: error=str(e) - if is_test: - raise Exception(error) if set_p: - tools.set_process(error,btnkey=inst.btnkey if inst else "") + tools.set_process(error,btnkey=inst.init['btnkey'] if inst else "") config.logger.error(f"cloneVoice合成失败:{error}") - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error - return error \ No newline at end of file + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error + raise Exception(error) + else: + return True \ No newline at end of file diff --git a/videotrans/tts/edgetts.py b/videotrans/tts/edgetts.py index b1a023ea..a5a88738 100644 --- a/videotrans/tts/edgetts.py +++ b/videotrans/tts/edgetts.py @@ -1,9 +1,9 @@ import asyncio +import re import sys import time import os import edge_tts - from videotrans.configure import config from videotrans.util import tools @@ -15,13 +15,26 @@ -def get_voice(*, text=None, role=None, rate=None,language=None, filename=None,set_p=True,is_test=False,inst=None): - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False - communicate = edge_tts.Communicate(text, role, rate=rate) +def get_voice(*, + text=None, + role=None, + rate="+0%", + language=None, + filename=None, + set_p=True, + inst=None, + pitch="+0Hz", + volume="+0%" + ): + if not re.match(r'^[+-]\d+%$',volume): + volume='+0%' + if not re.match(r'^[+-]\d+%$',rate): + rate='+0%' + if not re.match(r'^[+-]\d+Hz$',pitch,re.I): + pitch='+0Hz' + print(f'### {volume=},{pitch=}') + communicate = edge_tts.Communicate(text, role, rate=rate,volume=volume,pitch=pitch) try: - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False asyncio.run(communicate.save(filename)) if not tools.vail_file(filename): config.logger.error( f'edgeTTS配音失败:{text=},{filename=}') @@ -30,22 +43,32 @@ def get_voice(*, text=None, role=None, rate=None,language=None, filename=None,se tools.remove_silence_from_end(filename) if set_p and inst and inst.precent<80: inst.precent+=0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.init['btnkey'] if inst else "") + except Exception as e: err = str(e) - if is_test: - raise Exception(err) config.logger.error(f'[edgeTTS]{text=}{err=},') if err.find("Invalid response status") > 0 or err.find('WinError 10054')>-1: if set_p: - tools.set_process("edgeTTS过于频繁暂停5s后重试",btnkey=inst.btnkey if inst else "") + tools.set_process("edgeTTS过于频繁暂停5s后重试",btnkey=inst.init['btnkey'] if inst else "") config.settings['dubbing_thread']=1 time.sleep(10) - asyncio.run(communicate.save(filename)) + return get_voice( + text=text, + role=role, + rate=rate, + language=language, + filename=filename, + set_p=set_p, + inst=inst, + pitch=pitch, + volume=volume + ) elif set_p: - tools.set_process("有一个配音出错",btnkey=inst.btnkey if inst else "") + tools.set_process("有一个配音出错",btnkey=inst.init['btnkey'] if inst else "") config.logger.error( f'edgeTTS配音有一个失败:{text=},{filename=}') - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=err - return err + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=err + raise Exception(err) + else: + return True \ No newline at end of file diff --git a/videotrans/tts/elevenlabs.py b/videotrans/tts/elevenlabs.py index d54f89d0..90ae67ae 100644 --- a/videotrans/tts/elevenlabs.py +++ b/videotrans/tts/elevenlabs.py @@ -5,14 +5,12 @@ from videotrans.util import tools -def get_voice(*,text=None, role=None, rate=None,language=None, filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None, volume="+0%",pitch="+0Hz", rate=None,language=None, filename=None,set_p=True,inst=None): try: with open(os.path.join(config.rootdir,'elevenlabs.json'),'r',encoding="utf-8") as f: jsondata=json.loads(f.read()) if config.params['elevenlabstts_key']: set_api_key(config.params['elevenlabstts_key']) - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False audio = generate( text=text, voice=Voice(voice_id=jsondata[role]['voice_id']), @@ -24,15 +22,14 @@ def get_voice(*,text=None, role=None, rate=None,language=None, filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent<80: inst.precent+=0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.init['btnkey'] if inst else "") except Exception as e: error=str(e) - if is_test: - raise Exception(error) if set_p: - tools.set_process(f'elevenlabs:{error}',btnkey=inst.btnkey if inst else "") + tools.set_process(f'elevenlabs:{error}',btnkey=inst.init['btnkey'] if inst else "") config.logger.error(f"elevenlabsTTS:request error:{error}") - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error - return error + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error + raise Exception(error) + else: + return True \ No newline at end of file diff --git a/videotrans/tts/gptsovits.py b/videotrans/tts/gptsovits.py index d2d3fbc4..2207d9ef 100644 --- a/videotrans/tts/gptsovits.py +++ b/videotrans/tts/gptsovits.py @@ -8,7 +8,7 @@ from videotrans.util import tools -def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None,rate=None, volume="+0%",pitch="+0Hz", language=None, filename=None,set_p=True,inst=None): try: api_url=config.params['gptsovits_url'].strip().rstrip('/').lower() if not api_url: @@ -18,8 +18,6 @@ def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set text=text.strip() if not text: return True - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False splits = {",", "。", "?", "!", ",", ".", "?", "!", "~", ":", ":", "—", "…", } if text[-1] not in splits: text+='.' @@ -46,7 +44,7 @@ def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set f.write(response.content) time.sleep(1) if not os.path.exists(filename+".wav"): - return f'GPT-SoVITS合成声音失败-2:{text=}' + raise Exception(f'GPT-SoVITS合成声音失败-2:{text=}') tools.wav2mp3(filename+".wav",filename) if os.path.exists(filename+".wav"): os.unlink(filename+".wav") @@ -54,17 +52,16 @@ def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent < 80: inst.precent += 0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.btnkey if inst else "") + tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.init['btnkey'] if inst else "") else: raise Exception(f"GPT-SoVITS合成声音出错-3:{text=},{response.text=}") - return True except Exception as e: error=str(e) - if is_test: - raise Exception(error) if set_p: - tools.set_process(error,btnkey=inst.btnkey if inst else "") - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error + tools.set_process(error,btnkey=inst.init['btnkey'] if inst else "") + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error config.logger.error(f"{error}") - return error + raise Exception(error) + else: + return True \ No newline at end of file diff --git a/videotrans/tts/gtts.py b/videotrans/tts/gtts.py index 255b1cb3..50e88dba 100644 --- a/videotrans/tts/gtts.py +++ b/videotrans/tts/gtts.py @@ -5,17 +5,12 @@ from videotrans.util import tools -def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None, volume="+0%",pitch="+0Hz", rate=None, language=None,filename=None,set_p=True,inst=None): serv = tools.set_proxy() if serv: os.environ['HTTP_PROXY']=serv os.environ['HTTPS_PROXY']=serv - print(f'{serv=}') - try: - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False - lans=language.split('-') if len(lans)>1: language=f'{lans[0]}-{lans[1].upper()}' @@ -26,18 +21,15 @@ def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent<80: inst.precent+=0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.init['btnkey'] if inst else "") except Exception as e: error=str(e) - if is_test: - raise Exception(error) if error.lower().find('Failed to connect')>-1: - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=f'无法连接到 Google,请正确填写代理地址:{error}' + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=f'无法连接到 Google,请正确填写代理地址:{error}' config.logger.error(f"gtts 合成失败:request error:" + str(e)) - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error - return error - - + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error + raise Exception(error) + else: + return True diff --git a/videotrans/tts/openaitts.py b/videotrans/tts/openaitts.py index 2ed89916..25d67b3f 100644 --- a/videotrans/tts/openaitts.py +++ b/videotrans/tts/openaitts.py @@ -18,7 +18,7 @@ def get_url(url=""): return url+"/v1" return url -def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None, volume="+0%",pitch="+0Hz", rate=None, language=None,filename=None,set_p=True,inst=None): api_url=get_url(config.params['chatgpt_api']) proxies=None if not re.search(r'localhost',api_url) and not re.match(r'https?://(\d+\.){3}\d+',api_url): @@ -29,8 +29,6 @@ def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set 'https://': serv } try: - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False speed=1.0 if rate: rate=float(rate.replace('%',''))/100 @@ -52,24 +50,22 @@ def get_voice(*,text=None, role=None, rate=None, language=None,filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent<80: inst.precent+=0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ',btnkey=inst.init['btnkey'] if inst else "") except Exception as e: error=str(e) - if is_test: - raise Exception(error) if error.lower().find('connect timeout')>-1 or error.lower().find('ConnectTimeoutError')>-1: - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=f'无法连接到 {api_url},请正确填写代理地址:{error}' + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=f'无法连接到 {api_url},请正确填写代理地址:{error}' return False if error and re.search(r'Rate limit',error,re.I) is not None: if set_p: - tools.set_process(f'chatGPT请求速度被限制,暂停30s后自动重试',btnkey=inst.btnkey if inst else "") + tools.set_process(f'chatGPT请求速度被限制,暂停30s后自动重试',btnkey=inst.init['btnkey'] if inst else "") time.sleep(30) return get_voice(text=text, role=role, rate=rate, filename=filename) config.logger.error(f"openaiTTS合成失败:request error:" + str(e)) - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error - return error - + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error + raise Exception(error) + else: + return True diff --git a/videotrans/tts/ttsapi.py b/videotrans/tts/ttsapi.py index 163e83d8..00a458c0 100644 --- a/videotrans/tts/ttsapi.py +++ b/videotrans/tts/ttsapi.py @@ -6,15 +6,14 @@ from videotrans.util import tools -def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set_p=True,is_test=False,inst=None): +def get_voice(*,text=None, role=None, volume="+0%",pitch="+0Hz",rate=None, language=None, filename=None,set_p=True,inst=None): try: api_url=config.params['ttsapi_url'].strip().rstrip('/') if not api_url: raise Exception("get_voice:"+config.transobj['ttsapi_nourl']) config.logger.info(f'TTS-API:api={api_url}') - if config.current_status != 'ing' and config.box_tts != 'ing' and not is_test: - return False + data={"text":text.strip(),"language":language,"extra":config.params['ttsapi_extra'],"voice":role,"ostype":sys.platform,rate:rate} resraw=requests.post(f"{api_url}",data=data,proxies={"http":"","https":""},verify=False) @@ -34,15 +33,14 @@ def get_voice(*,text=None, role=None,rate=None, language=None, filename=None,set tools.remove_silence_from_end(filename) if set_p and inst and inst.precent < 80: inst.precent += 0.1 - tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.btnkey if inst else "") - return True + tools.set_process(f'{config.transobj["kaishipeiyin"]} ', btnkey=inst.init['btnkey'] if inst else "") except Exception as e: error=str(e) - if is_test: - raise Exception(error) if set_p: - tools.set_process(error,btnkey=inst.btnkey if inst else "") + tools.set_process(error,btnkey=inst.init['btnkey'] if inst else "") config.logger.error(f"TTS-API自定义失败:{error}") - if inst and inst.btnkey: - config.errorlist[inst.btnkey]=error - return error \ No newline at end of file + if inst and inst.init['btnkey']: + config.errorlist[inst.init['btnkey']]=error + raise Exception(error) + else: + return True \ No newline at end of file diff --git a/videotrans/ui/en.py b/videotrans/ui/en.py index 309a572f..b5b4e58d 100644 --- a/videotrans/ui/en.py +++ b/videotrans/ui/en.py @@ -10,6 +10,7 @@ from PySide6 import QtCore, QtGui, QtWidgets from PySide6.QtCore import Qt, QTimer +from PySide6.QtWidgets import QSizePolicy, QSpacerItem from videotrans.configure import config @@ -138,7 +139,7 @@ def setupUi(self, MainWindow): self.listen_btn = QtWidgets.QPushButton(self.layoutWidget) self.listen_btn.setEnabled(False) - self.listen_btn.setMaximumWidth(200) + self.listen_btn.setFixedWidth(80) self.verticalLayout_2.addLayout(self.horizontalLayout_5) @@ -234,6 +235,35 @@ def setupUi(self, MainWindow): self.layout_voice_role.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.voice_role) self.horizontalLayout.addLayout(self.layout_voice_role) self.horizontalLayout.addWidget(self.listen_btn) + + self.edge_volume_layout=QtWidgets.QHBoxLayout() + + + self.volume_label=QtWidgets.QLabel(self.layoutWidget) + self.volume_label.setText("音量+" if config.defaulelang=='zh' else "Volume+") + + self.volume_rate = QtWidgets.QSpinBox(self.layoutWidget) + # self.volume_rate.setMinimumSize(QtCore.QSize(0, 30)) + self.volume_rate.setMinimum(-95) + self.volume_rate.setMaximum(100) + self.volume_rate.setObjectName("volume_rate") + + self.pitch_label=QtWidgets.QLabel(self.layoutWidget) + self.pitch_label.setText("音调+" if config.defaulelang=='zh' else "Pitch+") + self.pitch_rate = QtWidgets.QSpinBox(self.layoutWidget) + self.pitch_rate.setMinimum(-100) + self.pitch_rate.setMaximum(100) + self.pitch_rate.setObjectName("pitch_rate") + + self.edge_volume_layout.addWidget(self.volume_label) + self.edge_volume_layout.addWidget(self.volume_rate) + self.edge_volume_layout.addWidget(self.pitch_label) + self.edge_volume_layout.addWidget(self.pitch_rate) + self.horizontalLayout.addLayout(self.edge_volume_layout) + + + + self.verticalLayout_2.addLayout(self.horizontalLayout) @@ -308,7 +338,7 @@ def setupUi(self, MainWindow): self.gaoji_layout_wrap.setObjectName("gaoji_layout_wrap") self.gaoji_layout_inner = QtWidgets.QHBoxLayout() self.gaoji_layout_inner.setObjectName("gaoji_layout_inner") - + self.gaoji_layout_inner2 = QtWidgets.QHBoxLayout() self.gaoji_layout_inner2.setObjectName("gaoji_layout_inner2") self.addbackbtn=QtWidgets.QPushButton(self.layoutWidget) @@ -342,26 +372,28 @@ def setupUi(self, MainWindow): self.layout_voice_rate.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_6) - self.voice_rate = QtWidgets.QLineEdit(self.layoutWidget) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.voice_rate.sizePolicy().hasHeightForWidth()) - self.voice_rate.setSizePolicy(sizePolicy) - self.voice_rate.setMinimumSize(QtCore.QSize(50, 30)) + + + self.voice_rate = QtWidgets.QSpinBox(self.layoutWidget) + self.voice_rate.setMinimum(-50) + self.voice_rate.setMaximum(50) self.voice_rate.setObjectName("voice_rate") + + self.layout_voice_rate.setAlignment(Qt.AlignVCenter) self.layout_voice_rate.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.voice_rate) self.gaoji_layout_inner.addLayout(self.layout_voice_rate) + self.append_video = QtWidgets.QCheckBox(self.layoutWidget) + self.append_video.setObjectName("append_video") + self.voice_autorate = QtWidgets.QCheckBox(self.layoutWidget) - self.voice_autorate.setMinimumSize(QtCore.QSize(0, 30)) self.voice_autorate.setObjectName("voice_autorate") self.video_autorate = QtWidgets.QCheckBox(self.layoutWidget) - self.video_autorate.setMinimumSize(QtCore.QSize(0, 30)) self.video_autorate.setObjectName("videoe_autorate") + self.gaoji_layout_inner.addWidget(self.append_video) self.gaoji_layout_inner.addWidget(self.voice_autorate) self.gaoji_layout_inner.addWidget(self.video_autorate) @@ -381,7 +413,7 @@ def setupUi(self, MainWindow): self.enable_cuda.setToolTip(config.transobj['cudatips']) self.gaoji_layout_inner.addWidget(self.enable_cuda) - + self.gaoji_layout_inner.setAlignment(Qt.AlignVCenter) self.gaoji_layout_wrap.addLayout(self.gaoji_layout_inner) self.gaoji_layout_wrap.addLayout(self.gaoji_layout_inner2) self.verticalLayout_2.addLayout(self.gaoji_layout_wrap) @@ -463,11 +495,14 @@ def setupUi(self, MainWindow): self.subtitle_layout.addLayout(self.layout_sub_bottom) self.horizontalLayout_7.addWidget(self.splitter) MainWindow.setCentralWidget(self.centralwidget) + #200ms后渲染文字 + QTimer.singleShot(200, lambda: self.retranslateUi(MainWindow)) + + + def retranslateUi(self, MainWindow): self.statusBar = QtWidgets.QStatusBar(MainWindow) self.statusBar.setObjectName("statusBar") MainWindow.setStatusBar(self.statusBar) - - self.menuBar = QtWidgets.QMenuBar(MainWindow) self.menuBar.setGeometry(QtCore.QRect(0, 0, 1488, 26)) self.menuBar.setObjectName("menuBar") @@ -596,9 +631,6 @@ def setupUi(self, MainWindow): self.action_yingyinhebing = QtGui.QAction(MainWindow) self.action_yingyinhebing.setObjectName("action_yingyinhebing") - self.action_geshi = QtGui.QAction(MainWindow) - - self.action_geshi.setObjectName("action_geshi") self.action_hun = QtGui.QAction(MainWindow) self.action_hun.setObjectName("action_hun") @@ -621,6 +653,8 @@ def setupUi(self, MainWindow): self.actionElevenlabs_key.setObjectName("actionElevenlabs_key") self.actionyoutube = QtGui.QAction(MainWindow) self.actionyoutube.setObjectName("actionyoutube") + self.actionsepar = QtGui.QAction(MainWindow) + self.actionsepar.setObjectName("actionsepar") self.menu_Key.addAction(self.actionbaidu_key) self.menu_Key.addSeparator() self.menu_Key.addAction(self.actiontencent_key) @@ -658,6 +692,8 @@ def setupUi(self, MainWindow): self.menu.addSeparator() self.menu.addAction(self.actionyoutube) self.menu.addSeparator() + self.menu.addAction(self.actionsepar) + self.menu.addSeparator() self.menu.addAction(self.action_clearcache) self.menu.addSeparator() self.menu_H.addSeparator() @@ -693,21 +729,19 @@ def setupUi(self, MainWindow): self.toolBar.addAction(self.action_xinshoujandan) self.toolBar.addAction(self.action_biaozhun) self.toolBar.addAction(self.action_tiquzimu) - self.toolBar.addAction(self.action_zimu_video) self.toolBar.addAction(self.action_zimu_peiyin) + self.toolBar.addAction(self.action_zimu_video) + self.toolBar.addAction(self.action_yuyinshibie) + self.toolBar.addAction(self.action_fanyi) self.toolBar.addAction(self.action_yuyinhecheng) self.toolBar.addAction(self.action_yinshipinfenli) self.toolBar.addAction(self.action_yingyinhebing) - self.toolBar.addAction(self.action_geshi) self.toolBar.addAction(self.action_hun) - self.toolBar.addAction(self.action_fanyi) - # QtCore.QMetaObject.connectSlotsByName(MainWindow) - QTimer.singleShot(200, lambda: self.retranslateUi(MainWindow)) - def retranslateUi(self, MainWindow): + self.btn_get_video.setToolTip(config.uilanglist.get("Multiple MP4 videos can be selected and automatically queued for processing")) self.btn_get_video.setText(config.uilanglist.get("Select video..")) self.btn_save_dir.setToolTip(config.uilanglist.get("Select where to save the processed output resources")) @@ -731,13 +765,14 @@ def retranslateUi(self, MainWindow): self.label_8.setText(config.uilanglist.get("Embed subtitles")) self.subtitle_type.setToolTip(config.uilanglist.get("shuoming02")) - self.label_6.setText(config.uilanglist.get("Dubbing speed")) + self.label_6.setText(config.uilanglist.get("Dubbing speed")+'+') self.voice_rate.setToolTip(config.uilanglist.get("Overall acceleration or deceleration of voice over playback")) - self.voice_rate.setPlaceholderText(config.uilanglist.get("Positive numbers accelerate, negative numbers decelerate, -90 to+90")) self.voice_autorate.setToolTip(config.uilanglist.get("shuoming03")) + self.voice_autorate.setText(config.uilanglist.get("Voice acceleration?")) self.video_autorate.setToolTip('视频自动慢速' if config.defaulelang=='zh' else 'Video Auto Slow') self.video_autorate.setText('视频自动慢速' if config.defaulelang=='zh' else 'Video Auto Slow') - self.voice_autorate.setText(config.uilanglist.get("Voice acceleration?")) + self.append_video.setToolTip('如果配音时长大于视频时,是否视频末尾延长' if config.defaulelang=='zh' else 'If the dubbing time is longer than the video time, is the end of the video extended?') + self.append_video.setText('视频末尾延长?' if config.defaulelang=='zh' else 'Extension video?') self.auto_ajust.setText(config.transobj.get("auto_ajust")) self.auto_ajust.setToolTip(config.uilanglist.get("shuoming03")) self.enable_cuda.setText(config.uilanglist.get("Enable CUDA?")) @@ -801,8 +836,6 @@ def retranslateUi(self, MainWindow): self.action_yinshipinfenli.setToolTip(config.uilanglist.get("Separate audio and silent videos from videos")) self.action_yingyinhebing.setText(config.uilanglist.get("Video Subtitles Merging")) self.action_yingyinhebing.setToolTip(config.uilanglist.get("Merge audio, video, and subtitles into one file")) - self.action_geshi.setText(config.uilanglist.get("Files Format Conversion")) - self.action_geshi.setToolTip(config.uilanglist.get("Convert various formats to each other")) self.action_hun.setText(config.uilanglist.get("Mixing 2 Audio Streams")) self.action_hun.setToolTip(config.uilanglist.get("Mix two audio files into one audio file")) self.action_fanyi.setText(config.uilanglist.get("Text Or Srt Translation")) @@ -815,3 +848,4 @@ def retranslateUi(self, MainWindow): self.actiongemini_key.setText("Gemini Pro") self.actionElevenlabs_key.setText("ElevenLabs TTS") self.actionyoutube.setText(config.uilanglist.get("Download from Youtube")) + self.actionsepar.setText('人声/背景音分离' if config.defaulelang=='zh'else 'Vocal & instrument Separate') diff --git a/videotrans/ui/separate.py b/videotrans/ui/separate.py index a15afa36..d0a5d5fe 100644 --- a/videotrans/ui/separate.py +++ b/videotrans/ui/separate.py @@ -111,7 +111,7 @@ def setupUi(self, separateform): self.url = QtWidgets.QLineEdit(self.layoutWidget) self.url.setMinimumSize(QtCore.QSize(0, 35)) self.url.setObjectName("url") - # self.url.setCursor(Qt.PointingHandCursor) + self.url.setCursor(Qt.PointingHandCursor) self.url.setToolTip(config.uilanglist['Open target dir']) self.url.setReadOnly(True) diff --git a/videotrans/ui/toolboxen.py b/videotrans/ui/toolboxen.py index 52b9e702..5b59e252 100644 --- a/videotrans/ui/toolboxen.py +++ b/videotrans/ui/toolboxen.py @@ -9,6 +9,7 @@ from PySide6 import QtCore, QtGui, QtWidgets +from PySide6.QtCore import Qt from videotrans.configure import config from videotrans.configure.config import box_lang @@ -26,189 +27,10 @@ def setupUi(self, MainWindow): self.tabWidget.setMinimumSize(QtCore.QSize(0, 400)) self.tabWidget.setIconSize(QtCore.QSize(20, 20)) self.tabWidget.setObjectName("tabWidget") - self.tab = QtWidgets.QWidget() - self.tab.setObjectName("tab") - self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.tab) - self.horizontalLayout_4.setObjectName("horizontalLayout_4") - self.yspfl_wrap = QtWidgets.QFrame(self.tab) - self.yspfl_wrap.setObjectName("yspfl_wrap") - self.horizontalLayout_4.addWidget(self.yspfl_wrap) - self.verticalLayout_6 = QtWidgets.QVBoxLayout() - self.verticalLayout_6.setObjectName("verticalLayout_6") - self.yspfl_widget = QtWidgets.QVBoxLayout() - self.yspfl_widget.setObjectName("yspfl_widget") - self.yspfl_startbtn = QtWidgets.QPushButton(self.tab) - self.yspfl_startbtn.setMinimumSize(QtCore.QSize(300, 40)) - self.yspfl_startbtn.setMaximumSize(QtCore.QSize(300, 40)) - self.yspfl_startbtn.setObjectName("yspfl_startbtn") - self.yspfl_widget.addWidget(self.yspfl_startbtn, 0, QtCore.Qt.AlignHCenter) - self.verticalLayout_6.addLayout(self.yspfl_widget) - self.verticalLayout_2 = QtWidgets.QVBoxLayout() - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.gridLayout = QtWidgets.QGridLayout() - self.gridLayout.setObjectName("gridLayout") - self.label = QtWidgets.QLabel(self.tab) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) - self.label.setSizePolicy(sizePolicy) - self.label.setMinimumSize(QtCore.QSize(100, 30)) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 1) - self.yspfl_videoinput = QtWidgets.QLineEdit(self.tab) - self.yspfl_videoinput.setMinimumSize(QtCore.QSize(0, 30)) - self.yspfl_videoinput.setReadOnly(True) - self.yspfl_videoinput.setObjectName("yspfl_videoinput") - self.gridLayout.addWidget(self.yspfl_videoinput, 0, 1, 1, 1) - self.yspfl_openbtn1 = QtWidgets.QPushButton(self.tab) - self.yspfl_openbtn1.setObjectName("yspfl_openbtn1") - self.gridLayout.addWidget(self.yspfl_openbtn1, 0, 2, 1, 1) - self.verticalLayout_2.addLayout(self.gridLayout) - self.gridLayout_2 = QtWidgets.QGridLayout() - self.gridLayout_2.setObjectName("gridLayout_2") - self.label_2 = QtWidgets.QLabel(self.tab) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) - self.label_2.setSizePolicy(sizePolicy) - self.label_2.setMinimumSize(QtCore.QSize(100, 30)) - self.label_2.setObjectName("label_2") - self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) - self.yspfl_wavinput = QtWidgets.QLineEdit(self.tab) - self.yspfl_wavinput.setMinimumSize(QtCore.QSize(0, 30)) - self.yspfl_wavinput.setReadOnly(True) - self.yspfl_wavinput.setPlaceholderText("") - self.yspfl_wavinput.setObjectName("yspfl_wavinput") - self.gridLayout_2.addWidget(self.yspfl_wavinput, 0, 1, 1, 1) - self.yspfl_openbtn2 = QtWidgets.QPushButton(self.tab) - self.yspfl_openbtn2.setObjectName("yspfl_openbtn2") - self.gridLayout_2.addWidget(self.yspfl_openbtn2, 0, 2, 1, 1) - self.verticalLayout_2.addLayout(self.gridLayout_2) - self.verticalLayout_6.addLayout(self.verticalLayout_2) - self.horizontalLayout_4.addLayout(self.verticalLayout_6) - self.tabWidget.addTab(self.tab, "") - self.tab_3 = QtWidgets.QWidget() - # self.tab_3.setMinimumSize(QtCore.QSize(0, 674)) - self.tab_3.setObjectName("tab_3") - self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.tab_3) - self.horizontalLayout_7.setObjectName("horizontalLayout_7") - self.verticalLayout_9 = QtWidgets.QVBoxLayout() - self.verticalLayout_9.setObjectName("verticalLayout_9") - self.verticalLayout = QtWidgets.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") - self.horizontalLayout_3 = QtWidgets.QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - self.label_4 = QtWidgets.QLabel(self.tab_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) - self.label_4.setSizePolicy(sizePolicy) - self.label_4.setMinimumSize(QtCore.QSize(100, 40)) - self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) - self.label_4.setObjectName("label_4") - self.horizontalLayout_3.addWidget(self.label_4, 0, QtCore.Qt.AlignTop) - self.ysphb_videoinput = QtWidgets.QLineEdit(self.tab_3) - self.ysphb_videoinput.setMinimumSize(QtCore.QSize(0, 40)) - self.ysphb_videoinput.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) - self.ysphb_videoinput.setReadOnly(True) - self.ysphb_videoinput.setObjectName("ysphb_videoinput") - self.horizontalLayout_3.addWidget(self.ysphb_videoinput, 0, QtCore.Qt.AlignTop) - self.ysphb_selectvideo = QtWidgets.QPushButton(self.tab_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.ysphb_selectvideo.sizePolicy().hasHeightForWidth()) - self.ysphb_selectvideo.setSizePolicy(sizePolicy) - self.ysphb_selectvideo.setMinimumSize(QtCore.QSize(150, 40)) - self.ysphb_selectvideo.setObjectName("ysphb_selectvideo") - self.horizontalLayout_3.addWidget(self.ysphb_selectvideo, 0, QtCore.Qt.AlignTop) - self.verticalLayout.addLayout(self.horizontalLayout_3) - self.horizontalLayout_5 = QtWidgets.QHBoxLayout() - self.horizontalLayout_5.setObjectName("horizontalLayout_5") - self.label_5 = QtWidgets.QLabel(self.tab_3) - self.label_5.setMinimumSize(QtCore.QSize(100, 40)) - self.label_5.setObjectName("label_5") - self.horizontalLayout_5.addWidget(self.label_5, 0, QtCore.Qt.AlignTop) - self.ysphb_wavinput = QtWidgets.QLineEdit(self.tab_3) - self.ysphb_wavinput.setMinimumSize(QtCore.QSize(0, 40)) - self.ysphb_wavinput.setReadOnly(True) - self.ysphb_wavinput.setObjectName("ysphb_wavinput") - self.horizontalLayout_5.addWidget(self.ysphb_wavinput, 0, QtCore.Qt.AlignTop) - self.ysphb_selectwav = QtWidgets.QPushButton(self.tab_3) - self.ysphb_selectwav.setMinimumSize(QtCore.QSize(150, 40)) - self.ysphb_selectwav.setObjectName("ysphb_selectwav") - self.horizontalLayout_5.addWidget(self.ysphb_selectwav, 0, QtCore.Qt.AlignTop) - self.verticalLayout.addLayout(self.horizontalLayout_5) - self.horizontalLayout_6 = QtWidgets.QHBoxLayout() - self.horizontalLayout_6.setObjectName("horizontalLayout_6") - self.label_6 = QtWidgets.QLabel(self.tab_3) - self.label_6.setMinimumSize(QtCore.QSize(100, 40)) - self.label_6.setObjectName("label_6") - self.horizontalLayout_6.addWidget(self.label_6, 0, QtCore.Qt.AlignTop) - self.ysphb_srtinput = QtWidgets.QLineEdit(self.tab_3) - self.ysphb_srtinput.setMinimumSize(QtCore.QSize(0, 40)) - self.ysphb_srtinput.setReadOnly(True) - self.ysphb_srtinput.setObjectName("ysphb_srtinput") - - self.horizontalLayout_6.addWidget(self.ysphb_srtinput, 0, QtCore.Qt.AlignTop) - self.ysphb_selectsrt = QtWidgets.QPushButton(self.tab_3) - self.ysphb_selectsrt.setMinimumSize(QtCore.QSize(150, 40)) - self.ysphb_selectsrt.setObjectName("ysphb_selectsrt") - self.horizontalLayout_6.addWidget(self.ysphb_selectsrt, 0, QtCore.Qt.AlignTop) - - - - self.horizontalLayout_replace = QtWidgets.QHBoxLayout() - self.horizontalLayout_replace.setObjectName("horizontalLayout_replace") - self.ysphb_replace = QtWidgets.QCheckBox(self.tab_3) - self.ysphb_replace.setObjectName("ysphb_replace") - self.ysphb_replace.setChecked(True) - self.ysphb_replace.setText(config.transobj['Preserve the original sound in the video']) - self.horizontalLayout_replace.addWidget(self.ysphb_replace, 0, QtCore.Qt.AlignTop) - self.verticalLayout.addLayout(self.horizontalLayout_6) - self.verticalLayout.addLayout(self.horizontalLayout_replace) - self.verticalLayout_9.addLayout(self.verticalLayout) - self.verticalLayout_8 = QtWidgets.QVBoxLayout() - self.verticalLayout_8.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) - self.verticalLayout_8.setObjectName("verticalLayout_8") - self.ysphb_startbtn = QtWidgets.QPushButton(self.tab_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.ysphb_startbtn.sizePolicy().hasHeightForWidth()) - self.ysphb_startbtn.setSizePolicy(sizePolicy) - self.ysphb_startbtn.setMinimumSize(QtCore.QSize(250, 40)) - self.ysphb_startbtn.setObjectName("ysphb_startbtn") - self.verticalLayout_8.addWidget(self.ysphb_startbtn) - self.widget = QtWidgets.QWidget(self.tab_3) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) - sizePolicy.setHorizontalStretch(0) - # sizePolicy.setVerticalStretch(0) + #=== - # sizePolicy.setHeightForWidth(self.widget.sizePolicy().hasHeightForWidth()) - self.widget.setSizePolicy(sizePolicy) - self.widget.setObjectName("widget") - self.horizontalLayout_20 = QtWidgets.QHBoxLayout(self.widget) - self.horizontalLayout_20.setObjectName("horizontalLayout_20") - self.ysphb_out = QtWidgets.QLineEdit(self.widget) - self.ysphb_out.setMinimumSize(QtCore.QSize(0, 30)) - self.ysphb_out.setReadOnly(True) - self.ysphb_out.setObjectName("ysphb_out") - self.horizontalLayout_20.addWidget(self.ysphb_out) - self.ysphb_opendir = QtWidgets.QPushButton(self.widget) - self.ysphb_opendir.setMinimumSize(QtCore.QSize(0, 30)) - self.ysphb_opendir.setObjectName("ysphb_opendir") - self.horizontalLayout_20.addWidget(self.ysphb_opendir) - # self.horizontalLayout_20.setStretch(0, 2) - self.verticalLayout_8.addWidget(self.widget) - self.verticalLayout_9.addLayout(self.verticalLayout_8) - self.horizontalLayout_7.addLayout(self.verticalLayout_9) - self.tabWidget.addTab(self.tab_3, "") self.tab_4 = QtWidgets.QWidget() self.tab_4.setObjectName("tab_4") self.horizontalLayout_9 = QtWidgets.QHBoxLayout(self.tab_4) @@ -455,28 +277,51 @@ def setupUi(self, MainWindow): self.formLayout_5.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.hecheng_rate) self.horizontalLayout_10.addLayout(self.formLayout_5) - # self.formLayout_8 = QtWidgets.QFormLayout() - # self.formLayout_8.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) - # self.formLayout_8.setObjectName("formLayout_8") + self.tts_issrt = QtWidgets.QCheckBox(self.tab_2) self.tts_issrt.setObjectName("tts_issrt") - # self.formLayout_8.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.tts_issrt) + self.voice_autorate = QtWidgets.QCheckBox(self.tab_2) self.voice_autorate.setEnabled(False) self.voice_autorate.setObjectName("voice_autorate") - # self.formLayout_8.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.voice_autorate) - # self.formLayout_81 = QtWidgets.QFormLayout() - # self.formLayout_81.setFormAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) - # self.formLayout_81.setObjectName("formLayout_81") self.audio_ajust = QtWidgets.QCheckBox(self.tab_2) self.audio_ajust.setEnabled(False) self.audio_ajust.setObjectName("audio_ajust") - # self.formLayout_81.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.audio_ajust) + self.horizontalLayout_10.addWidget(self.tts_issrt) self.horizontalLayout_10.addWidget(self.voice_autorate) self.horizontalLayout_10.addWidget(self.audio_ajust) + + self.edge_volume_layout = QtWidgets.QHBoxLayout() + + self.volume_label = QtWidgets.QLabel(self.tab_2) + self.volume_label.setText("音量+" if config.defaulelang == 'zh' else "Volume+") + + self.volume_rate = QtWidgets.QSpinBox(self.tab_2) + # self.volume_rate.setMinimumSize(QtCore.QSize(0, 30)) + self.volume_rate.setMinimum(-95) + self.volume_rate.setMaximum(100) + self.volume_rate.setObjectName("volume_rate") + + self.pitch_label = QtWidgets.QLabel(self.tab_2) + self.pitch_label.setText("音调+" if config.defaulelang == 'zh' else "Pitch+") + self.pitch_rate = QtWidgets.QSpinBox(self.tab_2) + self.pitch_rate.setMinimum(-100) + self.pitch_rate.setMaximum(100) + self.pitch_rate.setObjectName("pitch_rate") + + self.edge_volume_layout.addWidget(self.volume_label) + self.edge_volume_layout.addWidget(self.volume_rate) + self.edge_volume_layout.addWidget(self.pitch_label) + self.edge_volume_layout.addWidget(self.pitch_rate) + + + self.horizontalLayout_10.addLayout(self.edge_volume_layout) + + + self.hecheng_startbtn = QtWidgets.QPushButton(self.tab_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -485,8 +330,10 @@ def setupUi(self, MainWindow): self.hecheng_startbtn.setSizePolicy(sizePolicy) self.hecheng_startbtn.setMinimumSize(QtCore.QSize(200, 30)) self.hecheng_startbtn.setObjectName("hecheng_startbtn") - self.horizontalLayout_10.addWidget(self.hecheng_startbtn) + self.hecheng_startbtn.setCursor(Qt.PointingHandCursor) + self.verticalLayout_4.addLayout(self.horizontalLayout_10) + self.verticalLayout_4.addWidget(self.hecheng_startbtn) self.gridLayout_3 = QtWidgets.QGridLayout() self.gridLayout_3.setObjectName("gridLayout_3") self.label_7 = QtWidgets.QLabel(self.tab_2) @@ -504,109 +351,298 @@ def setupUi(self, MainWindow): self.verticalLayout_4.addLayout(self.gridLayout_3) self.horizontalLayout_11.addLayout(self.verticalLayout_4) self.tabWidget.addTab(self.tab_2, "") - self.tab_6 = QtWidgets.QWidget() - self.tab_6.setObjectName("tab_6") - self.horizontalLayout_15 = QtWidgets.QHBoxLayout(self.tab_6) - self.horizontalLayout_15.setObjectName("horizontalLayout_15") - self.verticalLayout_7 = QtWidgets.QVBoxLayout() - self.verticalLayout_7.setObjectName("verticalLayout_7") - self.horizontalLayout_13 = QtWidgets.QHBoxLayout() - self.horizontalLayout_13.setObjectName("horizontalLayout_13") - self.geshi_layout = QtWidgets.QVBoxLayout() - self.geshi_layout.setObjectName("geshi_layout") - self.horizontalLayout_13.addLayout(self.geshi_layout) - self.verticalLayout_5 = QtWidgets.QVBoxLayout() - self.verticalLayout_5.setSizeConstraint(QtWidgets.QLayout.SetNoConstraint) - self.verticalLayout_5.setContentsMargins(5, -1, 5, -1) - self.verticalLayout_5.setObjectName("verticalLayout_5") - self.geshi_mp4 = QtWidgets.QPushButton(self.tab_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) + + + self.tab_7 = QtWidgets.QWidget() + self.tab_7.setObjectName("tab_7") + self.horizontalLayout_23 = QtWidgets.QHBoxLayout(self.tab_7) + self.horizontalLayout_23.setObjectName("horizontalLayout_23") + self.verticalLayout_13 = QtWidgets.QVBoxLayout() + self.verticalLayout_13.setObjectName("verticalLayout_13") + self.horizontalLayout_18 = QtWidgets.QHBoxLayout() + self.horizontalLayout_18.setObjectName("horizontalLayout_18") + self.label_13 = QtWidgets.QLabel(self.tab_7) + self.label_13.setObjectName("label_13") + self.horizontalLayout_18.addWidget(self.label_13) + self.fanyi_translate_type = QtWidgets.QComboBox(self.tab_7) + self.fanyi_translate_type.setMinimumSize(QtCore.QSize(100, 30)) + self.fanyi_translate_type.setObjectName("fanyi_translate_type") + self.horizontalLayout_18.addWidget(self.fanyi_translate_type) + self.label_613 = QtWidgets.QLabel(self.tab_7) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.geshi_mp4.sizePolicy().hasHeightForWidth()) - self.geshi_mp4.setSizePolicy(sizePolicy) - self.geshi_mp4.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_mp4.setObjectName("geshi_mp4") - self.verticalLayout_5.addWidget(self.geshi_mp4) - self.geshi_avi = QtWidgets.QPushButton(self.tab_6) - self.geshi_avi.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_avi.setObjectName("geshi_avi") - self.verticalLayout_5.addWidget(self.geshi_avi) - self.geshi_mov = QtWidgets.QPushButton(self.tab_6) - self.geshi_mov.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_mov.setObjectName("geshi_mov") - self.verticalLayout_5.addWidget(self.geshi_mov) - self.geshi_wav = QtWidgets.QPushButton(self.tab_6) - self.geshi_wav.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_wav.setObjectName("geshi_wav") - self.verticalLayout_5.addWidget(self.geshi_wav) - self.geshi_mp3 = QtWidgets.QPushButton(self.tab_6) - self.geshi_mp3.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_mp3.setObjectName("geshi_mp3") - self.verticalLayout_5.addWidget(self.geshi_mp3) - self.geshi_aac = QtWidgets.QPushButton(self.tab_6) - self.geshi_aac.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_aac.setObjectName("geshi_aac") - self.verticalLayout_5.addWidget(self.geshi_aac) - self.geshi_m4a = QtWidgets.QPushButton(self.tab_6) - self.geshi_m4a.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_m4a.setObjectName("geshi_m4a") - self.verticalLayout_5.addWidget(self.geshi_m4a) - self.geshi_flac = QtWidgets.QPushButton(self.tab_6) - self.geshi_flac.setMinimumSize(QtCore.QSize(0, 30)) - self.geshi_flac.setObjectName("geshi_flac") - self.verticalLayout_5.addWidget(self.geshi_flac) - self.line = QtWidgets.QFrame(self.tab_6) + sizePolicy.setHeightForWidth(self.label_613.sizePolicy().hasHeightForWidth()) + self.label_613.setSizePolicy(sizePolicy) + self.label_613.setMinimumSize(QtCore.QSize(0, 30)) + self.label_613.setObjectName("label_613") + self.horizontalLayout_18.addWidget(self.label_613) + self.fanyi_target = QtWidgets.QComboBox(self.tab_7) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth()) - self.line.setSizePolicy(sizePolicy) - self.line.setFrameShape(QtWidgets.QFrame.HLine) - self.line.setFrameShadow(QtWidgets.QFrame.Sunken) - self.line.setObjectName("line") - self.verticalLayout_5.addWidget(self.line) - self.horizontalLayout_13.addLayout(self.verticalLayout_5) - self.geshi_result = QtWidgets.QPlainTextEdit(self.tab_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.geshi_result.sizePolicy().hasHeightForWidth()) - self.geshi_result.setSizePolicy(sizePolicy) - self.geshi_result.setReadOnly(True) - self.geshi_result.setObjectName("geshi_result") - self.horizontalLayout_13.addWidget(self.geshi_result) - self.verticalLayout_7.addLayout(self.horizontalLayout_13) - self.horizontalLayout_14 = QtWidgets.QHBoxLayout() - self.horizontalLayout_14.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) - self.horizontalLayout_14.setObjectName("horizontalLayout_14") - self.geshi_output = QtWidgets.QPushButton(self.tab_6) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHeightForWidth(self.fanyi_target.sizePolicy().hasHeightForWidth()) + self.fanyi_target.setSizePolicy(sizePolicy) + self.fanyi_target.setMinimumSize(QtCore.QSize(120, 30)) + self.fanyi_target.setObjectName("fanyi_target") + self.horizontalLayout_18.addWidget(self.fanyi_target) + self.label_614 = QtWidgets.QLabel(self.tab_7) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.geshi_output.sizePolicy().hasHeightForWidth()) - self.geshi_output.setSizePolicy(sizePolicy) - self.geshi_output.setMaximumSize(QtCore.QSize(200, 30)) - self.geshi_output.setObjectName("geshi_output") - self.horizontalLayout_14.addWidget(self.geshi_output) - self.verticalLayout_7.addLayout(self.horizontalLayout_14) - self.horizontalLayout_15.addLayout(self.verticalLayout_7) - self.tabWidget.addTab(self.tab_6, "") - self.tab_5 = QtWidgets.QWidget() - self.tab_5.setObjectName("tab_5") - self.horizontalLayout_17 = QtWidgets.QHBoxLayout(self.tab_5) - self.horizontalLayout_17.setObjectName("horizontalLayout_17") - self.verticalLayout_10 = QtWidgets.QVBoxLayout() - self.verticalLayout_10.setObjectName("verticalLayout_10") - self.verticalLayout_11 = QtWidgets.QVBoxLayout() - self.verticalLayout_11.setObjectName("verticalLayout_11") - self.horizontalLayout_12 = QtWidgets.QHBoxLayout() - self.horizontalLayout_12.setObjectName("horizontalLayout_12") - self.label_131 = QtWidgets.QLabel(self.tab_5) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHeightForWidth(self.label_614.sizePolicy().hasHeightForWidth()) + self.label_614.setSizePolicy(sizePolicy) + self.label_614.setMinimumSize(QtCore.QSize(0, 30)) + self.label_614.setObjectName("label_614") + self.horizontalLayout_18.addWidget(self.label_614) + self.fanyi_proxy = QtWidgets.QLineEdit(self.tab_7) + self.fanyi_proxy.setMinimumSize(QtCore.QSize(0, 30)) + self.fanyi_proxy.setObjectName("fanyi_proxy") + self.horizontalLayout_18.addWidget(self.fanyi_proxy) + self.verticalLayout_13.addLayout(self.horizontalLayout_18) + self.horizontalLayout_19 = QtWidgets.QHBoxLayout() + self.horizontalLayout_19.setContentsMargins(-1, 20, -1, -1) + self.horizontalLayout_19.setObjectName("horizontalLayout_19") + self.fanyi_import = QtWidgets.QPushButton(self.tab_7) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_131.sizePolicy().hasHeightForWidth()) + sizePolicy.setHeightForWidth(self.fanyi_import.sizePolicy().hasHeightForWidth()) + self.fanyi_import.setSizePolicy(sizePolicy) + self.fanyi_import.setMinimumSize(QtCore.QSize(200, 30)) + self.fanyi_import.setObjectName("fanyi_import") + self.horizontalLayout_19.addWidget(self.fanyi_import) + self.fanyi_issrt = QtWidgets.QCheckBox(self.tab_7) + self.fanyi_issrt.setChecked(True) + self.fanyi_issrt.setDisabled(True) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.fanyi_issrt.sizePolicy().hasHeightForWidth()) + self.fanyi_issrt.setSizePolicy(sizePolicy) + self.fanyi_issrt.setObjectName("fanyi_issrt") + self.horizontalLayout_19.addWidget(self.fanyi_issrt) + self.horizontalLayout_22 = QtWidgets.QHBoxLayout() + self.horizontalLayout_22.setObjectName("horizontalLayout_22") + self.daochu = QtWidgets.QToolButton(self.tab_7) + self.daochu.setMinimumSize(QtCore.QSize(0, 28)) + self.daochu.setObjectName("daochu") + self.horizontalLayout_22.addWidget(self.daochu) + self.horizontalLayout_19.addLayout(self.horizontalLayout_22) + self.verticalLayout_13.addLayout(self.horizontalLayout_19) + self.fanyi_layout = QtWidgets.QHBoxLayout() + self.fanyi_layout.setObjectName("fanyi_layout") + self.fanyi_start = QtWidgets.QPushButton(self.tab_7) + self.fanyi_start.setMinimumSize(QtCore.QSize(120, 30)) + self.fanyi_start.setObjectName("fanyi_start") + self.fanyi_layout.addWidget(self.fanyi_start) + self.fanyi_targettext = QtWidgets.QPlainTextEdit(self.tab_7) + self.fanyi_targettext.setObjectName("fanyi_targettext") + self.fanyi_layout.addWidget(self.fanyi_targettext) + self.verticalLayout_13.addLayout(self.fanyi_layout) + self.horizontalLayout_23.addLayout(self.verticalLayout_13) + self.tabWidget.addTab(self.tab_7, "") + + self.tab = QtWidgets.QWidget() + self.tab.setObjectName("tab") + self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.tab) + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.yspfl_wrap = QtWidgets.QFrame(self.tab) + self.yspfl_wrap.setObjectName("yspfl_wrap") + self.horizontalLayout_4.addWidget(self.yspfl_wrap) + self.verticalLayout_6 = QtWidgets.QVBoxLayout() + self.verticalLayout_6.setObjectName("verticalLayout_6") + self.yspfl_widget = QtWidgets.QVBoxLayout() + self.yspfl_widget.setObjectName("yspfl_widget") + self.yspfl_startbtn = QtWidgets.QPushButton(self.tab) + self.yspfl_startbtn.setMinimumSize(QtCore.QSize(300, 40)) + self.yspfl_startbtn.setMaximumSize(QtCore.QSize(300, 40)) + self.yspfl_startbtn.setObjectName("yspfl_startbtn") + self.yspfl_widget.addWidget(self.yspfl_startbtn, 0, QtCore.Qt.AlignHCenter) + self.verticalLayout_6.addLayout(self.yspfl_widget) + self.verticalLayout_2 = QtWidgets.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setMinimumSize(QtCore.QSize(100, 30)) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.yspfl_videoinput = QtWidgets.QLineEdit(self.tab) + self.yspfl_videoinput.setMinimumSize(QtCore.QSize(0, 30)) + self.yspfl_videoinput.setReadOnly(True) + self.yspfl_videoinput.setObjectName("yspfl_videoinput") + self.gridLayout.addWidget(self.yspfl_videoinput, 0, 1, 1, 1) + self.yspfl_openbtn1 = QtWidgets.QPushButton(self.tab) + self.yspfl_openbtn1.setObjectName("yspfl_openbtn1") + self.gridLayout.addWidget(self.yspfl_openbtn1, 0, 2, 1, 1) + self.verticalLayout_2.addLayout(self.gridLayout) + self.gridLayout_2 = QtWidgets.QGridLayout() + self.gridLayout_2.setObjectName("gridLayout_2") + self.label_2 = QtWidgets.QLabel(self.tab) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy) + self.label_2.setMinimumSize(QtCore.QSize(100, 30)) + self.label_2.setObjectName("label_2") + self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 1) + self.yspfl_wavinput = QtWidgets.QLineEdit(self.tab) + self.yspfl_wavinput.setMinimumSize(QtCore.QSize(0, 30)) + self.yspfl_wavinput.setReadOnly(True) + self.yspfl_wavinput.setPlaceholderText("") + self.yspfl_wavinput.setObjectName("yspfl_wavinput") + self.gridLayout_2.addWidget(self.yspfl_wavinput, 0, 1, 1, 1) + self.yspfl_openbtn2 = QtWidgets.QPushButton(self.tab) + self.yspfl_openbtn2.setObjectName("yspfl_openbtn2") + self.gridLayout_2.addWidget(self.yspfl_openbtn2, 0, 2, 1, 1) + self.verticalLayout_2.addLayout(self.gridLayout_2) + self.verticalLayout_6.addLayout(self.verticalLayout_2) + self.horizontalLayout_4.addLayout(self.verticalLayout_6) + self.tabWidget.addTab(self.tab, "") + self.tab_3 = QtWidgets.QWidget() + # self.tab_3.setMinimumSize(QtCore.QSize(0, 674)) + self.tab_3.setObjectName("tab_3") + self.horizontalLayout_7 = QtWidgets.QHBoxLayout(self.tab_3) + self.horizontalLayout_7.setObjectName("horizontalLayout_7") + self.verticalLayout_9 = QtWidgets.QVBoxLayout() + self.verticalLayout_9.setObjectName("verticalLayout_9") + self.verticalLayout = QtWidgets.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.label_4 = QtWidgets.QLabel(self.tab_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy) + self.label_4.setMinimumSize(QtCore.QSize(100, 40)) + self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.label_4.setObjectName("label_4") + self.horizontalLayout_3.addWidget(self.label_4, 0, QtCore.Qt.AlignTop) + self.ysphb_videoinput = QtWidgets.QLineEdit(self.tab_3) + self.ysphb_videoinput.setMinimumSize(QtCore.QSize(0, 40)) + self.ysphb_videoinput.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) + self.ysphb_videoinput.setReadOnly(True) + self.ysphb_videoinput.setObjectName("ysphb_videoinput") + self.horizontalLayout_3.addWidget(self.ysphb_videoinput, 0, QtCore.Qt.AlignTop) + self.ysphb_selectvideo = QtWidgets.QPushButton(self.tab_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ysphb_selectvideo.sizePolicy().hasHeightForWidth()) + self.ysphb_selectvideo.setSizePolicy(sizePolicy) + self.ysphb_selectvideo.setMinimumSize(QtCore.QSize(150, 40)) + self.ysphb_selectvideo.setObjectName("ysphb_selectvideo") + self.horizontalLayout_3.addWidget(self.ysphb_selectvideo, 0, QtCore.Qt.AlignTop) + self.verticalLayout.addLayout(self.horizontalLayout_3) + self.horizontalLayout_5 = QtWidgets.QHBoxLayout() + self.horizontalLayout_5.setObjectName("horizontalLayout_5") + self.label_5 = QtWidgets.QLabel(self.tab_3) + self.label_5.setMinimumSize(QtCore.QSize(100, 40)) + self.label_5.setObjectName("label_5") + self.horizontalLayout_5.addWidget(self.label_5, 0, QtCore.Qt.AlignTop) + self.ysphb_wavinput = QtWidgets.QLineEdit(self.tab_3) + self.ysphb_wavinput.setMinimumSize(QtCore.QSize(0, 40)) + self.ysphb_wavinput.setReadOnly(True) + self.ysphb_wavinput.setObjectName("ysphb_wavinput") + self.horizontalLayout_5.addWidget(self.ysphb_wavinput, 0, QtCore.Qt.AlignTop) + self.ysphb_selectwav = QtWidgets.QPushButton(self.tab_3) + self.ysphb_selectwav.setMinimumSize(QtCore.QSize(150, 40)) + self.ysphb_selectwav.setObjectName("ysphb_selectwav") + self.horizontalLayout_5.addWidget(self.ysphb_selectwav, 0, QtCore.Qt.AlignTop) + self.verticalLayout.addLayout(self.horizontalLayout_5) + self.horizontalLayout_6 = QtWidgets.QHBoxLayout() + self.horizontalLayout_6.setObjectName("horizontalLayout_6") + self.label_6 = QtWidgets.QLabel(self.tab_3) + self.label_6.setMinimumSize(QtCore.QSize(100, 40)) + self.label_6.setObjectName("label_6") + self.horizontalLayout_6.addWidget(self.label_6, 0, QtCore.Qt.AlignTop) + self.ysphb_srtinput = QtWidgets.QLineEdit(self.tab_3) + self.ysphb_srtinput.setMinimumSize(QtCore.QSize(0, 40)) + self.ysphb_srtinput.setReadOnly(True) + self.ysphb_srtinput.setObjectName("ysphb_srtinput") + + self.horizontalLayout_6.addWidget(self.ysphb_srtinput, 0, QtCore.Qt.AlignTop) + self.ysphb_selectsrt = QtWidgets.QPushButton(self.tab_3) + self.ysphb_selectsrt.setMinimumSize(QtCore.QSize(150, 40)) + self.ysphb_selectsrt.setObjectName("ysphb_selectsrt") + self.horizontalLayout_6.addWidget(self.ysphb_selectsrt, 0, QtCore.Qt.AlignTop) + + + + self.horizontalLayout_replace = QtWidgets.QHBoxLayout() + self.horizontalLayout_replace.setObjectName("horizontalLayout_replace") + self.ysphb_replace = QtWidgets.QCheckBox(self.tab_3) + self.ysphb_replace.setObjectName("ysphb_replace") + self.ysphb_replace.setChecked(True) + self.ysphb_replace.setText(config.transobj['Preserve the original sound in the video']) + self.horizontalLayout_replace.addWidget(self.ysphb_replace, 0, QtCore.Qt.AlignTop) + + self.verticalLayout.addLayout(self.horizontalLayout_6) + self.verticalLayout.addLayout(self.horizontalLayout_replace) + + self.verticalLayout_9.addLayout(self.verticalLayout) + self.verticalLayout_8 = QtWidgets.QVBoxLayout() + self.verticalLayout_8.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) + self.verticalLayout_8.setObjectName("verticalLayout_8") + self.ysphb_startbtn = QtWidgets.QPushButton(self.tab_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ysphb_startbtn.sizePolicy().hasHeightForWidth()) + self.ysphb_startbtn.setSizePolicy(sizePolicy) + self.ysphb_startbtn.setMinimumSize(QtCore.QSize(250, 40)) + self.ysphb_startbtn.setObjectName("ysphb_startbtn") + self.verticalLayout_8.addWidget(self.ysphb_startbtn) + self.widget = QtWidgets.QWidget(self.tab_3) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + + self.widget.setSizePolicy(sizePolicy) + self.widget.setObjectName("widget") + self.horizontalLayout_20 = QtWidgets.QHBoxLayout(self.widget) + self.horizontalLayout_20.setObjectName("horizontalLayout_20") + self.ysphb_out = QtWidgets.QLineEdit(self.widget) + self.ysphb_out.setMinimumSize(QtCore.QSize(0, 30)) + self.ysphb_out.setReadOnly(True) + self.ysphb_out.setObjectName("ysphb_out") + self.horizontalLayout_20.addWidget(self.ysphb_out) + self.ysphb_opendir = QtWidgets.QPushButton(self.widget) + self.ysphb_opendir.setMinimumSize(QtCore.QSize(0, 30)) + self.ysphb_opendir.setObjectName("ysphb_opendir") + self.horizontalLayout_20.addWidget(self.ysphb_opendir) + + self.verticalLayout_8.addWidget(self.widget) + self.verticalLayout_9.addLayout(self.verticalLayout_8) + self.horizontalLayout_7.addLayout(self.verticalLayout_9) + self.tabWidget.addTab(self.tab_3, "") + + + + + self.tab_5 = QtWidgets.QWidget() + self.tab_5.setObjectName("tab_5") + self.horizontalLayout_17 = QtWidgets.QHBoxLayout(self.tab_5) + self.horizontalLayout_17.setObjectName("horizontalLayout_17") + self.verticalLayout_10 = QtWidgets.QVBoxLayout() + self.verticalLayout_10.setObjectName("verticalLayout_10") + self.verticalLayout_11 = QtWidgets.QVBoxLayout() + self.verticalLayout_11.setObjectName("verticalLayout_11") + self.horizontalLayout_12 = QtWidgets.QHBoxLayout() + self.horizontalLayout_12.setObjectName("horizontalLayout_12") + self.label_131 = QtWidgets.QLabel(self.tab_5) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_131.sizePolicy().hasHeightForWidth()) self.label_131.setSizePolicy(sizePolicy) self.label_131.setMinimumSize(QtCore.QSize(100, 40)) self.label_131.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter) @@ -680,95 +716,12 @@ def setupUi(self, MainWindow): self.verticalLayout_10.addLayout(self.verticalLayout_12) self.horizontalLayout_17.addLayout(self.verticalLayout_10) self.tabWidget.addTab(self.tab_5, "") - self.tab_7 = QtWidgets.QWidget() - self.tab_7.setObjectName("tab_7") - self.horizontalLayout_23 = QtWidgets.QHBoxLayout(self.tab_7) - self.horizontalLayout_23.setObjectName("horizontalLayout_23") - self.verticalLayout_13 = QtWidgets.QVBoxLayout() - self.verticalLayout_13.setObjectName("verticalLayout_13") - self.horizontalLayout_18 = QtWidgets.QHBoxLayout() - self.horizontalLayout_18.setObjectName("horizontalLayout_18") - self.label_13 = QtWidgets.QLabel(self.tab_7) - self.label_13.setObjectName("label_13") - self.horizontalLayout_18.addWidget(self.label_13) - self.fanyi_translate_type = QtWidgets.QComboBox(self.tab_7) - self.fanyi_translate_type.setMinimumSize(QtCore.QSize(100, 30)) - self.fanyi_translate_type.setObjectName("fanyi_translate_type") - self.horizontalLayout_18.addWidget(self.fanyi_translate_type) - self.label_613 = QtWidgets.QLabel(self.tab_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_613.sizePolicy().hasHeightForWidth()) - self.label_613.setSizePolicy(sizePolicy) - self.label_613.setMinimumSize(QtCore.QSize(0, 30)) - self.label_613.setObjectName("label_613") - self.horizontalLayout_18.addWidget(self.label_613) - self.fanyi_target = QtWidgets.QComboBox(self.tab_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.fanyi_target.sizePolicy().hasHeightForWidth()) - self.fanyi_target.setSizePolicy(sizePolicy) - self.fanyi_target.setMinimumSize(QtCore.QSize(120, 30)) - self.fanyi_target.setObjectName("fanyi_target") - self.horizontalLayout_18.addWidget(self.fanyi_target) - self.label_614 = QtWidgets.QLabel(self.tab_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_614.sizePolicy().hasHeightForWidth()) - self.label_614.setSizePolicy(sizePolicy) - self.label_614.setMinimumSize(QtCore.QSize(0, 30)) - self.label_614.setObjectName("label_614") - self.horizontalLayout_18.addWidget(self.label_614) - self.fanyi_proxy = QtWidgets.QLineEdit(self.tab_7) - self.fanyi_proxy.setMinimumSize(QtCore.QSize(0, 30)) - self.fanyi_proxy.setObjectName("fanyi_proxy") - self.horizontalLayout_18.addWidget(self.fanyi_proxy) - self.verticalLayout_13.addLayout(self.horizontalLayout_18) - self.horizontalLayout_19 = QtWidgets.QHBoxLayout() - self.horizontalLayout_19.setContentsMargins(-1, 20, -1, -1) - self.horizontalLayout_19.setObjectName("horizontalLayout_19") - self.fanyi_import = QtWidgets.QPushButton(self.tab_7) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.fanyi_import.sizePolicy().hasHeightForWidth()) - self.fanyi_import.setSizePolicy(sizePolicy) - self.fanyi_import.setMinimumSize(QtCore.QSize(200, 30)) - self.fanyi_import.setObjectName("fanyi_import") - self.horizontalLayout_19.addWidget(self.fanyi_import) - self.fanyi_issrt = QtWidgets.QCheckBox(self.tab_7) - self.fanyi_issrt.setChecked(True) - self.fanyi_issrt.setDisabled(True) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.fanyi_issrt.sizePolicy().hasHeightForWidth()) - self.fanyi_issrt.setSizePolicy(sizePolicy) - self.fanyi_issrt.setObjectName("fanyi_issrt") - self.horizontalLayout_19.addWidget(self.fanyi_issrt) - self.horizontalLayout_22 = QtWidgets.QHBoxLayout() - self.horizontalLayout_22.setObjectName("horizontalLayout_22") - self.daochu = QtWidgets.QToolButton(self.tab_7) - self.daochu.setMinimumSize(QtCore.QSize(0, 28)) - self.daochu.setObjectName("daochu") - self.horizontalLayout_22.addWidget(self.daochu) - self.horizontalLayout_19.addLayout(self.horizontalLayout_22) - self.verticalLayout_13.addLayout(self.horizontalLayout_19) - self.fanyi_layout = QtWidgets.QHBoxLayout() - self.fanyi_layout.setObjectName("fanyi_layout") - self.fanyi_start = QtWidgets.QPushButton(self.tab_7) - self.fanyi_start.setMinimumSize(QtCore.QSize(120, 30)) - self.fanyi_start.setObjectName("fanyi_start") - self.fanyi_layout.addWidget(self.fanyi_start) - self.fanyi_targettext = QtWidgets.QPlainTextEdit(self.tab_7) - self.fanyi_targettext.setObjectName("fanyi_targettext") - self.fanyi_layout.addWidget(self.fanyi_targettext) - self.verticalLayout_13.addLayout(self.fanyi_layout) - self.horizontalLayout_23.addLayout(self.verticalLayout_13) - self.tabWidget.addTab(self.tab_7, "") + + + + + + self.horizontalLayout_2.addWidget(self.tabWidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) @@ -804,7 +757,7 @@ def retranslateUi(self, MainWindow): # self.label_9.setText(box_lang.get("Whisper model")) self.shibie_startbtn.setText(box_lang.get("Start")) self.shibie_savebtn.setText(box_lang.get("Save to srt..")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), box_lang.get("Voice recognition")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), '批量音视频转字幕' if config.defaulelang=='zh'else 'Audio Video to Subtitle') self.label_10.setText(box_lang.get("Subtitle lang")) self.label_8.setText("TTS") self.label_11.setText(box_lang.get("Select role")) @@ -819,17 +772,7 @@ def retranslateUi(self, MainWindow): self.hecheng_out.setPlaceholderText(box_lang.get("Set the name of the generated audio file here. If not filled in, use the time and date command")) self.hecheng_opendir.setText(box_lang.get("Open dir")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), box_lang.get("Text to speech")) - self.geshi_mp4.setText(box_lang.get("Convert mp4->")) - self.geshi_avi.setText(box_lang.get("Convert avi->")) - self.geshi_mov.setText(box_lang.get("Convert mov->")) - self.geshi_wav.setText(box_lang.get("Convert wav->")) - self.geshi_mp3.setText(box_lang.get("Convert mp3->")) - self.geshi_aac.setText(box_lang.get("Convert aac->")) - self.geshi_m4a.setText(box_lang.get("Convert m4a->")) - self.geshi_flac.setText(box_lang.get("Conver flac->")) - self.geshi_result.setPlaceholderText(box_lang.get("The conversion result is displayed here")) - self.geshi_output.setText(box_lang.get("Open output dir")) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_6), box_lang.get("Audio and video format conversion")) + self.label_131.setText(box_lang.get("Audio file 1")) self.hun_file1btn.setText(box_lang.get("Select the first audio file")) self.label_141.setText(box_lang.get("Audio file 2")) diff --git a/videotrans/util/tools.py b/videotrans/util/tools.py index 92733975..ad85412a 100644 --- a/videotrans/util/tools.py +++ b/videotrans/util/tools.py @@ -240,9 +240,9 @@ def runffmpeg(arg, *, noextname=None, # run ffprobe 获取视频元信息 def runffprobe(cmd): - cmd[-1] = os.path.normpath(cmd[-1]) + # cmd[-1] = os.path.normpath(cmd[-1]) try: - p = subprocess.run(['ffprobe'] + cmd, + p = subprocess.run( cmd if isinstance(cmd,str) else ['ffprobe'] + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8", @@ -250,7 +250,7 @@ def runffprobe(cmd): check=True, creationflags=0 if sys.platform != 'win32' else subprocess.CREATE_NO_WINDOW) if p.stdout: - return p.stdout + return p.stdout.strip() raise Exception(str(p.stderr)) except subprocess.CalledProcessError as e: msg = f'ffprobe call error:{str(e.stdout)},{str(e.stderr)}' @@ -565,15 +565,21 @@ def precise_speed_up_audio(*, file_path=None, out=None, target_duration_ms=None, return True -def show_popup(title, text): +def show_popup(title, text,parent=None): from PySide6.QtGui import QIcon + from PySide6.QtCore import Qt from PySide6.QtWidgets import QMessageBox + msg = QMessageBox() msg.setWindowTitle(title) msg.setWindowIcon(QIcon(f"{config.rootdir}/videotrans/styles/icon.ico")) msg.setText(text) msg.addButton(QMessageBox.Yes) msg.addButton(QMessageBox.Cancel) + msg.setWindowModality(Qt.ApplicationModal) # 设置为应用模态 + msg.setWindowFlags(msg.windowFlags() | Qt.WindowStaysOnTopHint) # 置于顶层 + + # msg.addButton(a2) msg.setIcon(QMessageBox.Information) x = msg.exec() # 显示消息框 @@ -788,6 +794,12 @@ def is_novoice_mp4(novoice_mp4, noextname): continue return True +def match_target_amplitude(sound, target_dBFS): + change_in_dBFS = target_dBFS - sound.dBFS + return sound.apply_gain(change_in_dBFS) + + + # 从视频中切出一段时间的视频片段 cuda + h264_cuvid def cut_from_video(*, ss="", to="", source="", pts="", out="", fps=None): @@ -906,8 +918,8 @@ def send_notification(title, message): notification.notify( title=title[:60], message=message[:120], - ticker="视频翻译与配音", - app_name="视频翻译与配音", # config.uilanglist['SP-video Translate Dubbing'], + ticker="pyVideoTrans", + app_name="pyVideoTrans", # config.uilanglist['SP-video Translate Dubbing'], app_icon=os.path.join(config.rootdir, 'videotrans/styles/icon.ico'), timeout=10 # Display duration in seconds ) @@ -990,14 +1002,13 @@ def remove_silence_from_end(input_file_path, silence_threshold=-50.0, chunk_size format = "wav" if isinstance(input_file_path, str): format = input_file_path.split('.')[-1].lower() - if format in ['wav', 'mp3']: - audio = AudioSegment.from_file(input_file_path, format=format) + if format in ['wav', 'mp3','m4a']: + audio = AudioSegment.from_file(input_file_path, format=format if format in ['wav','mp3'] else 'mp4') else: # 转为mp3 - tmp = input_file_path + ".mp3" try: - runffmpeg(['-y', '-i', input_file_path, tmp]) - audio = AudioSegment.from_file(tmp, format="mp3") + runffmpeg(['-y', '-i', input_file_path, input_file_path + ".mp3"]) + audio = AudioSegment.from_file(input_file_path + ".mp3", format="mp3") except Exception: return input_file_path @@ -1014,8 +1025,6 @@ def remove_silence_from_end(input_file_path, silence_threshold=-50.0, chunk_size # If we have nonsilent chunks, get the start and end of the last nonsilent chunk if nonsilent_chunks: start_index, end_index = nonsilent_chunks[-1] - elif isinstance(input_file_path, str): - return True else: # If the whole audio is silent, just return it as is return input_file_path @@ -1025,14 +1034,15 @@ def remove_silence_from_end(input_file_path, silence_threshold=-50.0, chunk_size if is_start and nonsilent_chunks[0] and nonsilent_chunks[0][0] > 0: trimmed_audio = audio[nonsilent_chunks[0][0]:end_index] if isinstance(input_file_path, str): - if format in ['wav', 'mp3']: - return trimmed_audio.export(input_file_path, format=format) + if format in ['wav', 'mp3','m4a']: + trimmed_audio.export(input_file_path, format=format if format in ['wav','mp3'] else 'mp4') + return input_file_path try: - trimmed_audio.export(tmp, format="mp3") - runffmpeg(['-y', '-i', tmp, input_file_path]) + trimmed_audio.export(input_file_path + ".mp3", format="mp3") + runffmpeg(['-y', '-i', input_file_path + ".mp3", input_file_path]) except Exception: pass - return True + return input_file_path return trimmed_audio