基于Python实现语音合成小工具
作者:虚梦年华 发布时间:2023-01-13 15:25:38
TTS简介
TTS(Text To Speech)是一种语音合成技术,可以让机器将输入文本以语音的方式播放出来,实现机器说话的效果。
TTS分成语音处理及语音合成,先由机器识别输入的文字,再根据语音库进行语音合成。现在有很多可供调用的TTS接口,比如百度智能云的语音合成接口。微软在Windows系统中也提供了TTS的接口,可以调用此接口实现离线的TTS语音合成功能。
本文将使用pyttsx3库作为示范,编写一个语音合成小工具。
pyttsx3官方文档:https://pyttsx3.readthedocs.io
本文源码已上传至GitHub:
https://github.com/XMNHCAS/SpeechSynthesisTool
安装需要的包
安装PyQt5及其GUI设计工具
# 安装PyQt5
pip install PyQt5
# 安装PyQt5设计器
pip install PyQt5Designer
本文使用的编辑器是VSCode,不是PyCharm,使用PyQt5的方式可能存在差异,具体使用时可以根据实际情况进行配置。
安装pyttsx3
pip install pyttsx3
UI界面
可参考下图设计简单的GUI界面,由于本文主要为功能实例,故不考虑界面美观问题。
界面应有一个文本输入框,用以输入将要转化为语音的文本,同时需要一个播放按钮,用以触发语音播放的方法。语速、音量和语言可以按需选择。
使用PyQt5的设计工具,可以根据以上配置的GUI界面生成以下UI(XML)代码:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>313</width>
<height>284</height>
</rect>
</property>
<property name="windowTitle">
<string>语音合成器</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>voice.ico</normaloff>voice.ico</iconset>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>291</width>
<height>261</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>20</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>播报文本</string>
</property>
<property name="alignment">
<set>Qt::AlignJustify|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="tbx_text"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>语速</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="slider_rate">
<property name="maximum">
<number>300</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_rate">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>音量</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="slider_volumn">
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_volumn">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>选择语言</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbtn_zh">
<property name="text">
<string>中文</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbtn_en">
<property name="text">
<string>英文</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_play">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>播放</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>
最后再使用PyQt5的界面工具,可以根据以上UI的代码,生成以下的窗体类:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'd:\Program\VSCode\Python\TTS_PyQT\tts_form.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(313, 284)
icon = QtGui.QIcon()
icon.addPixmap(
QtGui.QPixmap("./voice.ico"),
QtGui.QIcon.Normal, QtGui.QIcon.Off)
Form.setWindowIcon(icon)
self.verticalLayoutWidget = QtWidgets.QWidget(Form)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 291, 261))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setSpacing(20)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label.setAlignment(QtCore.Qt.AlignJustify | QtCore.Qt.AlignTop)
self.label.setObjectName("label")
self.horizontalLayout_2.addWidget(self.label)
self.tbx_text = QtWidgets.QTextEdit(self.verticalLayoutWidget)
self.tbx_text.setObjectName("tbx_text")
self.horizontalLayout_2.addWidget(self.tbx_text)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.label_3 = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label_3.setObjectName("label_3")
self.horizontalLayout_4.addWidget(self.label_3)
self.slider_rate = QtWidgets.QSlider(self.verticalLayoutWidget)
self.slider_rate.setMaximum(300)
self.slider_rate.setOrientation(QtCore.Qt.Horizontal)
self.slider_rate.setObjectName("slider_rate")
self.horizontalLayout_4.addWidget(self.slider_rate)
self.label_rate = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label_rate.setMinimumSize(QtCore.QSize(30, 0))
self.label_rate.setAlignment(QtCore.Qt.AlignCenter)
self.label_rate.setObjectName("label_rate")
self.horizontalLayout_4.addWidget(self.label_rate)
self.verticalLayout.addLayout(self.horizontalLayout_4)
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.label_2 = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label_2.setObjectName("label_2")
self.horizontalLayout_3.addWidget(self.label_2)
self.slider_volumn = QtWidgets.QSlider(self.verticalLayoutWidget)
self.slider_volumn.setMaximum(100)
self.slider_volumn.setOrientation(QtCore.Qt.Horizontal)
self.slider_volumn.setObjectName("slider_volumn")
self.horizontalLayout_3.addWidget(self.slider_volumn)
self.label_volumn = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label_volumn.setMinimumSize(QtCore.QSize(30, 0))
self.label_volumn.setAlignment(QtCore.Qt.AlignCenter)
self.label_volumn.setObjectName("label_volumn")
self.horizontalLayout_3.addWidget(self.label_volumn)
self.verticalLayout.addLayout(self.horizontalLayout_3)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label_4 = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label_4.setObjectName("label_4")
self.horizontalLayout.addWidget(self.label_4)
self.rbtn_zh = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.rbtn_zh.setChecked(True)
self.rbtn_zh.setObjectName("rbtn_zh")
self.horizontalLayout.addWidget(self.rbtn_zh)
self.rbtn_en = QtWidgets.QRadioButton(self.verticalLayoutWidget)
self.rbtn_en.setObjectName("rbtn_en")
self.horizontalLayout.addWidget(self.rbtn_en)
self.verticalLayout.addLayout(self.horizontalLayout)
self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
self.horizontalLayout_5.setObjectName("horizontalLayout_5")
self.label_5 = QtWidgets.QLabel(self.verticalLayoutWidget)
self.label_5.setMinimumSize(QtCore.QSize(60, 0))
self.label_5.setText("")
self.label_5.setObjectName("label_5")
self.horizontalLayout_5.addWidget(self.label_5)
self.btn_play = QtWidgets.QPushButton(self.verticalLayoutWidget)
self.btn_play.setMinimumSize(QtCore.QSize(0, 30))
self.btn_play.setObjectName("btn_play")
self.horizontalLayout_5.addWidget(self.btn_play)
self.verticalLayout.addLayout(self.horizontalLayout_5)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "语音合成器"))
self.label.setText(_translate("Form", "播报文本"))
self.label_3.setText(_translate("Form", "语速"))
self.label_rate.setText(_translate("Form", "0"))
self.label_2.setText(_translate("Form", "音量"))
self.label_volumn.setText(_translate("Form", "0"))
self.label_4.setText(_translate("Form", "选择语言"))
self.rbtn_zh.setText(_translate("Form", "中文"))
self.rbtn_en.setText(_translate("Form", "英文"))
self.btn_play.setText(_translate("Form", "播放"))
如果直接复制此代码,可能会出现图标丢失的问题。这个需要根据实际情况修改icon的配置,并添加要使用的ico图标文件。
功能代码
语音工具类
首先我们需要初始化并获取语音合成用的语音引擎对象。
# tts对象
engine = pyttsx3.init()
我们可以通过该对象的setProperty方法,对语音合成的对象的属性进行修改:
属性名 | 解释 |
rate | 以每分钟字数表示的整数语速 |
volume | 音量,取值范围为[0.0, 1.0] |
voices | 语音的字符串标识符 |
语音工具类代码如下,代码含义可参考注释:
import pyttsx3
class VoiceEngine():
'''
tts 语音工具类
'''
def __init__(self):
'''
初始化
'''
# tts对象
self.__engine = pyttsx3.init()
# 语速
self.__rate = 150
# 音量
self.__volume = 100
# 语音ID,0为中文,1为英文
self.__voice = 0
@property
def Rate(self):
'''
语速属性
'''
return self.__rate
@Rate.setter
def Rate(self, value):
self.__rate = value
@property
def Volume(self):
'''
音量属性
'''
return self.__volume
@Volume.setter
def Volume(self, value):
self.__volume = value
@property
def VoiceID(self):
'''
语音ID:0 -- 中文;1 -- 英文
'''
return self.__voice
@VoiceID.setter
def VoiceID(self, value):
self.__voice = value
def Say(self, text):
'''
播放语音
'''
self.__engine.setProperty('rate', self.__rate)
self.__engine.setProperty('volume', self.__volume)
# 获取可用语音列表,并设置语音
voices = self.__engine.getProperty('voices')
self.__engine.setProperty('voice', voices[self.__voice].id)
# 保存语音文件
# self.__engine.save_to_file(text, 'test.mp3')
self.__engine.say(text)
self.__engine.runAndWait()
self.__engine.stop()
窗体类
我们可以创建一个继承于我们刚刚创建的PyQt5的窗体类,并为窗体的拖拽事件和点击事件注册回调函数,同时创建一个语音工具类的实例,用以实现指定事件触发时需要执行的语音操作。
import sys
import _thread as th
from PyQt5.QtWidgets import QMainWindow, QApplication
from Ui_tts_form import Ui_Form
class MainWindow(QMainWindow, Ui_Form):
'''
窗体类
'''
def __init__(self, parent=None):
'''
初始化窗体
'''
super(MainWindow, self).__init__(parent)
self.setupUi(self)
# 获取tts工具类实例
self.engine = VoiceEngine()
self.__isPlaying = False
# 设置初始文本
self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
# 进度条数据绑定到label中显示
self.slider_rate.valueChanged.connect(self.setRateTextValue)
self.slider_volumn.valueChanged.connect(self.setVolumnTextValue)
# 设置进度条初始值
self.slider_rate.setValue(self.engine.Rate)
self.slider_volumn.setValue(self.engine.Volume)
# RadioButton选择事件
self.rbtn_zh.toggled.connect(self.onSelectVoice_zh)
self.rbtn_en.toggled.connect(self.onSelectVoice_en)
# 播放按钮点击事件
self.btn_play.clicked.connect(self.onPlayButtonClick)
def setRateTextValue(self):
'''
修改语速label文本值
'''
value = self.slider_rate.value()
self.label_rate.setText(str(value))
self.engine.Rate = value
def setVolumnTextValue(self):
'''
修改音量label文本值
'''
value = self.slider_volumn.value()
self.label_volumn.setText(str(value / 100))
self.engine.Volume = value
def onSelectVoice_zh(self):
'''
修改中文的语音配置及默认播放文本
'''
self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
self.engine.VoiceID = 0
def onSelectVoice_en(self):
'''
修改英文的语音配置及默认的播放文本
'''
self.tbx_text.setText('Hello World')
self.engine.VoiceID = 1
def playVoice(self):
'''
播放
'''
if self.__isPlaying is not True:
self.__isPlaying = True
text = self.tbx_text.toPlainText()
self.engine.Say(text)
self.__isPlaying = False
def onPlayButtonClick(self):
'''
播放按钮点击事件
开启线程新线程播放语音,避免窗体因为语音播放而假卡死
'''
th.start_new_thread(self.playVoice, ())
完整代码
import sys
import _thread as th
from PyQt5.QtWidgets import QMainWindow, QApplication
from Ui_tts_form import Ui_Form
import pyttsx3
class VoiceEngine():
'''
tts 语音工具类
'''
def __init__(self):
'''
初始化
'''
# tts对象
self.__engine = pyttsx3.init()
# 语速
self.__rate = 150
# 音量
self.__volume = 100
# 语音ID,0为中文,1为英文
self.__voice = 0
@property
def Rate(self):
'''
语速属性
'''
return self.__rate
@Rate.setter
def Rate(self, value):
self.__rate = value
@property
def Volume(self):
'''
音量属性
'''
return self.__volume
@Volume.setter
def Volume(self, value):
self.__volume = value
@property
def VoiceID(self):
'''
语音ID:0 -- 中文;1 -- 英文
'''
return self.__voice
@VoiceID.setter
def VoiceID(self, value):
self.__voice = value
def Say(self, text):
'''
播放语音
'''
self.__engine.setProperty('rate', self.__rate)
self.__engine.setProperty('volume', self.__volume)
voices = self.__engine.getProperty('voices')
self.__engine.setProperty('voice', voices[self.__voice])
# 保存语音文件
# self.__engine.save_to_file(text, 'test.mp3')
self.__engine.say(text)
self.__engine.runAndWait()
self.__engine.stop()
class MainWindow(QMainWindow, Ui_Form):
'''
窗体类
'''
def __init__(self, parent=None):
'''
初始化窗体
'''
super(MainWindow, self).__init__(parent)
self.setupUi(self)
# 获取tts工具类实例
self.engine = VoiceEngine()
self.__isPlaying = False
# 设置初始文本
self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
# 进度条数据绑定到label中显示
self.slider_rate.valueChanged.connect(self.setRateTextValue)
self.slider_volumn.valueChanged.connect(self.setVolumnTextValue)
# 设置进度条初始值
self.slider_rate.setValue(self.engine.Rate)
self.slider_volumn.setValue(self.engine.Volume)
# RadioButton选择事件
self.rbtn_zh.toggled.connect(self.onSelectVoice_zh)
self.rbtn_en.toggled.connect(self.onSelectVoice_en)
# 播放按钮点击事件
self.btn_play.clicked.connect(self.onPlayButtonClick)
def setRateTextValue(self):
'''
修改语速label文本值
'''
value = self.slider_rate.value()
self.label_rate.setText(str(value))
self.engine.Rate = value
def setVolumnTextValue(self):
'''
修改音量label文本值
'''
value = self.slider_volumn.value()
self.label_volumn.setText(str(value / 100))
self.engine.Volume = value
def onSelectVoice_zh(self):
'''
修改中文的语音配置及默认播放文本
'''
self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
self.engine.VoiceID = 0
def onSelectVoice_en(self):
'''
修改英文的语音配置及默认的播放文本
'''
self.tbx_text.setText('Hello World')
self.engine.VoiceID = 1
def playVoice(self):
'''
播放
'''
if self.__isPlaying is not True:
self.__isPlaying = True
text = self.tbx_text.toPlainText()
self.engine.Say(text)
self.__isPlaying = False
def onPlayButtonClick(self):
'''
修改语速label文本值
'''
th.start_new_thread(self.playVoice, ())
if __name__ == "__main__":
'''
主函数
'''
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
来源:https://blog.csdn.net/XUMENGCAS/article/details/128108557


