手撸个视频翻译和配音工具玩玩,感觉马马虎虎

1,188 阅读8分钟

近来得空研究了下视频翻译,即将某种语言的视频处理后,显示另一种语言的字幕并使用该语言进行配音。最终实现了这种效果:

可将一种语言的视频翻译为另一种语言和配音的视频。 语音识别基于 openai-whisper 开源模型 文字翻译使用 google 翻译api 文字合成语音使用 Microsoft Edge TTS 背景音消除使用 Spleeter 无需购买任何商业接口,也无需花费一分钱

github开源地址

p1.png

原本只打算语音识别转为文字后,生成字幕就完结了,因为实在没有找到满足“自然音色、准确度好、容易安装”这些条件的好的语音合成方案,比如facebook的 seamless_communication mozilla TTS , 又从 huggingface.co/ 找了不少模型,结果都不理想,貌似除了针对性训练,其他效果都很差,不具备可用性。

百度语音、讯飞等国内api只能提供中英语言,而无法提供其他语言比如日语、韩语等。 Azuer googleCloud 的效果还不错,可惜不面向国内服务,购买或免费试用都过于困难。

这几天突然想到,edge浏览器自带语音朗读功能,一般 win10 win11 上自带安装edge,直接调用 edge tts 不就得了呗。 说干就干,github上搜索了些 edge-tts相关项目,参考了下,将之前未完成的语音配音继续完成。

整体思路如下:

image.png

技术栈:python3.10 + ffmpeg + openai-whisper + Spleeter + pydub + googleApi

从视频中提取出音频,然后将音频切割分段,对音频通过 openai-whisper 语音识别得到文字,将文字调用google api 翻译为想要的语言文字,整理为srt字幕文件。

再将翻译后的语言文字,通过 Edge TTS 语音合成接口,生成相应语音片段,最后将语音片段、字幕和删掉音频轨道的视频合并,生成翻译后的视频

实现步骤

1. 分离视频和音频

原始 mp4 视频使用 ffmpeg 提取出音频,ffmpeg 命令如下

ffmpeg -i 1.mp4 -acodec pcm_s16le -f s16le -ac 1 -ar 16000 -f wav audio.wav

分离后的音频可以去除背景音,以便识别更准确,采用 Spleeter 库,适合背景是音乐的音频

from spleeter.separator import Separator
separator = Separator('spleeter:2stems', multiprocess=False)
separator.separate_to_file(a_name, destination=dirname, filename_format="{filename}{instrument}.{codec}")

这是一个强大的提取背景音乐的库,详细使用方法见 github.com/deezer/sple…

2. pydub 切分音频

将音频文件按照静音片段分隔为各个片段,以方便识别, 分割使用pydub库进行,安装命令pip install pydub


