开源,基于edge-tts的剪贴板文本自动朗读

用 GPT4o写的。
调用的edge大声朗读的功能,需要联网才能用。
所以延时比较大,最少都要一秒。


下载地址:

程序主要功能:监控剪贴板内容的变化,并将变化的文本内容转为语音播放。

功能列表
发音人选择:用户可以选择不同的发音人来进行文本转语音。
语速设置:用户可以设置语速/
快捷键监控:用户可以选择是否启用快捷键监控功能,并指定快捷键组合。
只转换英文内容:用户可以选择是否只转换英文内容,忽略其他语言的文本。
配置管理:程序启动时检查是否有配置文件 jtb.ini,存在则读取配置;不存在则让用户设置并保存配置。

安装依赖库
您可以使用以下命令通过 pip 安装以上所需的库:
pip install pyperclip edge-tts pygame keyboard configparser

源码:

# -*- coding: utf-8 -*-

import pyperclip
import time
import edge_tts
import os
import asyncio
import pygame
import keyboard  # 用于检测全局快捷键
import configparser

# 初始化 pygame
pygame.mixer.init()

# 发音人列表
voices = {
    1: 'zh-CN-XiaoxiaoNeural',
    2: 'zh-CN-XiaoyiNeural',
    3: 'zh-CN-YunjianNeural',
    4: 'zh-CN-YunxiNeural',
    5: 'zh-CN-YunxiaNeural',
    6: 'zh-CN-YunyangNeural'
}

config = configparser.ConfigParser()

if os.path.exists("jtb.ini"):
    config.read("jtb.ini")
    saved_settings = config["SETTINGS"]
    rate_input = saved_settings.get("rate", "1.0").strip()
    voice_choice = int(saved_settings.get("voice_choice", "1"))
    use_hotkey = saved_settings.get("use_hotkey", "n") == 'y'
    hotkey = saved_settings.get("hotkey", "")
    convert_english_only = saved_settings.get("convert_english_only", "n") == 'y'
else:
    rate_input = input("请输入语速(例如 '1.0' 表示正常速度,'1.5' 表示1.5倍速度,默认是 '1.0'): ").strip()
    try:
        rate = float(rate_input)
        rate_percent = f"+{int((rate - 1) * 100)}%"
    except ValueError:
        rate_percent = "+0%"  # 默认语速

    # 打印发音人选项
    print("请选择发音人:")
    for key, value in voices.items():
        print(f"{key}: {value}")

    voice_choice = int(input("请输入发音人编号: "))
    selected_voice = voices.get(voice_choice, 'zh-CN-XiaoxiaoNeural')  # 默认使用 XiaoXiao

    use_hotkey = input("是否启用快捷键监控功能?(y/n): ").strip().lower() == 'y'

    # 如果启用快捷键监控功能,用户定义一个快捷键组合
    if use_hotkey:
        print("请按下要监控的快捷键组合...")
        hotkey = keyboard.read_hotkey()
        print(f"您选择的快捷键组合是: {hotkey}")
    else:
        hotkey = ""

    convert_english_only = input("是否只转换英文内容?(y/n): ").strip().lower() == 'y'

    config["SETTINGS"] = {
        "rate": rate_input,
        "voice_choice": voice_choice,
        "use_hotkey": 'y' if use_hotkey else 'n',
        "hotkey": hotkey,
        "convert_english_only": 'y' if convert_english_only else 'n'
    }

    with open("jtb.ini", "w") as configfile:
        config.write(configfile)

# 用户配置已读取或设置,现在初始化其他变量
try:
    # 使用读取到或输入的 rate_input 进行语速设置
    rate = float(rate_input)
    rate_percent = f"+{int((rate - 1) * 100)}%"
except ValueError:
    rate_percent = "+0%"  # 默认语速

selected_voice = voices.get(voice_choice, 'zh-CN-XiaoxiaoNeural')  # 默认使用 XiaoXiao

# 初始化变量
previous_clipboard_content = ""