猜你喜欢
- 1、time模块(※※※※)import time #导入时间模块print(time.time()) #返回当前时间的时间戳,可用于计算程
- 在MacOs运行的PyCharm中,执行python文件,如果不指定python文件字符编码会报错:SyntaxError: Non-ASC
- 一、界面介绍文件导航区域 能够 浏览/定位/打开 项目文件文件编辑区域 能够 编辑 当前打开的文件控制台区域 能够:输出程序执行内容跟踪调试
- 动画精灵和碰撞检测一、动画精灵动画精灵:四处移动的单个图像或图像部分称为动画精灵(sprite),pygame有一个特殊的模块帮助跟踪屏幕上
- django和webpy都是python的web开发框架。Django的主要目的是简便、快速的开发数据库驱动的网站。它强调代码复用,多个组件
- 本文实例讲述了python对象转字典的两种实现方式。分享给大家供大家参考,具体如下:一. 方便但不完美的__dict__对象转字典用到的方法
- 一、MySQL 逻辑架构概览MySQL 最重要、最与众不同的特性就是它的可插拔存储引擎架构(pluggable storage engine
- 这篇文章主要介绍了如何使用python实现模拟鼠标点击,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 问题概述:有时候在使用print函数输出时,往往需要不断地切换字符串和变量,操作起来很不方便,需要不断地打引号和逗号。比如:firstNam
- 绘制一个菱形四边形,边长为 200 像素。方法1和2绘制了内角为60和120度的菱形,方法3绘制了内角为90度的菱形。方法1
- pytest是python语言中一款强大的单元测试框架,用来管理和组织测试用例,可应用在单元测试、自动化测试工作中。unittest也是py
- 作者:做梦的人(小姐姐)出处:https://www.cnblogs.com/chongyou/python读取yaml文件使用,有两种方式
- 前言在本次python文章中,主要通过定义一个排序方法,实现一组数列能够按照另一组数列指定的位置进行重新排序输出,默认正序排序,可通过Tru
- 在项目中遇到后台数据还没有加载完毕,但是页面上调用了后台数据中的字段,这样就会报undefined。例如:一进入页面直接回显数据。我在cre
- ES6添加了Promise对象,成功时在then中处理,失败则在catch中处理,但有时候,我们需要在无论成功或失败时都要做一些事,比如隐藏
- 看了不少朋友的个人网站,有一个小问题,似乎很多朋友都忽略了,那就是版权声明的写法。虽然那只是一小行字,不过作为设计师也好,作为个人的爱好也好
- 使用 str.join() 方法打印不带括号的元组,例如 result = ','.join(my_tuple)。 str.
- 本文实例为大家分享了python遗传算法的具体代码,供大家参考,具体内容如下1、基本概念遗传算法(GA)是最早由美国Holland教授提出的
- ajax开发中, 常遇到下面的几种情况: 1 服务端需要返回一段普通文本给客户端 2 服务端需要返回一段HTML代码给客户端 3 服务端需要
- 1. 云开发简介由于小程序本身存储数据的能力有限,所以不可能将大量的数据保存在客户端,而且将数据保存在本地既不安全,也无法与其他小程序用户共