2024-12-01百度TTS语音

百度TTS语音

个人尝试代码仓库:https://gitee.com/enzoism/chrome_tampermonkey

鸣谢:感谢每一位无私奉献的传道者,在此不一一具名!


1-学习目标

  • 1)了解百度TTS语音
  • 2)使用百度TTS语音

2-参考网址


3-执行过程记录

1-TTS市场现状

2024-12-01 目前应该是ChatTTS的呼声最高,但是为了不引入新的学习成本,暂时不考虑使用ChatTTS,直接使用在线API可以满足我的需求!(百度的TTS有太多的AI味,在这个时间节点上,百度可能还是没有把重心放在这些细节上)


2-Tampermonkey

已经完成整体的功能,可以直接复制当前脚本到Tampermonkey,选中文字即可进行语音播报

// ==UserScript==
// @name         Text to Speech using Baidu API
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Convert selected text to speech using Baidu Text-to-Speech API
// @author       Your name
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function() {
    'use strict';

    // 配置信息 - 需要替换成你的百度API密钥信息
    const API_KEY = '44kNzmCeZNnLHoUcNiGYnyI7';
    const SECRET_KEY = '7sNV1osooTqJliQB1aHB5lJKEKlLrg5f';
    let access_token = '';

    // 获取access_token的函数
    async function getAccessToken() {
        const url = `https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=${API_KEY}&client_secret=${SECRET_KEY}`;
        
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: url,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                onload: function(response) {
                    if (response.status === 200) {
                        try {
                            const data = JSON.parse(response.responseText);
                            if (data.access_token) {
                                access_token = data.access_token;
                                GM_setValue('access_token', access_token);
                                resolve(access_token);
                            } else {
                                console.error('获取access_token失败: 返回数据格式错误', data);
                                reject(new Error('获取access_token失败: 返回数据格式错误'));
                            }
                        } catch (error) {
                            console.error('解析access_token响应失败:', error);
                            reject(error);
                        }
                    } else {
                        console.error('获取access_token失败:', response.status, response.responseText);
                        reject(new Error(`获取access_token失败: ${response.status}`));
                    }
                },
                onerror: function(error) {
                    console.error('请求access_token失败:', error);
                    reject(error);
                }
            });
        });
    }

    // 文字转语音的函数
    async function textToSpeech(text, params = {}) {
        if (!access_token) {
            access_token = GM_getValue('access_token') || await getAccessToken();
        }

        if (!access_token) {
            alert('无法获取access_token,请检查API配置');
            return;
        }

        const url = `https://tsn.baidu.com/text2audio`;
        const requestParams = new URLSearchParams({
            tex: encodeURIComponent(text),
            tok: access_token,
            cuid: 'tampermonkey_tts',
            ctp: 1,
            lan: 'zh',
            spd: params.spd || 5,
            pit: params.pit || 5,
            vol: params.vol || 5,
            per: params.per || 0,
            aue: 3
        });

        try {
            GM_xmlhttpRequest({
                method: 'GET',
                url: `${url}?${requestParams.toString()}`,
                responseType: 'blob',
                onload: function(response) {
                    if (response.status === 200) {
                        const blob = response.response;
                        const audio = new Audio(URL.createObjectURL(blob));
                        audio.play().catch(error => {
                            console.error('播放音频失败:', error);
                            alert('播放音频失败,请重试');
                        });
                    } else {
                        console.error('语音合成请求失败:', response.status);
                        alert('语音合成失败,请重试');
                    }
                },
                onerror: function(error) {
                    console.error('请求失败:', error);
                    alert('请求失败,请重试');
                }
            });
        } catch (error) {
            console.error('语音合成出错:', error);
            alert('语音合成出错,请重试');
        }
    }

    // 创建控制面板
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.style.position = 'fixed';
        panel.style.bottom = '80px';
        panel.style.right = '20px';
        panel.style.zIndex = '9999';
        panel.style.backgroundColor = 'white';
        panel.style.padding = '10px';
        panel.style.border = '1px solid #ccc';
        panel.style.borderRadius = '5px';
        panel.style.display = 'none';

        // 语速控制
        const speedControl = document.createElement('div');
        speedControl.innerHTML = `
            <label>语速(0-15): 
                <input type="range" min="0" max="15" value="5" id="tts-speed">
                <span id="speed-value">5</span>
            </label>
        `;
        
        // 音量控制
        const volumeControl = document.createElement('div');
        volumeControl.innerHTML = `
            <label>音量(0-15): 
                <input type="range" min="0" max="15" value="5" id="tts-volume">
                <span id="volume-value">5</span>
            </label>
        `;

        // 音调控制
        const pitchControl = document.createElement('div');
        pitchControl.innerHTML = `
            <label>音调(0-15): 
                <input type="range" min="0" max="15" value="5" id="tts-pitch">
                <span id="pitch-value">5</span>
            </label>
        `;

        // 发音人选择
        const personControl = document.createElement('div');
        personControl.innerHTML = `
            <label>发音人: 
                <select id="tts-person">
                    <option value="0">女声</option>
                    <option value="1">男声</option>
                    <option value="3">度逍遥</option>
                    <option value="4">度丫丫</option>
                </select>
            </label>
        `;

        panel.appendChild(speedControl);
        panel.appendChild(volumeControl);
        panel.appendChild(pitchControl);
        panel.appendChild(personControl);

        // 添加事件监听
        ['speed', 'volume', 'pitch'].forEach(param => {
            const input = panel.querySelector(`#tts-${param}`);
            const value = panel.querySelector(`#${param}-value`);
            input.addEventListener('input', () => {
                value.textContent = input.value;
                GM_setValue(`tts-${param}`, input.value);
            });
        });

        panel.querySelector('#tts-person').addEventListener('change', (e) => {
            GM_setValue('tts-person', e.target.value);
        });

        document.body.appendChild(panel);
        return panel;
    }

    // 创建悬浮按钮
    function createFloatingButton() {
        const button = document.createElement('button');
        button.innerHTML = '朗读选中文本';
        button.style.position = 'fixed';
        button.style.bottom = '20px';
        button.style.right = '20px';
        button.style.zIndex = '9999';
        button.style.padding = '10px';
        button.style.backgroundColor = '#4CAF50';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';

        const settingsButton = document.createElement('button');
        settingsButton.innerHTML = '⚙️';
        settingsButton.style.position = 'fixed';
        settingsButton.style.bottom = '20px';
        settingsButton.style.right = '140px';
        settingsButton.style.zIndex = '9999';
        settingsButton.style.padding = '10px';
        settingsButton.style.backgroundColor = '#2196F3';
        settingsButton.style.color = 'white';
        settingsButton.style.border = 'none';
        settingsButton.style.borderRadius = '5px';
        settingsButton.style.cursor = 'pointer';

        const panel = createControlPanel();
        
        settingsButton.addEventListener('click', function() {
            panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        });

        button.addEventListener('click', function() {
            const selectedText = window.getSelection().toString().trim();
            if (selectedText) {
                const params = {
                    spd: parseInt(GM_getValue('tts-speed', 9)),
                    vol: parseInt(GM_getValue('tts-volume', 5)),
                    pit: parseInt(GM_getValue('tts-pitch', 5)),
                    per: parseInt(GM_getValue('tts-person', 0))
                };
                textToSpeech(selectedText, params);
            } else {
                alert('请先选择要朗读的文本');
            }
        });

        document.body.appendChild(button);
        document.body.appendChild(settingsButton);
    }

    // 初始化
    createFloatingButton();
})();

