为了账号安全,请及时绑定邮箱和手机立即绑定

用Python和AI实现语音转文字再转语音的趣味项目指南

如何制作语音转文字再转语音工具

图片 图片来源:Mariia Shalabaieva 提供的照片, 来自 Unsplash

我已经参加了整整十年的GeekCon(没错,就是极客们的大会 🙂)——一个为期周末的黑客马拉松制作马拉松,在这期间所有的项目都必须是无用且纯娱乐性质的。而今年有一个令人兴奋的新花样:所有的项目都必须加入某种形式的人工智能。

我的小组项目是一个语音转文字再转语音的游戏,下面是它的运作方式:用户选择一个想要交谈的角色,然后可以说出他们想对角色说的话。语音输入会被转换成文字并传送给ChatGPTChatGPT会像角色一样回复。最后,这个回复会通过语音合成技术说出来。

现在游戏已经上线,给大家带来了欢笑和乐趣,我写了一份指南,希望能帮助你制作出自己的游戏。在这篇文章里,我也会分享一下在黑客马拉松期间的一些思考和决定。

想看完整代码吗?可以在这里查看

程序流程

一旦服务器开始运行,应用将会提示用户选择想要对话的角色并开始与所选角色交谈。每当他们想大声说话时,只需按住键盘上的一个键即可。说完话并松开键后,他们的录音将由 [Whisper](https://platform.openai.com/docs/guides/speech-to-text/quickstart)(一个由 [OpenAI](https://platform.openai.com/docs/introduction/overview) 提供的语音转文本模型)转录,转录内容将发送到 [ChatGPT](https://platform.openai.com/docs/guides/gpt/chat-completions-api) 获取回复。回复将由一个文本转语音工具朗读出来,用户就能听到。

实现:
声明

需要特别注意的是,pyttsx3库不支持MacOS,该项目是在Windows操作系统上开发的,并且使用了pyttsx3库,该库不兼容M1/M2芯片。因为pyttsx3不支持MacOS,建议用户寻找其他与macOS环境兼容的文本转语音库。

OpenAI集成化

我使用了两个OpenAI模型:Whisper用于语音转文字的转录功能,以及ChatGPT API根据用户的输入为他们选择的角色生成回复。虽然这样做会花费一些钱,但价格非常便宜,我个人的费用至今仍低于1美元。一开始,我存了5美元作为预付款,而且这笔预付款一年内不会过期。
我写这篇文章并没有从OpenAI那里得到任何报酬或好处。

一旦你获得了OpenAI的API密钥,将其设置为环境变量,在调用API时使用。确保不要将密钥推送到代码库或其他公共位置,也不要随意分享。

语音转文字 — 创建转写

我们使用了Whisper来实现语音转文字的功能,这是一个来自OpenAI的模型。

下面是用于转录的函数代码片段:

    async def get_transcript(audio_file_path: str,   
                             text_to_draw_while_waiting: str) -> Optional[str]:  # 获取转录音频的文字内容
        openai.api_key = os.environ.get("OPENAI_API_KEY")  
        audio_file = open(audio_file_path, "rb")  
        transcript = None  

        async def transcribe_audio() -> None:  
            nonlocal transcript  
            try:  
                response = openai.Audio.transcribe(  
                    model="whisper-1", file=audio_file, language="en")  
                transcript = response.get("text")  
            except Exception as e:  
                print(f"错误: {e}")  # 错误处理,打印错误信息

        draw_thread = Thread(target=等待转录时显示文本(text_to_draw_while_waiting))  # 等待转录时显示文本
        draw_thread.start()  

        transcription_task = asyncio.create_task(transcribe_audio())  # 创建一个异步任务来转录音频
        await transcription_task  

        if transcript is None:  
            print("转录内容在指定时间内无法获取。")  # 如果转录内容在指定时间内无法获取

        # 返回转录内容
        return transcript

此功能被标记为异步(async),因为 API 调用可能需要一些时间才能返回响应,我们等待它,以确保程序不会在响应返回之前继续执行。

正如你所看到的,get_transcript 函数也调用了 print_text_while_waiting_for_transcription 函数。为什么呢?因为获取转写需要花费较长时间,我们希望用户知道程序正在积极处理他们的请求,而不是卡顿或没有反应。因此,这段文本会在用户等待下一步的过程中逐渐显示出来。

使用FuzzyWuzzy进行文本模糊匹配

将演讲转录成文字之后,我们要么直接使用这个转录文本,要么尝试将其与现有的文本进行比较。

比较用例包括:从预定义的选项列表中选择一个选项,决定是否继续玩,如果选择继续,则决定是换一个新选项还是继续用现在的。

我们想将用户的语音输入转写与我们列表里的选项进行比较,因此决定使用FuzzyWuzzy库来进行字符串匹配工作。

这就能让从列表中选择最合适的选项,只要匹配得分超过了设定的门槛。

这里是我们代码的一个片段

    def 从转录中检测选择的选项(transcript: str, options: List[str]) -> str:  
        最佳匹配得分 = 0  
        最佳匹配项 = ""  

        # 从选项列表中遍历每个选项
        for 选项 in options:  
            得分 = fuzz.token_set_ratio(transcript.lower(), 选项.lower())  
            # 如果得分大于最佳匹配得分,则更新最佳匹配得分和最佳匹配项
            if 得分 > 最佳匹配得分:  
                最佳匹配得分 = 得分  
                最佳匹配项 = 选项  

        # 如果最佳匹配得分大于等于70,则返回最佳匹配项
        if 最佳匹配得分 >= 70:  
            return 最佳匹配项  
        else:  
            # 如果最佳匹配得分小于70,则返回空字符串
            return ""

想了解更多的话,可以去看看我写的一篇关于FuzzyWuzzy库及其功能特性的文章,可以在这里找到这里

获取ChatGPT的回答

一旦我们有了转写文本,就可发给 ChatGPT 获取回复。

对于每个 ChatGPT 的请求,我们都添加了一个简短有趣的回答的提示。此外,让 ChatGPT 假装成某个角色。

因此我们的函数看起来像这样:

    def get_gpt_response(transcript: str, chosen_figure: str) -> str:  
        system_instructions = get_system_instructions(chosen_figure)  
        try:  
            return make_openai_request(  
                system_instructions=system_instructions,   
                user_question=transcript).choices[0].message["content"]  
        except Exception as e:  
            logging.error(f"无法从ChatGPT获取响应。错误 {str(e)}")  
            raise e

系统指令如下:

    def get_system_instructions(figure: str) -> str:  
        return f"你的回答要有趣且简短,你是:{figure},"
文字转语音功能

在文本转语音这一部分,我们选择了一个名为 pyttsx3 的 Python 库。这个选择不仅实现简单,而且还有一些额外好处。它是免费的,提供了两种声音选择:男声和女声,还允许你设定每分钟的语速。

当玩家启动游戏时,他们会在预设的角色列表中选择一个角色。如果我们无法找到他们选择的角色,我们会随机选择一个“备用角色”。每个角色都关联着一个性别,因此我们的语音合成功能也会收到与所选性别对应的语音ID(用于匹配性别对应的语音)。

这就是我们的文本转语音功能。

    def 文本转语音(text: str, gender: str = 'female' if Gender.FEMALE.value == 'female' else 'male') -> None:  
        engine = pyttsx3.init()  

        engine.setProperty("rate", WORDS_PER_MINUTE_RATE)  # 每分钟单词数的速率
        voices = engine.getProperty("voices")  
        voice_id = voices[1].id if gender == "male" else voices[0].id  
        engine.setProperty("voice", voice_id)  

        engine.say(text)  
        engine.runAndWait()
主要流程

现在我们差不多已经准备好应用程序的所有部分了,是时候深入游戏玩法了!主要流程如下所示。你可能已经注意到一些我们还没有详细讨论的功能(例如 choose_figureplay_round),但你可以通过查看仓库来查看完整的代码。这些高层次的功能最终都会和我们之前提到的内部功能联系起来。

这里有一段游戏的主要流程:

这里有一段游戏的主要流程:

    import asyncio
    from src.handle_transcript import text_to_speech
    from src.main_flow_helpers import choose_figure, start, play_round, is_another_round

    def farewell() -> None:
        farewell_message = "和你在一起真是太好了,希望很快再见到你!"
        print(f"\n{farewell_message}")
        text_to_speech(farewell_message)

    async def get_round_settings(figure: str) -> dict:
        new_round_choice = await is_another_round()
        if new_round_choice == "新角色":
            return {"figure": "", "another_round": True}
        elif new_round_choice == "不":
            return {"figure": "", "another_round": False}
        elif new_round_choice == "是的":
            return {"figure": figure, "another_round": True}

    async def main():
        start()
        another_round = True
        figure = ""

        while True:
            if not figure:
                figure = await choose_figure()

            while another_round:
                await play_round(chosen_figure=figure)
                user_choices = await get_round_settings(figure)
                figure, another_round = user_choices.get("figure"), user_choices.get("another_round")
                if not figure:
                    break

            if another_round is False:
                farewell()
                break

    if __name__ == "__main__":
        asyncio.run(main())
未走过的路

在黑客马拉松期间,我们有几个想法未能实现。这要么是因为在那个周末我们找不到满意的 API,要么是因为时间不够我们无法开发某些功能。以下是这个项目未走的路,

使回应的语气贴近所选角色的“典型”语气

想象如果用户选择与史瑞克、特朗普或奥普拉·温弗瑞聊天。我们希望我们的文字转语音库或API能够用与所选人物匹配的嗓音来回应。然而,在那次黑客马拉松期间,我们还没有找到提供这种功能且价格合理的库或API。如果您有任何建议,欢迎告诉我们哦 =)

