我一直热衷于折腾智能家居自动化,灯光、窗帘、空调都接入了系统。每天下班回家,一句“开灯”就能让客厅亮起温馨的光,非常方便。但时间长了,总觉得缺了点什么——那就是语音助手的音色。无论是小爱、小艺还是Siri,它们的声音虽然清晰,但总感觉过于“官方”和“机械”,少了点家的温度和温柔。

我理想中的智能家居,应该有一个像家人一样亲切、柔和的声音来回应我。既然现有的语音助手无法满足这个个性化的需求,作为一个喜欢动手的程序员,我决定自己来造一个。

我的思路是:利用微软Edge TTS优质的神经网络语音合成技术,通过Python脚本,将我想让“家”说的话,用更温柔的语音合成出来,并集成到我的智能家居自动化流程中。

准备工作:安装必要的包

在开始编写脚本前,你需要先安装核心的Python库。

# 核心TTS库
pip install edge-tts

# 如果你需要像进阶脚本那样实时播放音频,还需要安装pygame
pip install pygame

注意事项:

  • edge-tts是一个命令行工具,也是一个Python库,它需要联网调用微软的云服务来合成语音。

  • 语音合成质量很高,但会有轻微的网络延迟,不适合需要毫秒级响应的场景。对于智能家居的提醒、播报等场景,完全足够。


初级玩法:快速生成语音文件

这个脚本(比如保存为 simple_tts.py)非常适合用来生成固定的、需要重复使用的语音片段。比如,你可以提前合成好“欢迎回家”、“浴室热水已烧好”、“空调已为您调到26度”等语音提示,保存在智能家居系统的媒体库中。

代码示例:

# simple_tts.py
import asyncio
import edge_tts

# 你希望智能家居“说”的话
TEXT = "主人,欢迎回家!灯光已为您调至温馨模式,现在室内温度26度,非常舒适。"
# 输出的音频文件名
OUTPUT_FILE = "welcome_home.mp3"
# 指定语音名称,这里是我非常喜欢的温柔女声“晓晓”
VOICE = "zh-CN-XiaoxiaoNeural"

async def main() -> None:
    # 创建TTS对象
    communicate = edge_tts.Communicate(TEXT, VOICE)
    # 运行并保存音频文件
    await communicate.save(OUTPUT_FILE)
    print(f"语音合成完成!文件已保存为:{OUTPUT_FILE}")

if __name__ == "__main__":
    asyncio.run(main())

运行方式与用途:

  1. 在命令行执行 python simple_tts.py

  2. 脚本会生成一个 welcome_home.mp3文件。

  3. 你可以在智能家居App(如Home Assistant、Node-RED等)中,将这个音频文件设置为自动化动作——例如,当门窗传感器检测到你下班回家时,就在家里的音箱上播放这个音频

进阶玩法:交互式测试与实时生成

当你需要测试不同文本的朗读效果,或者自动化流程需要动态生成不固定的语音内容时(比如播报“今天北京晴,最高温度30度”),一个交互式的脚本就更方便了。

这个脚本(比如保存为 interactive_tts.py)允许你随时输入文本,立即试听效果,帮你快速找到最温柔的语调和措辞。

