十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

翻译回环+语音合成:用Python实现“歌词翻译20次再唱出来”

翻译回环+语音合成:用Python实现“歌词翻译20次再唱出来” 最近刷社区时又看到了一类很上头的创意玩法把一段歌词丢进机器翻译在不同语言之间来回翻译二十遍等文本面目全非以后再配上原曲旋律把它“唱”出来。这类视频的标题往往写成类似“谷歌翻译 20 次《人是猫》然后自己唱出来”的形式喜剧效果基本来自认真演唱和荒诞歌词之间的反差。这类实验看起来像整活但拆开之后其实是一个很完整的小型技术项目会涉及机器翻译接口调用、多语言回环逻辑、歌词文本对齐、TTS 语音合成、音频时间伸缩和伴奏混合。非常适合作为周末练手项目也能帮你把“调用在线 API”“处理音频文件”这些技能串起来。本文会从零开始带你做一套可以本地运行的“翻译回环 语音合成”实操方案。我们不依赖某个固定浏览器页面也不需要讨论怎样访问境外网站而是使用国内开发者可以正常申请的云翻译 API 和一个开源 TTS 库来复刻同款效果。如果你能翻到原版的视频会发现核心玩法完全一致先让机器把歌词翻译到面目全非最后再想办法唱出来。为了尊重歌词版权本文不会整段复制某首歌曲的原文。示例中会使用四句自拟的、长度接近中文歌曲主歌的文本作为演示输入你只需要把自己的目标歌词保存到input/lyrics.txt之后所有步骤都可以直接复用。1. 这个创意玩法到底在做什么1.1 什么是“翻译回环”翻译回环英文里常叫 Translation Loop 或 Round-trip Translation。最简形式是把一段文本从 A 语言翻译到 B 语言再把 B 语言结果翻译回 A 语言观察前后两次文本的差异。这个玩法最早的流行场景就是“谷歌翻译 XX 次”系列。人们把一段歌词、名言或新闻丢进翻译器在中文、英文、日文、法文等语言之间反复横跳。机器翻译并不是逐字查找词典而是基于上下文重新生成目标语言。每一次翻译都可能造成细微的歧义丢失、指代漂移和语气变化。次数多了以后原文就会变得非常离谱有时候甚至会产生一种“AI 大脑中经过多次折射后的梦境感”。简而言之翻译回环不是单纯地“翻过去再翻回来”而是让文本在不同语言模型中反复被重新解释最终产生意外的语义失真。这个特性和语言模型的学习方式有关也和源语言与目标语言之间的语序差异、文化差异有关。网上常见的版本一般是“翻译 20 次”。次数太少语义可能变化不明显次数太多文本又容易坍缩成一句简单且无趣的短句。20 次是一个比较平衡的经验值既能让内容产生足够多的失真又保留一些残存的情绪与意象。1.2 为什么要拿歌词来做实验歌词和新闻稿不同它天然带有三个属性有节奏有旋律有情绪意象即使歌词经过 20 次翻译后变得荒诞只要你还记得原曲旋律依然能感受到某种“节奏上的熟悉感”。例如“晚风轻轻吹过城市的屋顶”经过多次翻译后可能变成“晚上的风慢慢越过城市的顶部”甚至变成一句更奇怪的表达。但当演唱者用严肃认真的情绪去唱这句荒诞歌词时听感上的反差就会非常强烈。所以这个实验的重点不是“机器翻得好不好”而是“糟糕翻译后的文本与原始旋律重新组合能产生怎样的审美冲突”。这也是它能成为热门创意玩法的主要原因。1.3 “自己唱出来”具体指什么“自己唱出来”在技术上有两种理解第一种是自己真人跟伴奏演唱。操作流程是拿到第 20 轮翻译后的中文文本听着原曲伴奏一句一句对准节拍唱出来然后录音。这种方式依赖人的节奏感和音准不需要额外写太多代码。第二种是让机器帮你生成一个参考音频。我们可以用 TTS 把最终歌词逐句读出来再通过时间伸缩让每句话的长度与对应乐句接近。这样即使是“朗读腔”也能在节拍上与伴奏发生关系方便你反复练习后自己献声。本文会把两条路径都覆盖到。代码部分重点实现技术链路最终成品是一份“对准节奏的参考人声轨”以及一个把它叠进伴奏的演示音频。真正的“真人演唱”部分我保留给你来完成。1.4 项目整体输出物按本文步骤完成后你会得到如下文件输出文件作用output/history.json记录每一句、每一轮翻译的完整历史output/final_lines.txt第 20 轮翻译后得到的歌词行output/voice/line*.mp3逐句 TTS 生成的朗读音频output/voice/aligned_line*.mp3时间伸缩对齐后的句子音频output/voice/final_lines.mp3拼接后的完整人声参考轨output/demo.mp3伴奏与人声叠加后的演示音频这套文件链路非常适合复盘你随时可以回到中间某一步替换歌词、替换语言路线重新生成不同版本。2. 技术选型与整体架构2.1 核心流程整个实验可以拆成下面几步准备原始歌词按唱歌时的换气位置拆成若干行。对每一行歌词独立执行 20 轮翻译。每轮翻译的语言由“语言路由表”决定第 20 轮强制回到中文。保存每一步的中间结果形成完整的翻译历史。把第 20 轮得到的中文歌词写入final_lines.txt。使用 TTS 对最终歌词逐句合成语音。根据原曲乐句时长使用时间伸缩对齐每一句。把所有对齐后的句子拼接成一条完整人声轨。将人声轨叠加到伴奏上导出演示音频。从架构上看这是典型的“数据管道 外部服务调用 音频后处理”项目。翻译部分是一个文本处理管道语音部分是一个音频处理管道。两者通过文本文件串联拆解清晰也便于出问题时定位。文本流方向可以简单记为原始歌词行 - 多语言翻译循环 20 轮 - 最终歌词行 - TTS 生成人声 - 时间对齐 - 伴奏混合2.2 翻译接口应该怎么选这个玩法的核心是“机器翻译循环”而不是某一个具体的翻译产品。你在 B 站或抖音上看到的标题虽然叫“谷歌翻译 20 次”但它只是为了说明玩法来源。真正落地时你完全可以使用任何支持多语言互译的云翻译 API。本文示例选择百度翻译开放平台理由有三个国内环境可以直接访问。注册后可以申请免费额度适合小体量实验。提供中、英、日、韩、法、西等多种语言足够完成 20 轮循环。需要说明的是各厂商的语言代码并不统一。例如“日语”在百度翻译中通常写作jp而不是 ISO 标准的ja。项目代码里会做一个内部语言名到平台参数名的映射这样后面调整路线时更直观也不容易把代码写死。翻译接口的密钥属于敏感信息不要提交到公开的 Git 仓库。建议之后把appid和密钥放到环境变量或者本地配置文件中。2.3 语音合成与音频处理语音合成部分选择了edge-tts。它依赖微软 Edge 的在线语音合成能力可以用很短的代码把文本转成 MP3音色也比较自然支持中文、英文、日文等。为什么要用 TTS 而不是直接找真人唱因为大多数读者不会因为看了这篇文章就马上开始录音。先生成一个“机器参考人声”可以让你在不对着麦克风的情况下先感受到翻译后的歌词配上原有节奏是什么效果。如果效果不好你可以调整歌词、调整语言路线重新跑一遍成本很低。音频对齐部分使用librosa和soundfile。librosa提供了时间伸缩功能可以在不改变音高的前提下把一段音频拉长或缩短。但要注意时间伸缩和“修音高”是两回事。时间伸缩只改变发音快慢不会把人声变成歌唱旋律。真正想要“机器演唱”需要更复杂的声码器或歌声合成引擎那属于进阶玩法。3. 环境准备与项目结构3.1 运行环境说明建议使用 Python 3.9 或更高版本。操作系统不限Windows、macOS、Linux 均可。为了不污染系统 Python建议先创建一个虚拟环境。python -m venv venvWindows 下激活虚拟环境venv\Scripts\activatemacOS / Linux 下激活虚拟环境source venv/bin/activate3.2 安装依赖创建一个requirements.txt文件内容如下requests2.31.0 edge-tts6.1.10 librosa0.10.1 soundfile0.12.1 pydub0.25.1版本号只是参考。不同系统下部分音频库的依赖可能略有差异如果你安装时遇到版本冲突可以去掉版本号让 pip 自动选择当前环境较合适的版本。执行安装pip install -r requirements.txt另外pydub在混音时需要依赖 FFmpeg。如果你电脑还没安装 FFmpeg需要提前装好。安装完成后在终端执行ffmpeg -version能正常打印版本信息即可。3.3 项目目录结构建议整个项目按照下面的结构组织translate-sing-demo/ ├── input/ │ ├── backing.mp3 │ └── lyrics.txt ├── output/ │ ├── history.json │ ├── final_lines.txt │ ├── voice/ │ ├── aligned_voice/ │ └── demo/ ├── requirements.txt ├── translator.py ├── run_loop.py └── make_voice.pyinput/backing.mp3是原曲伴奏。input/lyrics.txt是原始歌词每行一句。可以先用很短的文本测试等流程跑通后再换完整的歌曲。3.4 示例歌词的准备由于版权原因本文不直接贴原曲完整歌词。我准备了一段自拟占位文本格式和中文歌曲主歌比较接近晚风轻轻吹过城市的屋顶 人们穿过亮着灯的路口 一只猫在墙头放慢脚步 谁也不知道它来自哪里把这四行保存到input/lyrics.txt。如果你的《人是猫》原文是一首日文歌也可以改成日文原文但语言路由需要相应调整。下面先以中文源文本为例。实际做实验时建议不要一次性放入太多行。四到六行比较合适因为每跑一行都需要执行 20 次翻译请求。行数越多等待时间越长也越容易触发翻译接口的调用频率限制。4. 实现多语言翻译循环4.1 翻译接口封装在项目根目录新建translator.py把翻译接口封装成一个独立模块。这样后续主流程只需要调用translate()函数不需要关心签名怎么计算、请求怎么发送。# 文件路径translator.py import hashlib import random import requests # 配置区 # 请在百度翻译开放平台注册并开通“通用文本翻译”服务 APPID 请替换为你的APPID SECRET_KEY 请替换为你的SECRET_KEY API_URL https://fanyi-api.baidu.com/api/trans/vip/translate # 内部语言名 - 百度翻译平台语言代码 LANG_MAP { zh: zh, en: en, ja: jp, ko: kor, fr: fra, es: spa, } # def to_platform_lang(lang: str) - str: 把内部语言名转换成具体平台的入参语言代码。 return LANG_MAP.get(lang, lang) def translate(text: str, target_lang: str, source_lang: str zh) - str: 调用百度翻译开放平台的通用文本翻译接口。 :param text: 待翻译文本 :param target_lang: 目标语言内部名例如 en / ja / ko :param source_lang: 源语言内部名默认 zh :return: 翻译后的文本 if not text.strip(): return text.strip() # 百度翻译要求根据 appid q salt 密钥 拼接后计算 MD5 salt str(random.randint(32768, 65536)) sign_str APPID text salt SECRET_KEY sign hashlib.md5(sign_str.encode(utf-8)).hexdigest() params { q: text, from: to_platform_lang(source_lang), to: to_platform_lang(target_lang), appid: APPID, salt: salt, sign: sign, } resp requests.get(API_URL, paramsparams, timeout10) data resp.json() # 接口出错时返回 error_code 和 error_msg if error_code in data: raise RuntimeError( f翻译接口错误{data.get(error_msg)}{data.get(error_code)} ) return data[trans_result][0][dst]这里的关键部分是签名算法。每个提供开放能力的翻译平台都有自己的签名规则百度翻译使用的是 MD5 拼接摘要。拼接顺序是appid 待翻译文本 salt 密钥然后对拼出来的字符串做 MD5得到sign。请求成功后返回的 JSON 结构大致如下{ from: zh, to: en, trans_result: [ { src: 晚风轻轻吹过城市的屋顶, dst: The evening breeze gently blows over the roof of the city } ] }所以代码里取data[trans_result][0][dst]就是翻译结果。4.2 翻译语言路由设计第 1 轮从中文开始第 20 轮要回到中文中间 18 轮要在不同语言之间不断切换。语言路由如果设置得太规律例如“中日中日中日”反复交替会让文本频繁回到中文语义失真可能不够明显。更好的方式是让语言在互不接近的语系之间跳跃。默认路由可以这样设计ko - en - ja - fr - es也就是中文翻译成韩文韩文翻译成英文英文翻译成日文日文翻译成法文法文翻译成西班牙文西班牙文再翻译成韩文这样循环到第 19 轮第 20 轮强制回到中文。4.3 主循环脚本接下来写主循环run_loop.py。它负责读取歌词、逐句翻译、保存中间历史。# 文件路径run_loop.py import json import time from translator import translate ROUNDS 20 SOURCE_LANG zh ROUTE [ko, en, ja, fr, es] HISTORY_PATH output/history.json FINAL_LINES_PATH output/final_lines.txt def run_one_sentence(sentence: str): 对单个句子执行 20 轮翻译返回最终中文结果和完整历史。 current sentence.strip() current_lang SOURCE_LANG history [] for step in range(1, ROUNDS 1): if step ROUNDS: target_lang SOURCE_LANG else: target_lang ROUTE[(step - 1) % len(ROUTE)] print(f[step {step:02d}] {current_lang} - {target_lang}) print(f 输入{current}) current translate( current, target_langtarget_lang, source_langcurrent_lang ) current_lang target_lang history.append({ round: step, target_lang: target_lang, text: current, }) return current, history def main(): with open(input/lyrics.txt, r, encodingutf-8) as f: sentences [line.strip() for line in f if line.strip()] all_history [] final_lines [] for idx, sentence in enumerate(sentences, start1): print(f\n 正在处理第 {idx} 句 ) final_result, history run_one_sentence(sentence) final_lines.append(final_result) all_history.append({ original: sentence, final: final_result, history: history, }) # 尽量降低请求频率避免触发接口限流 time.sleep(1.2) os.makedirs(output, exist_okTrue) with open(HISTORY_PATH, w, encodingutf-8) as f: json.dump(all_history, f, ensure_asciiFalse, indent2) with open(FINAL_LINES_PATH, w, encodingutf-8) as f: f.write(\n.join(final_lines)) print(\n 翻译循环完成 ) for i, line in enumerate(final_lines, start1): print(f{i}. {line}) if __name__ __main__: import os main()注意脚本开头用到os但导入语句放在模块顶部更规范。实际代码中需要把import os提升到顶部。上面main()内部写import os也可以运行不过更好的写法是在文件开头统一导入。运行命令python run_loop.py如果你申请的是标准版翻译服务免费额度下的调用频率比较低。每翻译一句之间加入time.sleep(1.2)可以显著降低限流概率。实际运行中如果遇到超时可以把timeout调大或者增加失败重试逻辑。由于每个句子都要跑 20 次翻译四句歌词大约要请求 80 次。即使每次间隔 1.2 秒加上网络耗时整体也需要数分钟。这属于正常现象不必担心。执行结束后打开output/final_lines.txt你会看到类似这样的最终歌词行晚间的风轻轻穿过城市的顶部 人们走过一条点燃的街道 一只猫在墙壁上慢慢移动着脚步 没有人知道它来自何处这里不放真实运行结果是因为每次请求、每次网络状况都会带来细微差异。更重要的是不同线上的翻译模型可能已经更新生成结果会随时间变化。这也是这个玩法的魅力所在它每个版本都不可复现错过就没了。5. 用 TTS 生成逐句人声5.1 TTS 脚本得到最终中文歌词后可以调用edge-tts为每一句生成语音。# 文件路径make_voice.py import asyncio import os import edge_tts VOICE zh-CN-YunxiNeural INPUT_PATH output/final_lines.txt VOICE_DIR output/voice async def synth_line(text: str, output_path: str): communicate edge_tts.Communicate(texttext, voiceVOICE) await communicate.save(output_path) def main(): os.makedirs(VOICE_DIR, exist_okTrue) with open(INPUT_PATH, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] for idx, line in enumerate(lines, start1): out_path os.path.join(VOICE_DIR, fline{idx}.mp3) print(f正在合成第 {idx} 句{line}) asyncio.run(synth_line(line, out_path)) print(逐句语音合成完成。) if __name__ __main__: main()运行python make_voice.pyedge-tts连接的是在线语音合成服务对网络环境有要求。如果某一句合成失败可以先重新运行一次看是否是瞬时网络抖动。如果反复失败可以尝试更换其他音色。查看支持音色列表edge-tts --list-voices输出中会列出大量音色中文音色常见的有zh-CN-XiaoxiaoNeural、zh-CN-XiaoyiNeural、zh-CN-YunxiNeural、zh-CN-YunjianNeural
返回列表