pyvideotrans / videotrans /task /taskcfg.py
jameson512
Upload files via LFS
1f235c3
Raw
History Blame Contribute Delete
11.8 kB
import os
from dataclasses import dataclass, asdict
from typing import Optional, Union
from pathlib import Path
@dataclass
class InputFile:
name: Union[os.PathLike,str]=None
dirname:Union[os.PathLike,str]=None
basename:str=None
noextname:str=None
ext:str=None
uuid:str=None
target_dir:Optional[str]=None
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
return setattr(self, key, value)
# 处理:dataclass_obj | dict_obj
def __or__(self, other):
if isinstance(other, dict):
return asdict(self) | other
return NotImplemented
# 处理:dict_obj | dataclass_obj
def __ror__(self, other):
if isinstance(other, dict):
return other | asdict(self)
return NotImplemented
def get(self, key,default=None):
return getattr(self,key,default)
@dataclass
class SignMsg:
type:str="logs"
uuid:str=""
text:str=""
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
return setattr(self, key, value)
def get(self, key,default=None):
return getattr(self,key,default)
# 应该在结束状态
def is_stop(self):
return self.type in ['end','stop','succeed','error']
def is_error(self):
return self.type == 'error'
@dataclass
class SrtItem:
text: str = ""
start_time: Union[int,float] = 0
end_time: Union[int,float] = 0
startraw: str = ''
endraw: str = ''
line: Optional[int] = 1
time: Optional[str] = ""
spk: Optional[str] = ""#说话人id
filename: Optional[str] = ""#对应音频片段
def __getitem__(self, key):
return getattr(self, key)
def __setitem__(self, key, value):
return setattr(self, key, value)
def get(self, key,default=None):
return getattr(self,key,default)
def items(self):
_names=("line","time","start_time","end_time","startraw","endraw","text","spk","filename")
for k in _names:
yield k,getattr(self,k)
def __iter__(self):
_names=("line","time","start_time","end_time","startraw","endraw","text","spk","filename")
return iter(_names)
# 视频翻译流程使用全部属性
@dataclass
class TaskCfgBase:
# 通用区域
uuid: str = None # 默认唯一任务id
name: Union[os.PathLike,str]=None # 规范化处理的原始文件绝对路径 D:/XXX/1.MP4
dirname: Union[os.PathLike,str]=None # 原始文件所在目录 D:/XXX
noextname: str = None # 去掉扩展名的原始视频名
basename: str = None # noextname + ext 名 1.mp4
ext: str = None # 扩展名 mp4
target_dir: str = None # 输出文件夹,目标视频输出文件夹
cache_folder: str = None # 当前文件的临时文件夹,用于存放临时过程文件
is_cuda: bool = False # 是否使用cuda加速
source_language: str = None # 原始语言名称或代码
source_language_code: str = None # 原始语言代码
source_sub: Union[os.PathLike,str]=None # 原始字幕文件绝对路径
source_wav: Union[os.PathLike,str]=None # 原始语言音频,存在于临时文件夹下
source_wav_output: Union[os.PathLike,str]=None # 原始语言音频输出,存在于目标文件夹下
target_language: str = None # 目标语言名称或代码
target_language_code: str = None # 目标语言代码
target_sub: Union[os.PathLike,str]=None # 目标字幕文件绝对路径
target_wav: Union[os.PathLike,str]=None # 目标语言音频,存在于临时文件夹下
target_wav_output: Union[os.PathLike,str]=None # 目标语言音频输出,存在于目标文件夹下
# 语音识别
@dataclass
class TaskCfgSTT(TaskCfgBase):
####### 语音识别相关
detect_language: str = None # 字幕检测语言代码
recogn_type: int = None # 语音识别渠道
model_name: str = None # 模型名字
shibie_audio: Union[os.PathLike,str]=None # 转为 pcm_s16le 16k 作为语音识别的音频文件
remove_noise: bool = False # 是否移除噪声
enable_diariz: bool = False # 是否进行说话人识别
nums_diariz: int = 0 # 是否进行说话人识别
rephrase: int = 0 # 0 默认断句不处理 1=LLM重新断句
fix_punc: int = 0 # 0=默认,1=恢复标点符号,2=移除所有标点
# 配音
@dataclass
class TaskCfgTTS(TaskCfgBase):
######## 配音相关
tts_type: int = None # 语音合成渠道
volume: str = "+0%" # 音量
pitch: str = "+0Hz" # 音调
voice_rate: str = "+0%" # 语速
voice_role: str = None # 配音角色
voice_autorate: bool = False # 是否音频自动加速
video_autorate: bool = False # 是否视频自动慢速
remove_silent_mid: bool = False # 是否移除字幕间的空隙
align_sub_audio: bool = True # 是否强制对齐字幕和声音
# 字幕翻译
@dataclass
class TaskCfgSTS(TaskCfgBase):
######## 字幕翻译相关
translate_type: int = None # 字幕翻译渠道
# 视频翻译所有
@dataclass
class TaskCfgVTT(TaskCfgSTT, TaskCfgTTS, TaskCfgSTS):
############## 视频翻译特有
subtitle_language: str = None # 软字幕嵌入语言代码,3位
app_mode: str = "biaozhun" # 工作模式 biaohzun tiqu
subtitles: str = "" # 已存在的字幕文本,例如预先导入的
targetdir_mp4: Union[os.PathLike,str]=None # 最终输出合成后的mp4
novoice_mp4: Union[os.PathLike,str]=None # 从原始视频分离出的无声视频
is_separate: bool = False # 是否进行人声、背景音分离
embed_bgm: bool = True # 是否需要重新嵌入背景音
instrument: Union[os.PathLike,str]=None # 分离出的背景音频
vocal: Union[os.PathLike,str]=None # 分离出的人声音频
clear_cache: bool = False # 是否清理已存在的文件
background_music: Union[os.PathLike,str]=None # 手动添加的背景音频,整理后的完整路径
subtitle_type: int = 0 # 软硬字幕嵌入类型 0=不嵌入,1=硬字幕,2=软字幕,3=双硬,4=双软
only_out_mp4: bool = False # 是否仅仅输出mp4,仅视频翻译使用
recogn2pass: bool = False # 对配音音频再次识别
output_srt: int = 0 # 转录并翻译 模式输出字幕类似,0=单字幕,1=目标语言在线双字幕,2=目标语言在上双字幕
copysrt_rawvideo: bool = False # 是否将生成的字幕复制到视频目录下
loop_backaudio: int = 0 # 循环背景音 或 延长拉伸背景音
backaudio_volume: float = 0.8 # 背景音量
batch:bool=False# 批量翻译模式或单视频翻译模式
batch_size:int=0#0批量并发模式,>0 每批n个
def __repr__(self):
_msg=[]
from videotrans.recognition import ALLOW_CHANGE_MODEL
from videotrans.util.tools import get_recogn_type,get_tanslate_type,get_tts_type
from videotrans.configure.config import tr,settings
isTrue={True:"已选",False:"未选"}
_duanjus=["默认断句","LLM重新断句"]
_subtitles=[
tr('nosubtitle'),
tr('embedsubtitle'),
tr('softsubtitle'),
tr('embedsubtitle2'),
tr('softsubtitle2')
]
_outs=["单字幕","目标语言在下双字幕","目标语言在上双字幕"]
_loops=["循环播放","拉长(降速播放)"]
if self.app_mode=="tiqu":
_msg.append(f"[TaskCfgVTT]当前工作模式: 转录并翻译字幕")
else:
_msg.append(f'[TaskCfgVTT]当前工作模式: 翻译视频或音频 {"批量翻译模式" if self.batch else "单视频模式"}' + (f" 每批{self.batch_size}个" if self.batch and self.batch_size>0 else "") )
_msg.append(f'原始输入文件名: {self.name}, \n输出结果保存到文件夹: {self.target_dir},\n临时文件夹: {self.cache_folder}')
_msg.append(f'{isTrue[self.clear_cache]} 清理已存在')
_msg.append(f'{isTrue[self.is_cuda]} 启用CUDA加速')
_msg.append(f'{isTrue[self.remove_noise]} 降噪')
if self.enable_diariz:
_msg.append(f'已选 识别说话人,最大说话人数量{"不限制" if self.nums_diariz<1 else self.nums_diariz+1}')
if self.fix_punc>0:
_msg.append(f'{"已选 恢复标点符号" if self.fix_punc==1 else "已选 删除所有标点符号"}')
_msg.append(f"{tr('Speech Recognit')}:{get_recogn_type(self.recogn_type)}, model_name: {self.model_name if self.recogn_type in ALLOW_CHANGE_MODEL else ''}, 发音语言: {self.source_language}, 断句方式:{_duanjus[self.rephrase]}")
if self.target_language in [None,'No','-'] or self.source_language==self.target_language:
_msg.append(f'{"发音语言和目标语言相同" if self.source_language==self.target_language else "未选 目标语言"},不翻译字幕')
else:
_msg.append(f"{tr('Translate channel')}:{get_tanslate_type(self.translate_type)},原始语言:{self.source_language},目标语言:{self.target_language}, {isTrue[settings['aisendsrt']]} {tr('Send SRT')}")
if self.app_mode=='tiqu':
if self.copysrt_rawvideo:
_msg.append('已选 将生成的字幕复制到视频目录下')
_msg.append(f"{tr('Subtitle format:')}: {_outs[self.output_srt]}")
else:
if self.voice_role in [None,'No']:
_msg.append('未选 配音角色,不进行配音')
else:
_msg.append(f"{tr('Dubbing channel')}:{get_tts_type(self.tts_type)}, 角色:{self.voice_role}, 配音语言:{self.target_language}, {isTrue[self.recogn2pass]} 二次语音识别")
if self.recogn2pass and self.subtitle_type>2:
_msg.append('\t[已选中 二次语音识别,但不会生效,因已选择 嵌入双字幕,需保证原始和目标字幕时间轴一致,而二次语音识别会重新生成时间轴不同的目标字幕]')
_msg.append(f'音量:{self.volume}, 语速:{self.voice_rate}, {isTrue[self.voice_autorate]} 音频加速, {isTrue[self.video_autorate]} 视频慢速')
if not self.voice_autorate and not self.video_autorate:
_msg.append(f'{isTrue[self.remove_silent_mid]} 移除字幕间空隙, {isTrue[self.align_sub_audio]} 强制对齐字幕和声音')
_msg.append(f'字幕: {_subtitles[self.subtitle_type]} {_outs[self.output_srt] if self.subtitle_type >2 else ""}')
_vocal_exists=self.vocal and Path(self.vocal).exists()
_instr_exists=self.instrument and Path(self.instrument).exists()
if self.is_separate or self.background_music or _vocal_exists or _instr_exists:
_str=f"{isTrue[self.is_separate]} 分离人声与背景声"
if self.embed_bgm:
_str+=f', 已选 重新嵌入背景声, 背景音量{self.backaudio_volume}, 背景声音时长 短于 视频时长时: {_loops[self.loop_backaudio]}'
if self.background_music:
_str+=f', 手动添加了背景音频:{self.background_music}\n'
if _vocal_exists:
_str+=',存在分离后的纯净人声文件'
if _instr_exists:
_str+=',存在分离后的背景声音文件'
_msg.append(_str)
if self.only_out_mp4:
_msg.append('已选 仅输出mp4')
_msg.append(f'代理地址:{settings.get("proxy")}')
return "\n".join(_msg)