Hello Speak#
The Hello Speak example reads out user-provided text.
The Hello Speak example demonstrates how QTextToSpeech can be used in a Qt C++ application to read out text, and to control the speech.
The example uses a widget UI to provide controls for the pitch, volume, and rate of the speech. It also lets the user select an engine, the language, and a voice.
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 port of the hello_speak example from Qt v6.x"""
import sys
from PySide6.QtCore import QLoggingCategory
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
if __name__ == "__main__":
QLoggingCategory.setFilterRules("qt.speech.tts=true\nqt.speech.tts.*=true")
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from PySide6.QtCore import QLocale, QSignalBlocker, Slot
from PySide6.QtWidgets import QMainWindow
from PySide6.QtTextToSpeech import QTextToSpeech, QVoice
from ui_mainwindow import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self._speech = None
self._voices = []
self._ui = Ui_MainWindow()
self._ui.setupUi(self)
# Populate engine selection list
self._ui.engine.addItem("Default", "default")
engines = QTextToSpeech.availableEngines()
for engine in engines:
self._ui.engine.addItem(engine, engine)
self._ui.engine.setCurrentIndex(0)
self.engine_selected(0)
self._ui.pitch.valueChanged.connect(self.set_pitch)
self._ui.rate.valueChanged.connect(self.set_rate)
self._ui.volume.valueChanged.connect(self.set_volume)
self._ui.engine.currentIndexChanged.connect(self.engine_selected)
self._ui.voice.currentIndexChanged.connect(self.voice_selected)
self._ui.language.currentIndexChanged.connect(self.language_selected)
@Slot(int)
def set_rate(self, rate):
self._speech.setRate(rate / 10.0)
@Slot(int)
def set_pitch(self, pitch):
self._speech.setPitch(pitch / 10.0)
@Slot(int)
def set_volume(self, volume):
self._speech.setVolume(volume / 100.0)
@Slot(QTextToSpeech.State)
def state_changed(self, state):
if state == QTextToSpeech.Speaking:
self._ui.statusbar.showMessage("Speech started...")
elif state == QTextToSpeech.Ready:
self._ui.statusbar.showMessage("Speech stopped...", 2000)
elif state == QTextToSpeech.Paused:
self._ui.statusbar.showMessage("Speech paused...")
else:
self._ui.statusbar.showMessage("Speech error!")
self._ui.pauseButton.setEnabled(state == QTextToSpeech.Speaking)
self._ui.resumeButton.setEnabled(state == QTextToSpeech.Paused)
can_stop = state == QTextToSpeech.Speaking or state == QTextToSpeech.Paused
self._ui.stopButton.setEnabled(can_stop)
@Slot(int)
def engine_selected(self, index):
engine_name = self._ui.engine.itemData(index)
self._speech = None
self._speech = (QTextToSpeech(self) if engine_name == "default"
else QTextToSpeech(engine_name, self))
# Block signals of the languages combobox while populating
current = self._speech.locale()
with QSignalBlocker(self._ui.language):
self._ui.language.clear()
# Populate the languages combobox before connecting its signal.
locales = self._speech.availableLocales()
for locale in locales:
lang = QLocale.languageToString(locale.language())
territory = QLocale.territoryToString(locale.territory())
self._ui.language.addItem(f"{lang} ({territory})", locale)
if locale.name() == current.name():
current = locale
self.set_rate(self._ui.rate.value())
self.set_pitch(self._ui.pitch.value())
self.set_volume(self._ui.volume.value())
self._ui.speakButton.clicked.connect(self.speak_text)
self._ui.stopButton.clicked.connect(self.stop_speaking)
self._ui.pauseButton.clicked.connect(self.pause_speaking)
self._ui.resumeButton.clicked.connect(self._speech.resume)
self._speech.stateChanged.connect(self.state_changed)
self._speech.localeChanged.connect(self.locale_changed)
self.locale_changed(current)
@Slot()
def speak_text(self):
self._speech.say(self._ui.plainTextEdit.toPlainText())
@Slot()
def stop_speaking(self):
self._speech.stop()
@Slot()
def pause_speaking(self):
self._speech.pause()
@Slot(int)
def language_selected(self, language):
locale = self._ui.language.itemData(language)
self._speech.setLocale(locale)
@Slot(int)
def voice_selected(self, index):
self._speech.setVoice(self._voices[index])
@Slot(QLocale)
def locale_changed(self, locale):
self._ui.language.setCurrentIndex(self._ui.language.findData(locale))
with QSignalBlocker(self._ui.voice):
self._ui.voice.clear()
self._voices = self._speech.availableVoices()
current_voice = self._speech.voice()
for voice in self._voices:
name = voice.name()
gender = QVoice.genderName(voice.gender())
age = QVoice.ageName(voice.age())
self._ui.voice.addItem(f"{name} - {gender} - {age}")
if voice.name() == current_voice.name():
self._ui.voice.setCurrentIndex(self._ui.voice.count() - 1)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>551</width>
<height>448</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="plainText">
<string>Hello QtTextToSpeech,
this is an example text in English.
QtSpeech is a library that makes text to speech easy with Qt.
Done, over and out.</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Engine</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Pitch:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&Language:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>language</cstring>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSlider" name="pitch">
<property name="minimum">
<number>-10</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Voice name:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSlider" name="volume">
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="pageStep">
<number>20</number>
</property>
<property name="value">
<number>70</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QComboBox" name="language">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QComboBox" name="voice"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Rate:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Volume:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSlider" name="rate">
<property name="minimum">
<number>-10</number>
</property>
<property name="maximum">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QComboBox" name="engine">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="speakButton">
<property name="text">
<string>Speak</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pauseButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Pause</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resumeButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Resume</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopButton">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<tabstops>
<tabstop>plainTextEdit</tabstop>
<tabstop>speakButton</tabstop>
<tabstop>pauseButton</tabstop>
<tabstop>resumeButton</tabstop>
<tabstop>stopButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>