让用户跟自己聊一聊

另一个有趣的想法是让用户提供一段自己的语音样本。我们会用这个样本来训练模型,并让ChatGPT生成的所有回答都用用户的语音朗读出来。在这种设定下,用户可以选择回答的语气(比如肯定和支持、讽刺、愤怒等),但声音会非常接近用户本人的声音。然而,我们没有在黑客马拉松期间找到支持这一功能的API。

给我们的应用加入前端

我们的最初计划是在应用程序中加入一个前端组件。然而,在最后一刻,小组参与人数发生了变化,我们决定优先处理后端开发。因此,应用程序现在在命令行界面(CLI)上运行,目前没有前端。

我们想到的更多改进

现在最让我头疼的是延迟问题。

流程中有几个环节延迟较高,我认为这对用户体验有些影响。例如:从输入音频到收到转录的时间,以及用户点击按钮到系统开始录音之间的时间。因此,如果用户一按键就开始说话,至少会有一秒的音频没被记录,因为存在这一延迟。

仓库链接及感谢

想看看整个项目吗?点击这里哦!点击这里查看!

此外,特别感谢Lior Yardeni(Lior Yardeni 链接),我的黑客松搭档,我们一起制作了这个游戏。

来总结一下吧

在这篇文章中,我们学习了如何使用Python创建将语音转文字再转语音的游戏,并将其与AI结合在一起。我们使用了OpenAIWhisper模型进行语音识别,使用FuzzyWuzzy库匹配文本,并通过其开发者API与ChatGPT进行对话,最后使用pyttsx3将文本转换为语音。尽管OpenAI的服务(如WhisperChatGPT的开发者版本)有费用,但总体来说还是很经济的。

希望这个指南对你有启发,并激励你开始着手你的项目。

加油!编程和乐趣!🚀

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消