# split audio by silence
def shorten_voice(normalized_sound):
    normalized_sound=match_target_amplitude(normalized_sound,-20.0)
    max_interval=10000
    buffer=500
    nonsilent_data = []
    audio_chunks = detect_nonsilent(normalized_sound, min_silence_len=int(config.video['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
    

3. 识别语音为文字

调用 openai-whisper 的语音识别库进行,安装命令 pip install SpeechRecognition

        r=speech_recognition.Recognizer()
        text = r.recognize_whisper(audio_listened, language="en")

4. 翻译识别结果为目标语言文字

根据识别到的文本,再调用 google翻译api 翻译为所需语言的文字,采用requests直接抓取google翻译页面,然后提取结果的白嫖方案

# google 翻译
def googletrans(text, src, dest):
    url = f"https://translate.google.com/m?sl={urllib.parse.quote(src)}&tl={urllib.parse.quote(dest)}&hl={urllib.parse.quote(dest)}&q={urllib.parse.quote(text)}"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }
    proxies = None
    if "http_proxy" in os.environ:
        proxies = {
            'http': os.environ['http_proxy'],
            'https': os.environ['https_proxy']
        }
    try:
        response = requests.get(url, proxies=proxies, headers=headers, timeout=40)
        if response.status_code != 200:
            return f"error translation code={response.status_code}"
        re_result = re.findall(
            r'(?s)class="(?:t0|result-container)">(.*?)<', response.text)
    except:
        return "[error google api] Please check the connectivity of the proxy or consider changing the IP address."
    return "error on translation" if len(re_result) < 1 else re_result[0]

5. 创建字幕文件

安装 srt 库 pip install srt 合并获取到的文字为srt格式字幕

subs=[]
sub = srt.Subtitle(index=index, start=start, end=end, content=text)
subs.append(sub)

final_srt = srt.compose(subs)
with open(sub_name, 'w', encoding="utf-8") as f:
    f.write(final_srt)    

6. 将字幕嵌入视频

使用 ffmpeg 可以很容易实现

ffmpeg -y -i {source_mp4} -i {sub_name} -c copy -c:s mov_text -metadata:s:s:0 language={subtitle_language} {video_subtitle.mp4}

7. 语音合成创建配音音频

安装 pip install edge-tts,将翻译后的每段文本依次交给edge_tts 合成,合成后临时创建mp3音频,将该音频使用pydub转为合适的数据格式,存在segments列表中,同时记录下每段文本位于原音频中的开始时间 start_time 放入 start_times 列表中,关键代码如下

segments=[]
communicate = edge_tts.Communicate(result, "配音角色名", rate="加减语速")
tmpname = f"./tmp/{start_time}-{index}.mp3"
asyncio.run(communicate.save(tmpname))
audio_data = AudioSegment.from_file(tmpname, format="mp3")
segments.append(audio_data)
start_times.append(start_time)

最后将合成后的所有语音片段连接

# 拼接配音片段
def merge_audio_segments(segments, start_times, total_duration, mp4name):
    # 创建一个空白的音频段作为初始片段
    merged_audio = AudioSegment.empty()
    # 检查是否需要在第一个片段之前添加静音
    if start_times[0] != 0:
        silence_duration = start_times[0]
        silence = AudioSegment.silent(duration=silence_duration)
        merged_audio += silence

    # 逐个连接音频片段
    for i in range(len(segments)):
        segment = segments[i]
        start_time = start_times[i]
        # 检查前一个片段的结束时间与当前片段的开始时间之间是否有间隔
        if i > 0:
            previous_end_time = start_times[i - 1] + len(segments[i - 1])
            silence_duration = start_time - previous_end_time
            # 可能存在字幕 语音对应问题
            if silence_duration > 0:
                silence = AudioSegment.silent(duration=silence_duration)
                merged_audio += silence

        # 连接当前片段
        merged_audio += segment
    # 检查总时长是否大于指定的时长,并丢弃多余的部分
    if len(merged_audio) > total_duration:
        merged_audio = merged_audio[:total_duration]
    merged_audio.export(f"./tmp/{mp4name}.wav", format="wav")
    return merged_audio

8. 配音音频加入视频

同样执行 ffmpeg 命令实现

ffmpeg -y -i {video_subtitle.mp4} -i hecheng.wav -c copy -map 0:v:0 -map 1:a:0 {target_mp4}

至此完成。


遇到不少坑

  1. ffmepg 直接放在工程目录下,然后通过修改 os.environ['path'] 的方式直接定位

  2. 声画不对齐的问题。

    同样一句话,使用中文和使用英文大概率所需时间是不同的,这就导致原本5s的声音片段转为其他语言后变成了10s或者相反。 因此加入了语速调整,根据需要可降低或增加语速。

    同时添加了自动调整语速,如果翻译后的语音时长大于翻译前的,则对翻译后的音频强制加速播放,实现时长对齐。

  3. 按照静音片段分割音频时,可能切割出时长非常长的音频,比如100s、200s等,尤其是有背景音乐或背景噪音的视频,没有足够的静音片段,因此针对此情况,强制对大于10s的片段再次分割,同时防止一句话突然断掉,加入 buffer 时长来后延,比如 buffer=1000,当在第1分25秒切割时,实际延后1s到第1分26s切割,以尽量增强连贯性。不过这种方案会出现重复文字,原因很显然,前一个片段的最后1s和下一个片段的前1s是同样内容。

使用 PyQt5 包装个GUI界面

  1. 之前版本使用py自带的标准GUI库 tkinter 简单包装了下,为布局方便,又使用了 tkinter 的包装库 PySimpleGUI,但界面过于丑陋,而且打包不便,不同平台常常出现缺失 Tcl 问题,因此重构采用了PyQt5

  2. 使用 pyinstaller 打包exe pyinstall -w sp.py

GUI界面

p1.png

image.png

CLI 模式

新增了cli模式,先部署源码工程后,执行 python cli.py,可在命令行下执行

支持的参数

--source_mp4: 【必填】待翻译视频路径,以.mp4结尾 --target_dir: 翻译后视频存放位置,默认存放源视频目录下的 _video_out 文件夹

--source_language:视频语言代码,默认en ( zh-cn | zh-tw | en | fr | de | ja | ko | ru | es | th | it | pt | vi | ar ) --target_language:目标语言代码,默认zh-cn ( zh-cn | zh-tw | en | fr | de | ja | ko | ru | es | th | it | pt | vi | ar )

--proxy:填写 http 代理地址,默认 None,如果所在地区无法访问google,需要填写,例如: http://127.0.0.1:10809

--voice_role:根据所选目标语言代码,填写对应的角色名,注意角色名的前2个字母需要和目标语言代码的前2个字母一致,如果不知道该怎么填写,执行python cli.py show_vioce 将显示每种语言对应可用的角色名称

--voice_rate:负数降低配音语速,正数加快配音语速,默认10,即加快 --remove_background:是否移除背景音,如果传入该参数即代表去除背景音

--voice_silence: 输入100-2000之间的数字,表示静音段的最小毫秒,默认为300。

--voice_autorate: 如果翻译后的音频时长超过原时长,是否强制加速播放翻译后的音频,以便对齐时长?

--whisper_model: 默认为base,可选 base / small / medium / large,效果越来好,速度越来越慢。

cli示例

image.png

python cli.py --source_mp4 "D:/video/ex.mp4" --source_language en --target_language zh-cn --proxy "http://127.0.0.1:10809" --voice_role zh-CN-XiaoxiaoNeural

上述意思是,将源语言位英文的 D:/video/ex.mp4 视频,翻译为中文视频,设置代理 http://127.0.0.1:10809 使用配音橘色为 zh-CN-XiaoxiaoNeural

python cli.py --source_mp4 "D:/video/ex.mp4" --source_language zh-cn --target_language en --proxy "http://127.0.0.1"1080 9" --voice_role en-US-AriaNeural --voice_autorate --whisper_model small

上述意思是,将源语言为中文的 D:/video/ex.mp4 视频,翻译为英文视频,设置代理 http://127.0.0.1:10809 使用配音角色为 en-US-AriaNeural,如果翻译后的语音时长大于原语音,则自动加速,文字识别模型采用 small 模型

效果

点击查看demo对比效果

image.png

Youtube 操作演示

github源码

github.com/jianchang51…

使用或参考的开源项目

github.com/jiaaro/pydu…

github.com/rany2/edge-…

github.com/facebookres…

github.com/coqui-ai/TT…

github.com/deezer/sple…

github.com/openai/whis…