基于Python实现语音合成小工具

Python
324
0
0
2023-06-28
标签   Python实践
目录
  • TTS简介
  • 安装需要的包
  • UI界面 
  • 功能代码
  • 语音工具类
  • 窗体类
  • 完整代码

TTS简介

TTS(Text To Speech)是一种语音合成技术,可以让机器将输入文本以语音的方式播放出来,实现机器说话的效果。

TTS分成语音处理及语音合成,先由机器识别输入的文字,再根据语音库进行语音合成。现在有很多可供调用的TTS接口,比如百度智能云的语音合成接口。微软在Windows系统中也提供了TTS的接口,可以调用此接口实现离线的TTS语音合成功能。

本文将使用pyttsx3库作为示范,编写一个语音合成小工具。

pyttsx3官方文档:https://pyttsx3.readthedocs.io 

本文源码已上传至GitHub:

https://github.com/XMNHCAS/SpeechSynthesisTool

安装需要的包

安装PyQt5及其GUI设计工具

# 安装PyQt
pip install PyQt
 
# 安装PyQt设计器
pip install PyQtDesigner

本文使用的编辑器是VSCode,不是PyCharm,使用PyQt5的方式可能存在差异,具体使用时可以根据实际情况进行配置。 

安装pyttsx3

pip install pyttsx

UI界面 

可参考下图设计简单的GUI界面,由于本文主要为功能实例,故不考虑界面美观问题。

界面应有一个文本输入框,用以输入将要转化为语音的文本,同时需要一个播放按钮,用以触发语音播放的方法。语速、音量和语言可以按需选择。 

使用PyQt5的设计工具,可以根据以上配置的GUI界面生成以下UI(XML)代码:

<?xml version=".0" encoding="UTF-8"?>
<ui version=".0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <property name="geometry">
   <rect>
    <x></x>
    <y></y>
    <width></width>
    <height></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></x>
     <y></y>
     <width></width>
     <height></height>
    </rect>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <property name="spacing">
     <number></number>
    </property>
    <item>
     <layout class="QHBoxLayout" name="horizontalLayout_">
      <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_">
      <item>
       <widget class="QLabel" name="label_">
        <property name="text">
         <string>语速</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QSlider" name="slider_rate">
        <property name="maximum">
         <number></number>
        </property>
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_rate">
        <property name="minimumSize">
         <size>
          <width></width>
          <height></height>
         </size>
        </property>
        <property name="text">
         <string></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_">
        <property name="text">
         <string>音量</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QSlider" name="slider_volumn">
        <property name="maximum">
         <number></number>
        </property>
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QLabel" name="label_volumn">
        <property name="minimumSize">
         <size>
          <width></width>
          <height></height>
         </size>
        </property>
        <property name="text">
         <string></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_">
        <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_">
      <item>
       <widget class="QLabel" name="label_">
        <property name="minimumSize">
         <size>
          <width></width>
          <height></height>
         </size>
        </property>
        <property name="text">
         <string/>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="btn_play">
        <property name="minimumSize">
         <size>
          <width></width>
          <height></height>
         </size>
        </property>
        <property name="text">
         <string>播放</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

最后再使用PyQt5的界面工具,可以根据以上UI的代码,生成以下的窗体类:

# -*- coding: utf- -*-
 
# Form implementation generated from reading ui file 'd:\Program\VSCode\Python\TTS_PyQT\tts_form.ui'
#
# Created by: PyQt UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic is
# run again.  Do not edit this file unless you know what you are doing.
 
from PyQt import QtCore, QtGui, QtWidgets
 
 
class Ui_Form(object):
 
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(, 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, 291, 261))
        self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
        self.verticalLayout.setContentsMargins(, 0, 0, 0)
        self.verticalLayout.setSpacing()
        self.verticalLayout.setObjectName("verticalLayout")
        self.horizontalLayout_ = QtWidgets.QHBoxLayout()
        self.horizontalLayout_.setObjectName("horizontalLayout_2")
        self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label.setAlignment(QtCore.Qt.AlignJustify | QtCore.Qt.AlignTop)
        self.label.setObjectName("label")
        self.horizontalLayout_.addWidget(self.label)
        self.tbx_text = QtWidgets.QTextEdit(self.verticalLayoutWidget)
        self.tbx_text.setObjectName("tbx_text")
        self.horizontalLayout_.addWidget(self.tbx_text)
        self.verticalLayout.addLayout(self.horizontalLayout_)
        self.horizontalLayout_ = QtWidgets.QHBoxLayout()
        self.horizontalLayout_.setObjectName("horizontalLayout_4")
        self.label_ = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label_.setObjectName("label_3")
        self.horizontalLayout_.addWidget(self.label_3)
        self.slider_rate = QtWidgets.QSlider(self.verticalLayoutWidget)
        self.slider_rate.setMaximum()
        self.slider_rate.setOrientation(QtCore.Qt.Horizontal)
        self.slider_rate.setObjectName("slider_rate")
        self.horizontalLayout_.addWidget(self.slider_rate)
        self.label_rate = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label_rate.setMinimumSize(QtCore.QSize(, 0))
        self.label_rate.setAlignment(QtCore.Qt.AlignCenter)
        self.label_rate.setObjectName("label_rate")
        self.horizontalLayout_.addWidget(self.label_rate)
        self.verticalLayout.addLayout(self.horizontalLayout_)
        self.horizontalLayout_ = QtWidgets.QHBoxLayout()
        self.horizontalLayout_.setObjectName("horizontalLayout_3")
        self.label_ = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label_.setObjectName("label_2")
        self.horizontalLayout_.addWidget(self.label_2)
        self.slider_volumn = QtWidgets.QSlider(self.verticalLayoutWidget)
        self.slider_volumn.setMaximum()
        self.slider_volumn.setOrientation(QtCore.Qt.Horizontal)
        self.slider_volumn.setObjectName("slider_volumn")
        self.horizontalLayout_.addWidget(self.slider_volumn)
        self.label_volumn = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label_volumn.setMinimumSize(QtCore.QSize(, 0))
        self.label_volumn.setAlignment(QtCore.Qt.AlignCenter)
        self.label_volumn.setObjectName("label_volumn")
        self.horizontalLayout_.addWidget(self.label_volumn)
        self.verticalLayout.addLayout(self.horizontalLayout_)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label_ = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label_.setObjectName("label_4")
        self.horizontalLayout.addWidget(self.label_)
        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_ = QtWidgets.QHBoxLayout()
        self.horizontalLayout_.setObjectName("horizontalLayout_5")
        self.label_ = QtWidgets.QLabel(self.verticalLayoutWidget)
        self.label_.setMinimumSize(QtCore.QSize(60, 0))
        self.label_.setText("")
        self.label_.setObjectName("label_5")
        self.horizontalLayout_.addWidget(self.label_5)
        self.btn_play = QtWidgets.QPushButton(self.verticalLayoutWidget)
        self.btn_play.setMinimumSize(QtCore.QSize(, 30))
        self.btn_play.setObjectName("btn_play")
        self.horizontalLayout_.addWidget(self.btn_play)
        self.verticalLayout.addLayout(self.horizontalLayout_)
 
        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_.setText(_translate("Form", "语速"))
        self.label_rate.setText(_translate("Form", ""))
        self.label_.setText(_translate("Form", "音量"))
        self.label_volumn.setText(_translate("Form", ""))
        self.label_.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 = pyttsx.init()

我们可以通过该对象的setProperty方法,对语音合成的对象的属性进行修改:

属性名

解释

rate

以每分钟字数表示的整数语速

volume

音量,取值范围为[0.0, 1.0]

voices

语音的字符串标识符

语音工具类代码如下,代码含义可参考注释:

import pyttsx
 
 
class VoiceEngine():
    '''
    tts 语音工具类
    '''
 
    def __init__(self):
        '''
        初始化
        '''
        # tts对象
        self.__engine = pyttsx.init()
        # 语速
        self.__rate =
        # 音量
        self.__volume =
        # 语音ID,为中文,1为英文
        self.__voice =
 
    @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: -- 中文;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.mp')
 
        self.__engine.say(text)
        self.__engine.runAndWait()
        self.__engine.stop()

窗体类

我们可以创建一个继承于我们刚刚创建的PyQt5的窗体类,并为窗体的拖拽事件和点击事件注册回调函数,同时创建一个语音工具类的实例,用以实现指定事件触发时需要执行的语音操作。

import sys
import _thread as th
from PyQt.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 /))
        self.engine.Volume = value
 
    def onSelectVoice_zh(self):
        '''
        修改中文的语音配置及默认播放文本
        '''
        self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
        self.engine.VoiceID =
 
    def onSelectVoice_en(self):
        '''
        修改英文的语音配置及默认的播放文本
        '''
        self.tbx_text.setText('Hello World')
        self.engine.VoiceID =
 
    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 PyQt.QtWidgets import QMainWindow, QApplication
from Ui_tts_form import Ui_Form
import pyttsx
 
 
class VoiceEngine():
    '''
    tts 语音工具类
    '''
 
    def __init__(self):
        '''
        初始化
        '''
        # tts对象
        self.__engine = pyttsx.init()
        # 语速
        self.__rate =
        # 音量
        self.__volume =
        # 语音ID,为中文,1为英文
        self.__voice =
 
    @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: -- 中文;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.mp')
 
        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 /))
        self.engine.Volume = value
 
    def onSelectVoice_zh(self):
        '''
        修改中文的语音配置及默认播放文本
        '''
        self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
        self.engine.VoiceID =
 
    def onSelectVoice_en(self):
        '''
        修改英文的语音配置及默认的播放文本
        '''
        self.tbx_text.setText('Hello World')
        self.engine.VoiceID =
 
    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_())