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.

Hello Speak Screenshot

Download this example

# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations

"""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 __future__ import annotations

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::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::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::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::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>&amp;Language:</string>
        </property>
        <property name="alignment">
         <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::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::Orientation::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::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::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::Orientation::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::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::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::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::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::Orientation::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::Orientation::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>