代码示例:

  1. 实时合成与播放:输入文本后直接播放,无需手动点击音频文件。

  2. 非阻塞播放:你可以在上一句还在播放时,输入下一句进行测试。

  3. 语音切换:方便地在“晓晓”(女声)和“云希”(男声)等不同音色间切换。

  4. 临时文件管理:自动清理合成过程中产生的临时音频文件,保持系统整洁。

    #interactive_tts.py
    import asyncio
    import edge_tts
    import pygame
    import threading
    import tempfile
    import os
    import time
    from io import BytesIO
    
    class EdgeTTSPlayer:
        def __init__(self):
            """初始化TTS播放器"""
            pygame.mixer.init()
            self.is_playing = False
            self.current_text = ""
            self.voice = "zh-CN-XiaoxiaoNeural"
            self.temp_files = []  # 用于跟踪临时文件
            
        async def text_to_speech(self, text):
            """将文本转换为语音并播放
            
            Args:
                text: 要转换的文本
            """
            if not text.strip():
                print("输入文本为空,请重新输入。")
                return
                
            self.current_text = text
            print(f"正在合成语音: {text}")
            
            try:
                # 创建TTS通信对象
                communicate = edge_tts.Communicate(text, self.voice)
                
                # 创建临时文件来保存音频
                with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_file:
                    temp_filename = temp_file.name
                    
                # 保存音频到临时文件
                await communicate.save(temp_filename)
                
                # 播放音频
                self.play_audio(temp_filename)
                
            except Exception as e:
                print(f"语音合成失败: {e}")
                # 清理临时文件
                if 'temp_filename' in locals():
                    try:
                        os.unlink(temp_filename)
                    except:
                        pass
        
        def play_audio(self, audio_file_path):
            """播放音频文件"""
            def _play():
                self.is_playing = True
                try:
                    # 使用文件路径加载音频
                    pygame.mixer.music.load(audio_file_path)
                    pygame.mixer.music.play()
                    
                    # 等待播放完成
                    while pygame.mixer.music.get_busy():
                        pygame.time.Clock().tick(10)  # 每秒检查10次
                        
                except Exception as e:
                    print(f"播放失败: {e}")
                finally:
                    self.is_playing = False
                    # 播放完成后清理临时文件
                    try:
                        os.unlink(audio_file_path)
                    except:
                        pass
            
            # 在新线程中播放以避免阻塞
            play_thread = threading.Thread(target=_play)
            play_thread.daemon = True
            play_thread.start()
        
        def stop_playback(self):
            """停止当前播放"""
            if pygame.mixer.music.get_busy():
                pygame.mixer.music.stop()
                self.is_playing = False
    
    # 以下是您原有的InteractiveTTSApp类,只需确保它使用修复后的EdgeTTSPlayer
    class InteractiveTTSApp:
        def __init__(self):
            """初始化交互式TTS应用"""
            self.tts_player = EdgeTTSPlayer()
            self.running = True
            
        def display_menu(self):
            """显示菜单选项"""
            print("\n" + "="*50)
            print("        Edge TTS 交互式语音合成系统")
            print("="*50)
            print("1. 输入文本并合成语音")
            print("2. 停止当前播放")
            print("3. 更换语音(当前: 晓晓)")
            print("4. 退出系统")
            print("="*50)
        
        def get_user_input(self):
            """获取用户输入的文本"""
            print("\n请输入要转换为语音的文本:")
            text = input("> ").strip()
            return text
        
        def change_voice(self):
            """更换语音选项"""
            print("\n可用的语音选项:")
            print("1. 晓晓 (zh-CN-XiaoxiaoNeural) - 女声")
            print("2. 云希 (zh-CN-YunxiNeural) - 男声")
            
            choice = input("请选择语音 (1-2, 回车使用当前语音): ").strip()
            
            voice_map = {
                "1": "zh-CN-XiaoxiaoNeural",
                "2": "zh-CN-YunxiNeural"
            }
            
            if choice in voice_map:
                self.tts_player.voice = voice_map[choice]
                print(f"已切换语音到: {voice_map[choice]}")
            else:
                print("使用当前语音: 晓晓")
        
        async def process_choice(self, choice):
            """处理用户选择"""
            if choice == "1":
                text = self.get_user_input()
                if text:
                    await self.tts_player.text_to_speech(text)
                    
            elif choice == "2":
                self.tts_player.stop_playback()
                print("已停止当前播放")
                
            elif choice == "3":
                self.change_voice()
                
            elif choice == "4":
                self.running = False
                print("感谢使用,再见!")
                
            else:
                print("无效选择,请重新输入。")
        
        async def run(self):
            """运行主应用程序"""
            print("系统初始化完成,开始运行...")
            
            while self.running:
                self.display_menu()
                choice = input("请选择操作 (1-4): ").strip()
                
                await self.process_choice(choice)
    
    async def main():
        """主函数"""
        app = InteractiveTTSApp()
        await app.run()
    
    if __name__ == "__main__":
        try:
            asyncio.run(main())
        except KeyboardInterrupt:
            print("\n程序被用户中断")
        except Exception as e:
            print(f"程序运行出错: {e}")

使用方式:

  1. 直接运行 python interactive_tts.py

  2. 会出现一个命令行菜单,选择“1”并输入你想测试的文本,比如“亲爱的,浴缸的热水已经放好了,可以来泡澡了”。

  3. 回车后,程序会自动合成并用你选择的语音播放出来。

  4. 你可以反复调整文本,直到合成出最令你满意的、最温柔的语音效果。


版权声明:本文为个人技术实践记录,欢迎交流学习。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