jameson512
Upload files via LFS
1f235c3
Raw
History Blame Contribute Delete
6.63 kB
import copy
import shutil
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Union
from videotrans.configure.config import tr, app_cfg, logger, ROOT_DIR
from videotrans.configure.base import BaseCon
from videotrans.task.taskcfg import TaskCfgBase, SrtItem
@dataclass
class BaseTask(BaseCon):
# 各项配置信息,例如 翻译、配音、识别渠道等
cfg: TaskCfgBase = field(default_factory=TaskCfgBase, repr=False)
# 进度记录
precent: int = 1
# 需要配音的原始字幕信息 List[dict]
queue_tts: List = field(default_factory=list, repr=False)
# 是否已结束
hasend: bool = False
# 是否需要语音识别
should_recogn: bool = False
# 是否需要字幕翻译
should_trans: bool = False
# 是否需要配音
should_dubbing: bool = False
# 是否需要人声分离
should_separate: bool = False
# 是否需要嵌入配音或字幕
should_hebing: bool = False
def __post_init__(self):
super().__post_init__()
if self.cfg.uuid:
self.uuid = self.cfg.uuid
# 预先处理,例如从视频中拆分音频、人声背景分离、转码等
def prepare(self):
pass
# 语音识别创建原始语言字幕
def recogn(self):
pass
# 说话人识别,Funasr/豆包语音识别大模型 /Deepgram 除外,再判断是否已有说话人,Gemini/openai gpt4-dia 会生成说话人
def diariz(self):
pass
# 将原始语言字幕翻译到目标语言字幕
def trans(self):
pass
# 根据 queue_tts 进行配音
def dubbing(self):
pass
# 配音加速、视频慢速对齐
def align(self):
pass
# 视频、音频、字幕合并生成结果文件
def assembling(self):
pass
# 删除临时文件,移动或复制,发送成功消息
def task_done(self):
pass
# 删掉尺寸为0的无效文件
def _unlink_size0(self, file: Union[str, List[str]]):
if not file: return
files = [file] if isinstance(file, str) else file
for f in files:
p = Path(f)
if p.exists() and p.stat().st_size == 0:
p.unlink(missing_ok=True)
# 保存字幕文件 到目标文件夹
def _save_srt_target(self, srtstr: List[SrtItem], file: str):
from videotrans.util.help_srt import get_srt_from_list
try:
txt = get_srt_from_list(srtstr)
with open(file, "w", encoding="utf-8", errors="ignore") as f:
f.write(txt)
except Exception as e:
from videotrans.configure.excepts import VideoTransError
raise VideoTransError(f'保存字幕前格式化srt失败:{file=}') from e
self.signal(text=Path(file).read_text(encoding='utf-8', errors="ignore"), type='replace_subtitle')
return True
# 如果启用了 LLM重新断句,则跳过该步骤,LLM断句后时间轴发生变更,无法和原始字幕对齐
def check_target_sub(self, source_srt_list: List[SrtItem], target_srt_list: List[SrtItem]) -> List[SrtItem]:
source_len = len(source_srt_list)
target_len = len(target_srt_list)
if source_len == target_len:
logger.debug(f'原始语言字幕和目标语言字幕行数一致,均为 {source_len=}')
return target_srt_list
logger.warning(f'翻译结果行数{target_len},原始字幕行数{source_len},不一致,根据原始字幕时间轴获取对应目标字幕文本')
# 根据原始字幕的时间轴,到目标字幕内寻找同样时间轴的字幕文本,更准确
_time2srt={}
for it in target_srt_list:
_time2srt[it['time']]=it['text']
logger.debug(f'翻译结果行数{target_len} > 原始字幕行{source_len},根据原始字幕的时间轴,到目标字幕内寻找同样时间轴的字幕文本')
_source=copy.deepcopy(source_srt_list)
for it in _source:
it['text']=_time2srt.get(it['time'],'')
return _source
# 手动调用设为结束,成功完成或出错时
def set_end(self, succeed=False):
self.hasend = True
if succeed:
self.precent = 100
if self.uuid in app_cfg.stoped_uuid_set:
return
self.signal(text=f"{self.cfg.name}", type='succeed')
if app_cfg.exec_mode=="cli":
print(f'Save to:[ {self.cfg.target_dir} ]')
else:
from videotrans.util.help_ffmpeg import send_notification
send_notification(tr('Succeed'), f"{self.cfg.basename}")
# 清理临时文件
try:
if self.cfg.cache_folder:
shutil.rmtree(self.cfg.cache_folder, ignore_errors=True)
except Exception as e:
logger.exception(f'任务结束后清理临时文件失败,跳过,{e}:{self.cfg.cache_folder=}', exc_info=True)
app_cfg.stoped_uuid_set.add(self.uuid)
async def _edgetts_single(self, target_audio, kwargs):
from edge_tts import Communicate
from io import BytesIO
from videotrans.configure.excepts import DubbingSrtError
useproxy_initial = None if not self.proxy_str or Path(
f'{ROOT_DIR}/edgetts-noproxy.txt').exists() else self.proxy_str
proxies_to_try = [useproxy_initial]
if useproxy_initial is not None:
proxies_to_try.append(None)
for proxy in proxies_to_try:
try:
audio_buffer = BytesIO()
communicate_task = Communicate(
text=kwargs['text'],
voice=kwargs['voice'],
rate=kwargs['rate'],
volume=kwargs['volume'],
proxy=proxy,
pitch=kwargs['pitch']
)
idx = 0
async for chunk in communicate_task.stream():
if chunk["type"] == "audio":
audio_buffer.write(chunk["data"])
self.signal(text=f'{idx} segment')
idx += 1
audio_buffer.seek(0)
from pydub import AudioSegment
au = AudioSegment.from_file(audio_buffer, format="mp3")
au.export(target_audio, format='mp3')
return
except Exception as e:
raise DubbingSrtError(f'edge-tts error:{target_audio=}') from e
raise DubbingSrtError(f'Dubbing error')