3-Python版本

Python3.8.5版本可运行

import requests
import json
import base64
from urllib.parse import quote
import os
from playsound import playsound

class BaiduTTS:
    def __init__(self):
        # API配置信息
        self.API_KEY = '44kNzmCeZNnLHoUcNiGYnyI7'
        self.SECRET_KEY = '7sNV1osooTqJliQB1aHB5lJKEKlLrg5f'
        self.access_token = None
        
    def get_access_token(self):
        """获取access_token"""
        url = f"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={self.API_KEY}&client_secret={self.SECRET_KEY}"
        
        response = requests.post(url)
        if response.status_code == 200:
            result = response.json()
            self.access_token = result['access_token']
            return self.access_token
        else:
            print(f"获取access_token失败: {response.status_code}")
            return None

    def text_to_speech(self, text, params=None):
        """文字转语音"""
        if not self.access_token:
            self.access_token = self.get_access_token()
            
        if not self.access_token:
            print("无法获取access_token")
            return False

        # 默认参数
        default_params = {
            'spd': 9,  # 语速,取值0-15
            'pit': 5,  # 音调,取值0-15
            'vol': 5,  # 音量,取值0-15
            'per': 0,  # 发音人,0为女声,1为男声,3为度逍遥,4为度丫丫
        }

        # 更新参数
        if params:
            default_params.update(params)

        url = "https://tsn.baidu.com/text2audio"
        
        params = {
            'tex': quote(text),
            'tok': self.access_token,
            'cuid': 'python_tts',
            'ctp': 1,
            'lan': 'zh',
            'aue': 3,  # mp3格式
            **default_params
        }
        
        try:
            response = requests.get(url, params=params)
            
            # 检查是否返回音频数据
            if response.headers['Content-Type'].startswith('audio/'):
                # 保存音频文件
                audio_file = "output.mp3"
                with open(audio_file, 'wb') as f:
                    f.write(response.content)
                print(f"已保存音频文件: {audio_file}")
                
                # 播放音频
                playsound(audio_file)
                return True
            else:
                error_msg = response.json()
                print(f"转换失败: {error_msg}")
                return False
                
        except Exception as e:
            print(f"请求失败: {str(e)}")
            return False