async def text_to_speech(text, output_file="output.mp3"):
    # 将发音人设定为用户选择的发音人,并设置语速
    communicator = edge_tts.Communicate(text, voice=selected_voice, rate=rate_percent)
    await communicator.save(output_file)

def play_audio_file(file_path, start_time):
    pygame.mixer.music.load(file_path)

    end_time = time.time()  # 记录结束时间
    elapsed_time = end_time - start_time
    print(f"生成时间: {elapsed_time:.2f} 秒")

    pygame.mixer.music.play()

    while pygame.mixer.music.get_busy():
        pygame.time.Clock().tick(10)

    # 等待播放完毕后卸载音频文件
    pygame.mixer.music.unload()

def safe_remove(file_path, retries=3, delay=1):
    """
    安全删除文件的方法
    :param file_path: 要删除的文件路径
    :param retries: 最大重试次数
    :param delay: 每次重试之间的延迟(秒)
    """
    for i in range(retries):
        try:
            os.remove(file_path)
            print(f"播放完成")
            break
        except PermissionError as e:
            print(f"删除文件失败,重试 {i+1}/{retries}...")
            time.sleep(delay)
        except Exception as e:
            print(f"发生意外错误: {e}")
            break

def is_english(text):
    try:
        # 检查文本是否只包含英文字符和常用标点符号
        return all(ord(c) < 128 for c in text)
    except:
        return False

def is_valid_text(text):
    try:
        return isinstance(text, str) and len(text.strip()) > 0
    except:
        return False

def process_clipboard_text():
    global previous_clipboard_content
    try:
        current_clipboard_content = pyperclip.paste()
        # 仅在剪贴板内容是有效文本且与上次不同的时候处理
        if is_valid_text(current_clipboard_content) and current_clipboard_content != previous_clipboard_content:
            previous_clipboard_content = current_clipboard_content

            # 根据用户设置只处理英文内容
            if not convert_english_only or is_english(current_clipboard_content):
                start_time = time.time()  # 记录开始时间
                print(f"新文本: {current_clipboard_content}")
                # 使用 edge-tts 合成语音并播放
                output_file = "output.mp3"
                asyncio.run(text_to_speech(current_clipboard_content, output_file))

                # 检查音频文件是否成功生成且有效
                if os.path.exists(output_file) and os.path.getsize(output_file) > 0:
                    play_audio_file(output_file, start_time)

                # 播放完毕后删除音频文件
                safe_remove(output_file)
    except pyperclip.PyperclipException as e:
        print(f"读取剪贴板内容时出现错误: {e}")

def monitor_clipboard():
    global previous_clipboard_content

    if use_hotkey:
        print(f"按下组合键 '{hotkey}' 进行文本读取...")
        keyboard.add_hotkey(hotkey, process_clipboard_text)
        keyboard.wait()  # 等待快捷键被触发
    else:
        while True:
            process_clipboard_text()
            # 每隔一秒检测一次
            time.sleep(1)

if __name__ == "__main__":
    try:
        monitor_clipboard()
    except KeyboardInterrupt:
        pygame.mixer.quit()  # 程序退出时清理 pygame
        print("程序已退出")
1 Like

不错啊,代码那么短。挺有用的|然而我不知道有啥用。

你能讲讲用来干嘛么?听收款码到账?

你知道的,没有配音但是有剧情的游戏可以用这个来读,读屏软件这种东西很多,但是基本都是翻译。剪贴板朗读其实也有现成的软件比如:Balabolka。但是调用的是系统本身的tts引擎,机械音。这就是做这东西的起因。
我自用的版本是好几个游戏,好几套配置,然后配置里面游戏角色设置不同的发音人,玩什么游戏就加在哪套配置,这样就能实现不同声音的对白。
日常使用中,它就有另外的应用场景,大部分情况下都是不需要一直监控剪切板的,所以我设置了监控按键的功能,这样学渣就能:学中文or学英文 :grin: