• Python语音合成小工具(PyQt5 + pyttsx3)


    TTS简介

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

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

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

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

    本文源码已上传至GitHub:

    https://github.com/XMNHCAS/SpeechSynthesisTool


    安装需要的包

    安装PyQt5及其GUI设计工具

    1. # 安装PyQt5
    2. pip install PyQt5
    3. # 安装PyQt5设计器
    4. pip install PyQt5Designer

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

    安装pyttsx3

    pip install pyttsx3

    UI界面 

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

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

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

    1. "1.0" encoding="UTF-8"?>
    2. <ui version="4.0">
    3. <class>Formclass>
    4. <widget class="QWidget" name="Form">
    5. <property name="geometry">
    6. <rect>
    7. <x>0x>
    8. <y>0y>
    9. <width>313width>
    10. <height>284height>
    11. rect>
    12. property>
    13. <property name="windowTitle">
    14. <string>语音合成器string>
    15. property>
    16. <property name="windowIcon">
    17. <iconset>
    18. <normaloff>voice.iconormaloff>voice.icoiconset>
    19. property>
    20. <widget class="QWidget" name="verticalLayoutWidget">
    21. <property name="geometry">
    22. <rect>
    23. <x>10x>
    24. <y>10y>
    25. <width>291width>
    26. <height>261height>
    27. rect>
    28. property>
    29. <layout class="QVBoxLayout" name="verticalLayout">
    30. <property name="spacing">
    31. <number>20number>
    32. property>
    33. <item>
    34. <layout class="QHBoxLayout" name="horizontalLayout_2">
    35. <item>
    36. <widget class="QLabel" name="label">
    37. <property name="text">
    38. <string>播报文本string>
    39. property>
    40. <property name="alignment">
    41. <set>Qt::AlignJustify|Qt::AlignTopset>
    42. property>
    43. widget>
    44. item>
    45. <item>
    46. <widget class="QTextEdit" name="tbx_text"/>
    47. item>
    48. layout>
    49. item>
    50. <item>
    51. <layout class="QHBoxLayout" name="horizontalLayout_4">
    52. <item>
    53. <widget class="QLabel" name="label_3">
    54. <property name="text">
    55. <string>语速string>
    56. property>
    57. widget>
    58. item>
    59. <item>
    60. <widget class="QSlider" name="slider_rate">
    61. <property name="maximum">
    62. <number>300number>
    63. property>
    64. <property name="orientation">
    65. <enum>Qt::Horizontalenum>
    66. property>
    67. widget>
    68. item>
    69. <item>
    70. <widget class="QLabel" name="label_rate">
    71. <property name="minimumSize">
    72. <size>
    73. <width>30width>
    74. <height>0height>
    75. size>
    76. property>
    77. <property name="text">
    78. <string>0string>
    79. property>
    80. <property name="alignment">
    81. <set>Qt::AlignCenterset>
    82. property>
    83. widget>
    84. item>
    85. layout>
    86. item>
    87. <item>
    88. <layout class="QHBoxLayout" name="horizontalLayout_3">
    89. <item>
    90. <widget class="QLabel" name="label_2">
    91. <property name="text">
    92. <string>音量string>
    93. property>
    94. widget>
    95. item>
    96. <item>
    97. <widget class="QSlider" name="slider_volumn">
    98. <property name="maximum">
    99. <number>100number>
    100. property>
    101. <property name="orientation">
    102. <enum>Qt::Horizontalenum>
    103. property>
    104. widget>
    105. item>
    106. <item>
    107. <widget class="QLabel" name="label_volumn">
    108. <property name="minimumSize">
    109. <size>
    110. <width>30width>
    111. <height>0height>
    112. size>
    113. property>
    114. <property name="text">
    115. <string>0string>
    116. property>
    117. <property name="alignment">
    118. <set>Qt::AlignCenterset>
    119. property>
    120. widget>
    121. item>
    122. layout>
    123. item>
    124. <item>
    125. <layout class="QHBoxLayout" name="horizontalLayout">
    126. <item>
    127. <widget class="QLabel" name="label_4">
    128. <property name="text">
    129. <string>选择语言string>
    130. property>
    131. widget>
    132. item>
    133. <item>
    134. <widget class="QRadioButton" name="rbtn_zh">
    135. <property name="text">
    136. <string>中文string>
    137. property>
    138. <property name="checked">
    139. <bool>truebool>
    140. property>
    141. widget>
    142. item>
    143. <item>
    144. <widget class="QRadioButton" name="rbtn_en">
    145. <property name="text">
    146. <string>英文string>
    147. property>
    148. widget>
    149. item>
    150. layout>
    151. item>
    152. <item>
    153. <layout class="QHBoxLayout" name="horizontalLayout_5">
    154. <item>
    155. <widget class="QLabel" name="label_5">
    156. <property name="minimumSize">
    157. <size>
    158. <width>60width>
    159. <height>0height>
    160. size>
    161. property>
    162. <property name="text">
    163. <string/>
    164. property>
    165. widget>
    166. item>
    167. <item>
    168. <widget class="QPushButton" name="btn_play">
    169. <property name="minimumSize">
    170. <size>
    171. <width>0width>
    172. <height>30height>
    173. size>
    174. property>
    175. <property name="text">
    176. <string>播放string>
    177. property>
    178. widget>
    179. item>
    180. layout>
    181. item>
    182. layout>
    183. widget>
    184. widget>
    185. <resources/>
    186. <connections/>
    187. ui>

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

    1. # -*- coding: utf-8 -*-
    2. # Form implementation generated from reading ui file 'd:\Program\VSCode\Python\TTS_PyQT\tts_form.ui'
    3. #
    4. # Created by: PyQt5 UI code generator 5.15.7
    5. #
    6. # WARNING: Any manual changes made to this file will be lost when pyuic5 is
    7. # run again. Do not edit this file unless you know what you are doing.
    8. from PyQt5 import QtCore, QtGui, QtWidgets
    9. class Ui_Form(object):
    10. def setupUi(self, Form):
    11. Form.setObjectName("Form")
    12. Form.resize(313, 284)
    13. icon = QtGui.QIcon()
    14. icon.addPixmap(
    15. QtGui.QPixmap("./voice.ico"),
    16. QtGui.QIcon.Normal, QtGui.QIcon.Off)
    17. Form.setWindowIcon(icon)
    18. self.verticalLayoutWidget = QtWidgets.QWidget(Form)
    19. self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 291, 261))
    20. self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
    21. self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
    22. self.verticalLayout.setContentsMargins(0, 0, 0, 0)
    23. self.verticalLayout.setSpacing(20)
    24. self.verticalLayout.setObjectName("verticalLayout")
    25. self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
    26. self.horizontalLayout_2.setObjectName("horizontalLayout_2")
    27. self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
    28. self.label.setAlignment(QtCore.Qt.AlignJustify | QtCore.Qt.AlignTop)
    29. self.label.setObjectName("label")
    30. self.horizontalLayout_2.addWidget(self.label)
    31. self.tbx_text = QtWidgets.QTextEdit(self.verticalLayoutWidget)
    32. self.tbx_text.setObjectName("tbx_text")
    33. self.horizontalLayout_2.addWidget(self.tbx_text)
    34. self.verticalLayout.addLayout(self.horizontalLayout_2)
    35. self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
    36. self.horizontalLayout_4.setObjectName("horizontalLayout_4")
    37. self.label_3 = QtWidgets.QLabel(self.verticalLayoutWidget)
    38. self.label_3.setObjectName("label_3")
    39. self.horizontalLayout_4.addWidget(self.label_3)
    40. self.slider_rate = QtWidgets.QSlider(self.verticalLayoutWidget)
    41. self.slider_rate.setMaximum(300)
    42. self.slider_rate.setOrientation(QtCore.Qt.Horizontal)
    43. self.slider_rate.setObjectName("slider_rate")
    44. self.horizontalLayout_4.addWidget(self.slider_rate)
    45. self.label_rate = QtWidgets.QLabel(self.verticalLayoutWidget)
    46. self.label_rate.setMinimumSize(QtCore.QSize(30, 0))
    47. self.label_rate.setAlignment(QtCore.Qt.AlignCenter)
    48. self.label_rate.setObjectName("label_rate")
    49. self.horizontalLayout_4.addWidget(self.label_rate)
    50. self.verticalLayout.addLayout(self.horizontalLayout_4)
    51. self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
    52. self.horizontalLayout_3.setObjectName("horizontalLayout_3")
    53. self.label_2 = QtWidgets.QLabel(self.verticalLayoutWidget)
    54. self.label_2.setObjectName("label_2")
    55. self.horizontalLayout_3.addWidget(self.label_2)
    56. self.slider_volumn = QtWidgets.QSlider(self.verticalLayoutWidget)
    57. self.slider_volumn.setMaximum(100)
    58. self.slider_volumn.setOrientation(QtCore.Qt.Horizontal)
    59. self.slider_volumn.setObjectName("slider_volumn")
    60. self.horizontalLayout_3.addWidget(self.slider_volumn)
    61. self.label_volumn = QtWidgets.QLabel(self.verticalLayoutWidget)
    62. self.label_volumn.setMinimumSize(QtCore.QSize(30, 0))
    63. self.label_volumn.setAlignment(QtCore.Qt.AlignCenter)
    64. self.label_volumn.setObjectName("label_volumn")
    65. self.horizontalLayout_3.addWidget(self.label_volumn)
    66. self.verticalLayout.addLayout(self.horizontalLayout_3)
    67. self.horizontalLayout = QtWidgets.QHBoxLayout()
    68. self.horizontalLayout.setObjectName("horizontalLayout")
    69. self.label_4 = QtWidgets.QLabel(self.verticalLayoutWidget)
    70. self.label_4.setObjectName("label_4")
    71. self.horizontalLayout.addWidget(self.label_4)
    72. self.rbtn_zh = QtWidgets.QRadioButton(self.verticalLayoutWidget)
    73. self.rbtn_zh.setChecked(True)
    74. self.rbtn_zh.setObjectName("rbtn_zh")
    75. self.horizontalLayout.addWidget(self.rbtn_zh)
    76. self.rbtn_en = QtWidgets.QRadioButton(self.verticalLayoutWidget)
    77. self.rbtn_en.setObjectName("rbtn_en")
    78. self.horizontalLayout.addWidget(self.rbtn_en)
    79. self.verticalLayout.addLayout(self.horizontalLayout)
    80. self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
    81. self.horizontalLayout_5.setObjectName("horizontalLayout_5")
    82. self.label_5 = QtWidgets.QLabel(self.verticalLayoutWidget)
    83. self.label_5.setMinimumSize(QtCore.QSize(60, 0))
    84. self.label_5.setText("")
    85. self.label_5.setObjectName("label_5")
    86. self.horizontalLayout_5.addWidget(self.label_5)
    87. self.btn_play = QtWidgets.QPushButton(self.verticalLayoutWidget)
    88. self.btn_play.setMinimumSize(QtCore.QSize(0, 30))
    89. self.btn_play.setObjectName("btn_play")
    90. self.horizontalLayout_5.addWidget(self.btn_play)
    91. self.verticalLayout.addLayout(self.horizontalLayout_5)
    92. self.retranslateUi(Form)
    93. QtCore.QMetaObject.connectSlotsByName(Form)
    94. def retranslateUi(self, Form):
    95. _translate = QtCore.QCoreApplication.translate
    96. Form.setWindowTitle(_translate("Form", "语音合成器"))
    97. self.label.setText(_translate("Form", "播报文本"))
    98. self.label_3.setText(_translate("Form", "语速"))
    99. self.label_rate.setText(_translate("Form", "0"))
    100. self.label_2.setText(_translate("Form", "音量"))
    101. self.label_volumn.setText(_translate("Form", "0"))
    102. self.label_4.setText(_translate("Form", "选择语言"))
    103. self.rbtn_zh.setText(_translate("Form", "中文"))
    104. self.rbtn_en.setText(_translate("Form", "英文"))
    105. self.btn_play.setText(_translate("Form", "播放"))

    如果直接复制此代码,可能会出现图标丢失的问题。这个需要根据实际情况修改icon的配置,并添加要使用的ico图标文件。 


    功能代码

    语音工具类

    首先我们需要初始化并获取语音合成用的语音引擎对象。

    1. # tts对象
    2. engine = pyttsx3.init()

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

    属性名解释
    rate以每分钟字数表示的整数语速
    volume音量,取值范围为[0.0, 1.0]
    voices语音的字符串标识符

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

    1. import pyttsx3
    2. class VoiceEngine():
    3. '''
    4. tts 语音工具类
    5. '''
    6. def __init__(self):
    7. '''
    8. 初始化
    9. '''
    10. # tts对象
    11. self.__engine = pyttsx3.init()
    12. # 语速
    13. self.__rate = 150
    14. # 音量
    15. self.__volume = 100
    16. # 语音ID,0为中文,1为英文
    17. self.__voice = 0
    18. @property
    19. def Rate(self):
    20. '''
    21. 语速属性
    22. '''
    23. return self.__rate
    24. @Rate.setter
    25. def Rate(self, value):
    26. self.__rate = value
    27. @property
    28. def Volume(self):
    29. '''
    30. 音量属性
    31. '''
    32. return self.__volume
    33. @Volume.setter
    34. def Volume(self, value):
    35. self.__volume = value
    36. @property
    37. def VoiceID(self):
    38. '''
    39. 语音ID:0 -- 中文;1 -- 英文
    40. '''
    41. return self.__voice
    42. @VoiceID.setter
    43. def VoiceID(self, value):
    44. self.__voice = value
    45. def Say(self, text):
    46. '''
    47. 播放语音
    48. '''
    49. self.__engine.setProperty('rate', self.__rate)
    50. self.__engine.setProperty('volume', self.__volume)
    51. # 获取可用语音列表,并设置语音
    52. voices = self.__engine.getProperty('voices')
    53. self.__engine.setProperty('voice', voices[self.__voice].id)
    54. # 保存语音文件
    55. # self.__engine.save_to_file(text, 'test.mp3')
    56. self.__engine.say(text)
    57. self.__engine.runAndWait()
    58. self.__engine.stop()

    窗体类

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

    1. import sys
    2. import _thread as th
    3. from PyQt5.QtWidgets import QMainWindow, QApplication
    4. from Ui_tts_form import Ui_Form
    5. class MainWindow(QMainWindow, Ui_Form):
    6. '''
    7. 窗体类
    8. '''
    9. def __init__(self, parent=None):
    10. '''
    11. 初始化窗体
    12. '''
    13. super(MainWindow, self).__init__(parent)
    14. self.setupUi(self)
    15. # 获取tts工具类实例
    16. self.engine = VoiceEngine()
    17. self.__isPlaying = False
    18. # 设置初始文本
    19. self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
    20. # 进度条数据绑定到label中显示
    21. self.slider_rate.valueChanged.connect(self.setRateTextValue)
    22. self.slider_volumn.valueChanged.connect(self.setVolumnTextValue)
    23. # 设置进度条初始值
    24. self.slider_rate.setValue(self.engine.Rate)
    25. self.slider_volumn.setValue(self.engine.Volume)
    26. # RadioButton选择事件
    27. self.rbtn_zh.toggled.connect(self.onSelectVoice_zh)
    28. self.rbtn_en.toggled.connect(self.onSelectVoice_en)
    29. # 播放按钮点击事件
    30. self.btn_play.clicked.connect(self.onPlayButtonClick)
    31. def setRateTextValue(self):
    32. '''
    33. 修改语速label文本值
    34. '''
    35. value = self.slider_rate.value()
    36. self.label_rate.setText(str(value))
    37. self.engine.Rate = value
    38. def setVolumnTextValue(self):
    39. '''
    40. 修改音量label文本值
    41. '''
    42. value = self.slider_volumn.value()
    43. self.label_volumn.setText(str(value / 100))
    44. self.engine.Volume = value
    45. def onSelectVoice_zh(self):
    46. '''
    47. 修改中文的语音配置及默认播放文本
    48. '''
    49. self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
    50. self.engine.VoiceID = 0
    51. def onSelectVoice_en(self):
    52. '''
    53. 修改英文的语音配置及默认的播放文本
    54. '''
    55. self.tbx_text.setText('Hello World')
    56. self.engine.VoiceID = 1
    57. def playVoice(self):
    58. '''
    59. 播放
    60. '''
    61. if self.__isPlaying is not True:
    62. self.__isPlaying = True
    63. text = self.tbx_text.toPlainText()
    64. self.engine.Say(text)
    65. self.__isPlaying = False
    66. def onPlayButtonClick(self):
    67. '''
    68. 播放按钮点击事件
    69. 开启线程新线程播放语音,避免窗体因为语音播放而假卡死
    70. '''
    71. th.start_new_thread(self.playVoice, ())

    完整代码

    1. import sys
    2. import _thread as th
    3. from PyQt5.QtWidgets import QMainWindow, QApplication
    4. from Ui_tts_form import Ui_Form
    5. import pyttsx3
    6. class VoiceEngine():
    7. '''
    8. tts 语音工具类
    9. '''
    10. def __init__(self):
    11. '''
    12. 初始化
    13. '''
    14. # tts对象
    15. self.__engine = pyttsx3.init()
    16. # 语速
    17. self.__rate = 150
    18. # 音量
    19. self.__volume = 100
    20. # 语音ID,0为中文,1为英文
    21. self.__voice = 0
    22. @property
    23. def Rate(self):
    24. '''
    25. 语速属性
    26. '''
    27. return self.__rate
    28. @Rate.setter
    29. def Rate(self, value):
    30. self.__rate = value
    31. @property
    32. def Volume(self):
    33. '''
    34. 音量属性
    35. '''
    36. return self.__volume
    37. @Volume.setter
    38. def Volume(self, value):
    39. self.__volume = value
    40. @property
    41. def VoiceID(self):
    42. '''
    43. 语音ID:0 -- 中文;1 -- 英文
    44. '''
    45. return self.__voice
    46. @VoiceID.setter
    47. def VoiceID(self, value):
    48. self.__voice = value
    49. def Say(self, text):
    50. '''
    51. 播放语音
    52. '''
    53. self.__engine.setProperty('rate', self.__rate)
    54. self.__engine.setProperty('volume', self.__volume)
    55. voices = self.__engine.getProperty('voices')
    56. self.__engine.setProperty('voice', voices[self.__voice])
    57. # 保存语音文件
    58. # self.__engine.save_to_file(text, 'test.mp3')
    59. self.__engine.say(text)
    60. self.__engine.runAndWait()
    61. self.__engine.stop()
    62. class MainWindow(QMainWindow, Ui_Form):
    63. '''
    64. 窗体类
    65. '''
    66. def __init__(self, parent=None):
    67. '''
    68. 初始化窗体
    69. '''
    70. super(MainWindow, self).__init__(parent)
    71. self.setupUi(self)
    72. # 获取tts工具类实例
    73. self.engine = VoiceEngine()
    74. self.__isPlaying = False
    75. # 设置初始文本
    76. self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
    77. # 进度条数据绑定到label中显示
    78. self.slider_rate.valueChanged.connect(self.setRateTextValue)
    79. self.slider_volumn.valueChanged.connect(self.setVolumnTextValue)
    80. # 设置进度条初始值
    81. self.slider_rate.setValue(self.engine.Rate)
    82. self.slider_volumn.setValue(self.engine.Volume)
    83. # RadioButton选择事件
    84. self.rbtn_zh.toggled.connect(self.onSelectVoice_zh)
    85. self.rbtn_en.toggled.connect(self.onSelectVoice_en)
    86. # 播放按钮点击事件
    87. self.btn_play.clicked.connect(self.onPlayButtonClick)
    88. def setRateTextValue(self):
    89. '''
    90. 修改语速label文本值
    91. '''
    92. value = self.slider_rate.value()
    93. self.label_rate.setText(str(value))
    94. self.engine.Rate = value
    95. def setVolumnTextValue(self):
    96. '''
    97. 修改音量label文本值
    98. '''
    99. value = self.slider_volumn.value()
    100. self.label_volumn.setText(str(value / 100))
    101. self.engine.Volume = value
    102. def onSelectVoice_zh(self):
    103. '''
    104. 修改中文的语音配置及默认播放文本
    105. '''
    106. self.tbx_text.setText('床前明月光,疑似地上霜。\n举头望明月,低头思故乡。')
    107. self.engine.VoiceID = 0
    108. def onSelectVoice_en(self):
    109. '''
    110. 修改英文的语音配置及默认的播放文本
    111. '''
    112. self.tbx_text.setText('Hello World')
    113. self.engine.VoiceID = 1
    114. def playVoice(self):
    115. '''
    116. 播放
    117. '''
    118. if self.__isPlaying is not True:
    119. self.__isPlaying = True
    120. text = self.tbx_text.toPlainText()
    121. self.engine.Say(text)
    122. self.__isPlaying = False
    123. def onPlayButtonClick(self):
    124. '''
    125. 修改语速label文本值
    126. '''
    127. th.start_new_thread(self.playVoice, ())
    128. if __name__ == "__main__":
    129. '''
    130. 主函数
    131. '''
    132. app = QApplication(sys.argv)
    133. form = MainWindow()
    134. form.show()
    135. sys.exit(app.exec_())

  • 相关阅读:
    ヾ(⌐ ■_■)— HTML-CSS选择器
    12、Python -- if 分支 的讲解和使用
    文心一言 VS 讯飞星火 VS chatgpt (119)-- 算法导论10.3 4题
    Project joee 算法开发日志(一)
    JVM调优(一)之性能优化步骤
    MYSQL的存储过程
    [附源码]Python计算机毕业设计Django勤工俭学管理小程序
    基于Django+Vue开发的社区疫情管理系统(附源码)
    JSTL和EL详细笔记
    编程在生活中的小应用
  • 原文地址:https://blog.csdn.net/XUMENGCAS/article/details/128108557