def main():
    # 使用示例
    tts = BaiduTTS()
    
    # 要转换的文本
    text = input("请输入要转换的文字: ")
    
    # 可选:自定义参数
    params = {
        'spd': 9,  # 语速
        'pit': 5,  # 音调
        'vol': 5,  # 音量
        'per': 0   # 发音人
    }
    
    # 执行转换
    tts.text_to_speech(text, params)

if __name__ == "__main__":
    main()

4-其他知识补充

1-TTS发展历史

TTS(Text-to-Speech,文本转语音)技术的发展历史可以追溯到20世纪50年代,经历了多个阶段的技术进步和创新。以下是TTS技术发展的主要里程碑:

1. 早期研究(20世纪50年代至70年代)

  • 早期尝试:最早的TTS系统开始于20世纪50年代。研究人员尝试通过机械设备和简单的声音合成器来生成语音。
  • 基础研究:在20世纪60年代和70年代,主要集中在语音合成的基本原理和方法上,如共振峰合成(Formant Synthesis),通过模拟人类声道的共振特性来生成语音。

2. 数字信号处理时代(20世纪80年代至90年代)

  • 数字信号处理技术:随着数字信号处理(DSP)技术的发展,TTS系统开始使用计算机来处理和生成语音。这种方法提高了语音合成的质量和效率。
  • 规则合成器:出现了基于规则的合成器,这些系统通过一系列规则将文本转换为语音。例如,线性预测编码(LPC)技术被广泛应用于语音合成。
  • 有限状态机:有限状态机(FSM)被用于文本分析和语音合成,这种方法简化了复杂文本的处理。

3. 统计方法和数据驱动技术(21世纪初)

  • 统计语音合成:2000年后,统计方法开始主导TTS技术的研究。这些方法依赖于大量的语音数据和文本数据,通过机器学习算法来生成语音。梅尔频率倒谱系数(MFCC)和隐马尔可夫模型(HMM)等技术被广泛应用。
  • 语音库和TTS系统:出现了基于语音库的TTS系统,这些系统使用预先录制的语音片段来生成自然语音。例如,单元选择合成(Unit Selection Synthesis)通过选择和拼接预先录制的语音单元来生成语音。

4. 深度学习和神经网络时代(2010年至今)

  • 神经网络TTS:随着深度学习技术的发展,神经网络在TTS中的应用迅速增长。递归神经网络(RNN)、长短期记忆网络(LSTM)和变分自编码器(VAE)等技术被用于生成更加自然和流畅的语音。
  • 端到端模型:出现了端到端TTS模型,这些模型直接从文本生成语音,简化了处理流程。例如,WaveNet、Tacotron和Transformer等模型显著提高了语音合成的质量。
  • 多语言和个性化TTS:现代TTS系统支持多语言和个性化语音合成。用户可以选择不同的语音风格和角色,甚至可以定制自己的语音模型。

5. 当前趋势和未来展望

  • 低资源语言支持:为了让更多人受益于TTS技术,研究人员正在开发低资源语言的TTS系统,这些系统可以在数据有限的情况下仍然生成高质量的语音。
  • 实时TTS:随着硬件和算法的进步,实时TTS系统的性能不断提升,使得语音合成的延迟大大降低。
  • 多模态TTS:结合其他感知模态(如视觉和触觉)的多模态TTS系统正在研究中,以提供更加丰富的用户体验。

总的来说,TTS技术从早期的简单合成发展到了现在的深度学习驱动的自然语音生成,不断推动着人机交互和无障碍通信的发展。

2-TTS目前的发展

1-深度学习和神经网络时代(2010年至今)

  • 神经网络TTS
    • 技术应用:RNN, LSTM, VAE
    • 目标:自然流畅的语音
  • 端到端模型
    • 模型:WaveNet, Tacotron, Transformer
    • 优势:简化流程,提高质量
  • 多语言和个性化TTS
    • 支持:多语言,个性化语音
    • 功能:选择语音风格和角色,定制语音模型

2-当前趋势和未来展望

  • 低资源语言支持
    • 目标:数据有限下生成高质量语音
  • 实时TTS
    • 进展:硬件和算法进步
    • 结果:降低延迟
  • 多模态TTS
    • 方向:结合视觉和触觉
    • 目标:丰富用户体验

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容