我们都知道外网上有很多优秀的视频教程平台,比如Coursera和deeplearning.ai。尤其是后者,由吴恩达老师与OpenAI、Langchain、LlamaIndex、AutoGen等公司和作者合作,推出了一系列广受好评的LLM教程,如PromptEngineering、Langchain教程、LlamaIndex教程和AutoGen教程。deeplearning.ai的课程紧跟时下热点,是大语言模型爱好者和从业者不可或缺的资源。然而,deepleaning.ai的课程通常没有中文字幕,这无疑提高了学习的门槛。即使有些同学坚持学习,也可能因为语言障碍只能学到皮毛。我肝了4天,我成功地将这些课程转换成流畅自然的普通话。话不多说,让我们直接看看效果视频。
注:本文中有很多关于字幕处理和音视频处理的库都是与MarsCode交互得知,我本人对字幕和音视频处理并不是很了解~,如果其中对于音视频处理有疑问的地方,还望指出,感谢~
但是我的AppleM1使用torchaudio.save会报错,如下所示。
把上述错误拷贝给MarCode进行一番交流,终于解决了运行错误。但在与MarsCode交流中,我觉得MarsCode对于指令的跟随性不太好,我需要的是mp3,结果给出的代码是存储为wav文件。
此外,我觉得还可以改进的地方是上下文,看起来它已经忘记我使用torchaudio报错的,这仅仅只有3轮对话。
后来我要求使用pydup实现将numpyarray存储mp3,MarCode给出一版初稿,然后我运行报错,再次贴报错给它,最终给出如下代码。此段代码,可以运行,但是生成的mp3没有声音。于是我查询资料得知,它在将数组归一化之后量化到int16时,没有将数据缩放导致没有声音。加入numpy_array=(numpy_array*32767).astype(np.int16)如下代码即可,但生成的MP3听起来很快,按照官方教程中将frame_rate修改为24000,语音变得正常。
最终采用的是pydup的方法,得到了可运行的numpy_array_to_mp3函数,可以将numpy数组转换成mp3,需要安装pydub。
defnumpy_array_to_mp3(numpy_array,output_file):#将numpy数组规范化到[-1,1]范围numpy_array=np.clip(numpy_array,-1,1)numpy_array=(numpy_array*32767).astype(np.int16)#将numpy数组转换为int16类型的WAV格式字节流byte_stream=numpy_array.tobytes()#创建一个新的AudioSegment对象audio_segment=AudioSegment(data=byte_stream,sample_width=2,frame_rate=24000,channels=1)#将AudioSegment对象保存为MP3文件audio_segment.export(output_file,format="mp3")新的运行代码如下:
mp3_filename="output_audio1.mp3"numpy_array_to_mp3(wavs[0],mp3_filename)生成的语音极为流畅,你可以听听看。
根据我个人测试,seed_1332发音语气平稳、中英文穿插也能很好的合成,在长句合成上也未见到音色切换,推荐大家使用,上面那段欢迎公众号的音频就是来自seed_1332。但是现在新发布的ChatTTS已经不支持直接采用pt权重文件固定音色了,如何办呢?
上述代码运行后依然会报错,RuntimeError:Theexpandedsizeofthetensor(768)mustmatchtheexistingsize(1536)atnon-singletondimension2.Targetsizes:[1,35,768].Tensorsizes:[1,1,1536]。把这段报错给到MarsCode它无法解决,从这个错误来看,其实我们只需要将tensor转换为FP16即可,spk_emb_np=spk_emb.cpu().numpy().astype(np.float16)。经过修改后,得到如下函数tensor_to_str。
deftensor_to_str(spk_emb):#将Tensor转换为NumPy数组spk_emb_np=spk_emb.cpu().numpy().astype(np.float16)#将NumPy数组编码为字符串spk_emb_str=b14.encode_to_string(lzma.compress(spk_emb_np,format=lzma.FORMAT_RAW,filters=[{"id":lzma.FILTER_LZMA2,"preset":9|lzma.PRESET_EXTREME}]))returnspk_emb_str所以在下载好音色之后,我们就可以通过如下方式加载pt文件来固定音色。
WEBVTT格式:
WEBVTT100:00:02.080-->00:00:05.660RetrievalAugmentedGeneration,orRAG,hasbecomeakeySRT格式:
100:00:02,080-->00:00:05,660RetrievalAugmentedGeneration,orRAG,hasbecomeakey根据MarsCode沟通得知使用FFmpeg可将WEBVTT字幕文件转换为SRT文件:
ffmpeg-isubtitle.vttsubtitle.srt但是我发现转换后第一行字幕被移除了,这是因为VTT文件的第一行包含"WEBVTT",被识别为非字幕块。移除这一行后转换就可以正常进行。不过,MarsCode告诉我可以使用pysrt直接转换,看起来更加简单,也不需要使用FFmpeg这种重量级工具。
pysrt.open(vtt_path,encoding="utf-8").save(srt_path,encoding="utf-8")虽然pysrt可以直接转换,但是第一行依然会被移除。因此,我们需要编写代码移除第一行WEBVTT字幕,使用MarsCode自动编码,写出函数名然后编写comment,他就会自动提示,此时我们只需要按Tab键即可将这段自动生成代码输入。可以看出对于这种比较简单的功能,生成的代码基本不用考虑再次修改,基本上随着comment输入完成,代码自动生成就会提示,速度挺快的。
字幕文件已经转换完成,接下来需要考虑如何翻译字幕。通过pysrt可以逐行读取并翻译,但测试发现这种方法的翻译质量较差,因为一个字幕块通常不是完整的句子。例如:
content="""你是一个专业的翻译官,尤其擅长科技类中英文翻译,必须保持原本的字幕格式,要求保持精简,不要肆意添加东西,不要漏翻。比如:原文:100:00:03,270-->00:00:06,292howtosetupbothabasicandadvancedRAGpipeline翻译:100:00:03,270-->00:00:06,292如何设置基本和高级的RAG管道"""注意Prompt加入了必须这个词,要求保持字幕样式,还有要求保持精简,不要肆意添加东西,也不要漏翻。这是因为我发现有时候翻译出的中文太啰嗦了,导致后面使用chattts合成的音频要比原视频多出几分钟,这是无法接受的。
这样我们就能得到类似如下的中文字幕。
但我的内容是字幕,因此对于翻译本身还有要求。因此我参考吴恩达老师的反思策略写了translator_agent。
deftranslate_agent(source_text):translation_1=initial_translation(source_text)reflection=reflect_on_translation(source_text,translation_1)translation_2=improve_translation(source_text,translation_1,reflection)returntranslation_2最早的Prompt设定,经常出现几个连续字幕是重复的内容,就是通过反思他也只是删除重复内容,但不能让字幕在整句翻译的时候考虑字幕块的分布问题,所以Prompt在初始翻译的设定要考虑到这一点。
最后是根据反思的建议进行改进的的Prompt设定,它的系统Prompt设定不变,但是要求按照专家的建议进行改进,我还告诉他干得好,奖励5000美元(不知道人民币有没有效果)。在改进过程中,我经常看到它会把移除的字幕换成提示语(removeduplicate),所以我们在Prompt种要求在无需改进时候就返回原始翻译或者移除的情况下就展示为空等。
我们已经知道pysrt可以处理字幕文件,接下来可以使用pysrt读取中文字幕,并遍历调用ChatTTS的infer接口生成音频,如在第一节所示。音频的连接我们采用pydup工具,它支持插入静音片段和使用+号进行连接,也可以直接读取mp3、wav等文件。尽管我没有深入研究其全部功能,但主要是在MarsCode上生成的代码基础上进行了一些修改。
full_audio=AudioSegment.silent(duration=0)subtitles=pysrt.open(subtitle_path,encoding="utf-8")fori,subtitleinenumerate(subtitles):text=subtitle.textstart_time=time_to_ms(subtitle.start)end_time=time_to_ms(subtitle.end)duration_ms=end_time-start_timewavs=chat.infer([text.replace('\n','')],use_decoder=True,params_infer_code=params_infer_code)mp3_filename=f"temp_{i}.mp3"wav_to_mp3(mp3_filename,wavs[0])speech=AudioSegment.from_mp3(mp3_filename)full_audio+=speech虽然看起来比较简单,但实际生成的音频和字幕大部分都无法匹配。需要处理诸如空字幕、开始前后加入静音片段。最主要的问题是合成的音频长度和字幕不匹配,这意味视频和音频也不会匹配,这是一个严重影响体验的问题。与MarsCode沟通得知,FFmpeg中大约有几种方法,其中asspeed我们不考虑,第2个方法的输入源必须是视频,因此也不考虑。
经过测试,我感觉第一种使用atempo过滤器的方法效果最好。因此,我要求MarsCode使用pydup实现第一种方法如下,最好将倍速调整到0.5-2.0之间,超过2.0之后基本听不太清楚了。
既然我们已经生成了中文字幕和普通话音频,最后一步就是通过FFmpeg将所有文件合并,生成最终的视频。在反复询问MarsCode后,我了解到由于字幕的合并需要重新编码视频,因此最终FFmpeg命令如下:
最终,我们成功地将一些只有英文字幕而没有中文配音的教程转译为自然地道的中文配音,期望能更好地普及优秀的英文视频教程。虽然现阶段对于英文转中文发音后,导致字幕和合成语音有些许同步的问题,此外对于音频的速度控制,仍然不够理想,但我想这已经是一个很大的进步,这里也欢迎亲爱的读者提出更好的建议。此外,我们还可以使用ChatTTS克隆音色,并控制在不同人说话时候的音色,这样就能做到完全拟真的效果,也不会有违和的感觉,或许这需要使用whisper等ASR自动语音识别判别声音,或许有一天多模态大模型可以完全自动克隆音色并翻译合成。如果你有想看的英文教程,欢迎留言,不要私信,私信需要我回关才能回复。