From 86331d3579706af7c15b9c0cae6b324824c5ab91 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Mon, 20 Mar 2023 13:15:28 +0100 Subject: Re-port the hello_speak example The UI has been extended since the last port. On this occasion, fix the example folder to be in line with Qt. Task-number: PYSIDE-2206 Change-Id: I5b6e73375ae7b81adb387822d1da5eaf66ffcf9c Reviewed-by: Cristian Maureira-Fredes --- examples/speech/hello_speak/doc/hello_speak.rst | 16 ++ examples/speech/hello_speak/doc/hello_speak.webp | Bin 0 -> 25432 bytes examples/speech/hello_speak/hello_speak.pyproject | 5 + examples/speech/hello_speak/main.py | 20 ++ examples/speech/hello_speak/mainwindow.py | 134 +++++++++++ examples/speech/hello_speak/mainwindow.ui | 267 +++++++++++++++++++++ examples/speech/hello_speak/ui_mainwindow.py | 211 ++++++++++++++++ examples/texttospeech/hello_speak/hello_speak.py | 73 ------ .../texttospeech/hello_speak/hello_speak.pyproject | 3 - 9 files changed, 653 insertions(+), 76 deletions(-) create mode 100644 examples/speech/hello_speak/doc/hello_speak.rst create mode 100644 examples/speech/hello_speak/doc/hello_speak.webp create mode 100644 examples/speech/hello_speak/hello_speak.pyproject create mode 100644 examples/speech/hello_speak/main.py create mode 100644 examples/speech/hello_speak/mainwindow.py create mode 100644 examples/speech/hello_speak/mainwindow.ui create mode 100644 examples/speech/hello_speak/ui_mainwindow.py delete mode 100644 examples/texttospeech/hello_speak/hello_speak.py delete mode 100644 examples/texttospeech/hello_speak/hello_speak.pyproject diff --git a/examples/speech/hello_speak/doc/hello_speak.rst b/examples/speech/hello_speak/doc/hello_speak.rst new file mode 100644 index 000000000..b7c17c35f --- /dev/null +++ b/examples/speech/hello_speak/doc/hello_speak.rst @@ -0,0 +1,16 @@ +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. + +.. image:: hello_speak.webp + :width: 400 + :alt: Hello Speak Screenshot + diff --git a/examples/speech/hello_speak/doc/hello_speak.webp b/examples/speech/hello_speak/doc/hello_speak.webp new file mode 100644 index 000000000..c378d1a4b Binary files /dev/null and b/examples/speech/hello_speak/doc/hello_speak.webp differ diff --git a/examples/speech/hello_speak/hello_speak.pyproject b/examples/speech/hello_speak/hello_speak.pyproject new file mode 100644 index 000000000..0cefc7531 --- /dev/null +++ b/examples/speech/hello_speak/hello_speak.pyproject @@ -0,0 +1,5 @@ +{ + "files": ["main.py", + "mainwindow.py", + "mainwindow.ui"] +} diff --git a/examples/speech/hello_speak/main.py b/examples/speech/hello_speak/main.py new file mode 100644 index 000000000..b025dd6c0 --- /dev/null +++ b/examples/speech/hello_speak/main.py @@ -0,0 +1,20 @@ +# 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()) diff --git a/examples/speech/hello_speak/mainwindow.py b/examples/speech/hello_speak/mainwindow.py new file mode 100644 index 000000000..7790fec8c --- /dev/null +++ b/examples/speech/hello_speak/mainwindow.py @@ -0,0 +1,134 @@ +# 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) diff --git a/examples/speech/hello_speak/mainwindow.ui b/examples/speech/hello_speak/mainwindow.ui new file mode 100644 index 000000000..6f3accf6c --- /dev/null +++ b/examples/speech/hello_speak/mainwindow.ui @@ -0,0 +1,267 @@ + + + MainWindow + + + + 0 + 0 + 551 + 448 + + + + MainWindow + + + + + + + + 0 + 0 + + + + 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. + + + + + + + + + + 0 + 0 + + + + Engine + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Pitch: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + &Language: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + language + + + + + + + -10 + + + 10 + + + 1 + + + Qt::Horizontal + + + + + + + Voice name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 100 + + + 5 + + + 20 + + + 70 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Rate: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + Volume: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + -10 + + + 10 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + + + + + Speak + + + + + + + false + + + Pause + + + + + + + false + + + Resume + + + + + + + Stop + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + plainTextEdit + speakButton + pauseButton + resumeButton + stopButton + + + + diff --git a/examples/speech/hello_speak/ui_mainwindow.py b/examples/speech/hello_speak/ui_mainwindow.py new file mode 100644 index 000000000..b42d35f03 --- /dev/null +++ b/examples/speech/hello_speak/ui_mainwindow.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'mainwindow.ui' +## +## Created by: Qt User Interface Compiler version 6.5.0 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QComboBox, QGridLayout, QHBoxLayout, + QLabel, QMainWindow, QPlainTextEdit, QPushButton, + QSizePolicy, QSlider, QSpacerItem, QStatusBar, + QVBoxLayout, QWidget) + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.resize(551, 448) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.verticalLayout = QVBoxLayout(self.centralwidget) + self.verticalLayout.setObjectName(u"verticalLayout") + self.plainTextEdit = QPlainTextEdit(self.centralwidget) + self.plainTextEdit.setObjectName(u"plainTextEdit") + sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth()) + self.plainTextEdit.setSizePolicy(sizePolicy) + + self.verticalLayout.addWidget(self.plainTextEdit) + + self.gridLayout = QGridLayout() + self.gridLayout.setObjectName(u"gridLayout") + self.label_5 = QLabel(self.centralwidget) + self.label_5.setObjectName(u"label_5") + sizePolicy1 = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth()) + self.label_5.setSizePolicy(sizePolicy1) + self.label_5.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_5, 4, 0, 1, 1) + + self.label_3 = QLabel(self.centralwidget) + self.label_3.setObjectName(u"label_3") + sizePolicy1.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) + self.label_3.setSizePolicy(sizePolicy1) + self.label_3.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1) + + self.label_4 = QLabel(self.centralwidget) + self.label_4.setObjectName(u"label_4") + sizePolicy1.setHeightForWidth(self.label_4.sizePolicy().hasHeightForWidth()) + self.label_4.setSizePolicy(sizePolicy1) + self.label_4.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_4, 5, 0, 1, 1) + + self.pitch = QSlider(self.centralwidget) + self.pitch.setObjectName(u"pitch") + self.pitch.setMinimum(-10) + self.pitch.setMaximum(10) + self.pitch.setSingleStep(1) + self.pitch.setOrientation(Qt.Horizontal) + + self.gridLayout.addWidget(self.pitch, 3, 2, 1, 1) + + self.label_6 = QLabel(self.centralwidget) + self.label_6.setObjectName(u"label_6") + self.label_6.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_6, 6, 0, 1, 1) + + self.volume = QSlider(self.centralwidget) + self.volume.setObjectName(u"volume") + self.volume.setMaximum(100) + self.volume.setSingleStep(5) + self.volume.setPageStep(20) + self.volume.setValue(70) + self.volume.setOrientation(Qt.Horizontal) + + self.gridLayout.addWidget(self.volume, 1, 2, 1, 1) + + self.language = QComboBox(self.centralwidget) + self.language.setObjectName(u"language") + sizePolicy2 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + sizePolicy2.setHorizontalStretch(0) + sizePolicy2.setVerticalStretch(0) + sizePolicy2.setHeightForWidth(self.language.sizePolicy().hasHeightForWidth()) + self.language.setSizePolicy(sizePolicy2) + + self.gridLayout.addWidget(self.language, 5, 2, 1, 1) + + self.voice = QComboBox(self.centralwidget) + self.voice.setObjectName(u"voice") + + self.gridLayout.addWidget(self.voice, 6, 2, 1, 1) + + self.label = QLabel(self.centralwidget) + self.label.setObjectName(u"label") + sizePolicy1.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy1) + self.label.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label, 2, 0, 1, 1) + + self.label_2 = QLabel(self.centralwidget) + self.label_2.setObjectName(u"label_2") + sizePolicy1.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) + self.label_2.setSizePolicy(sizePolicy1) + self.label_2.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + + self.rate = QSlider(self.centralwidget) + self.rate.setObjectName(u"rate") + self.rate.setMinimum(-10) + self.rate.setMaximum(10) + self.rate.setOrientation(Qt.Horizontal) + + self.gridLayout.addWidget(self.rate, 2, 2, 1, 1) + + self.engine = QComboBox(self.centralwidget) + self.engine.setObjectName(u"engine") + sizePolicy2.setHeightForWidth(self.engine.sizePolicy().hasHeightForWidth()) + self.engine.setSizePolicy(sizePolicy2) + + self.gridLayout.addWidget(self.engine, 4, 2, 1, 1) + + + self.verticalLayout.addLayout(self.gridLayout) + + self.horizontalLayout = QHBoxLayout() + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.speakButton = QPushButton(self.centralwidget) + self.speakButton.setObjectName(u"speakButton") + + self.horizontalLayout.addWidget(self.speakButton) + + self.pauseButton = QPushButton(self.centralwidget) + self.pauseButton.setObjectName(u"pauseButton") + self.pauseButton.setEnabled(False) + + self.horizontalLayout.addWidget(self.pauseButton) + + self.resumeButton = QPushButton(self.centralwidget) + self.resumeButton.setObjectName(u"resumeButton") + self.resumeButton.setEnabled(False) + + self.horizontalLayout.addWidget(self.resumeButton) + + self.stopButton = QPushButton(self.centralwidget) + self.stopButton.setObjectName(u"stopButton") + + self.horizontalLayout.addWidget(self.stopButton) + + + self.verticalLayout.addLayout(self.horizontalLayout) + + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + + self.verticalLayout.addItem(self.verticalSpacer) + + MainWindow.setCentralWidget(self.centralwidget) + self.statusbar = QStatusBar(MainWindow) + self.statusbar.setObjectName(u"statusbar") + MainWindow.setStatusBar(self.statusbar) +#if QT_CONFIG(shortcut) + self.label_4.setBuddy(self.language) +#endif // QT_CONFIG(shortcut) + QWidget.setTabOrder(self.plainTextEdit, self.speakButton) + QWidget.setTabOrder(self.speakButton, self.pauseButton) + QWidget.setTabOrder(self.pauseButton, self.resumeButton) + QWidget.setTabOrder(self.resumeButton, self.stopButton) + + self.retranslateUi(MainWindow) + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) + self.plainTextEdit.setPlainText(QCoreApplication.translate("MainWindow", u"Hello QtTextToSpeech,\n" +"this is an example text in English.\n" +"\n" +"QtSpeech is a library that makes text to speech easy with Qt.\n" +"Done, over and out.", None)) + self.label_5.setText(QCoreApplication.translate("MainWindow", u"Engine", None)) + self.label_3.setText(QCoreApplication.translate("MainWindow", u"Pitch:", None)) + self.label_4.setText(QCoreApplication.translate("MainWindow", u"&Language:", None)) + self.label_6.setText(QCoreApplication.translate("MainWindow", u"Voice name:", None)) + self.label.setText(QCoreApplication.translate("MainWindow", u"Rate:", None)) + self.label_2.setText(QCoreApplication.translate("MainWindow", u"Volume:", None)) + self.speakButton.setText(QCoreApplication.translate("MainWindow", u"Speak", None)) + self.pauseButton.setText(QCoreApplication.translate("MainWindow", u"Pause", None)) + self.resumeButton.setText(QCoreApplication.translate("MainWindow", u"Resume", None)) + self.stopButton.setText(QCoreApplication.translate("MainWindow", u"Stop", None)) + # retranslateUi + diff --git a/examples/texttospeech/hello_speak/hello_speak.py b/examples/texttospeech/hello_speak/hello_speak.py deleted file mode 100644 index d7612f362..000000000 --- a/examples/texttospeech/hello_speak/hello_speak.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -"""PySide6 QTextToSpeech example""" - -import sys -from PySide6.QtCore import Qt, Slot -from PySide6.QtWidgets import (QApplication, QComboBox, QFormLayout, - QHBoxLayout, QLineEdit, QMainWindow, QPushButton, QSlider, QWidget) - -from PySide6.QtTextToSpeech import QTextToSpeech - - -class MainWindow(QMainWindow): - def __init__(self): - super().__init__() - - centralWidget = QWidget() - self.setCentralWidget(centralWidget) - layout = QFormLayout(centralWidget) - - textLayout = QHBoxLayout() - self.text = QLineEdit('Hello, PySide6') - self.text.setClearButtonEnabled(True) - textLayout.addWidget(self.text) - self.sayButton = QPushButton('Say') - textLayout.addWidget(self.sayButton) - self.text.returnPressed.connect(self.sayButton.animateClick) - self.sayButton.clicked.connect(self.say) - layout.addRow('Text:', textLayout) - - self.voiceCombo = QComboBox() - layout.addRow('Voice:', self.voiceCombo) - - self.volumeSlider = QSlider(Qt.Horizontal) - self.volumeSlider.setMinimum(0) - self.volumeSlider.setMaximum(100) - self.volumeSlider.setValue(100) - layout.addRow('Volume:', self.volumeSlider) - - self.engine = None - engineNames = QTextToSpeech.availableEngines() - if len(engineNames) > 0: - engineName = engineNames[0] - self.engine = QTextToSpeech(engineName) - self.engine.stateChanged.connect(self.stateChanged) - self.setWindowTitle(f'QTextToSpeech Example ({engineName})') - self.voices = [] - for voice in self.engine.availableVoices(): - self.voices.append(voice) - self.voiceCombo.addItem(voice.name()) - else: - self.setWindowTitle('QTextToSpeech Example (no engines available)') - self.sayButton.setEnabled(False) - - @Slot() - def say(self): - self.sayButton.setEnabled(False) - self.engine.setVoice(self.voices[self.voiceCombo.currentIndex()]) - self.engine.setVolume(float(self.volumeSlider.value()) / 100) - self.engine.say(self.text.text()) - - @Slot("QTextToSpeech::State") - def stateChanged(self, state): - if (state == QTextToSpeech.State.Ready): - self.sayButton.setEnabled(True) - - -if __name__ == '__main__': - app = QApplication(sys.argv) - mainWin = MainWindow() - mainWin.show() - sys.exit(app.exec()) diff --git a/examples/texttospeech/hello_speak/hello_speak.pyproject b/examples/texttospeech/hello_speak/hello_speak.pyproject deleted file mode 100644 index 15b9b8529..000000000 --- a/examples/texttospeech/hello_speak/hello_speak.pyproject +++ /dev/null @@ -1,3 +0,0 @@ -{ - "files": ["hello_speak.py"] -} -- cgit v1.2.3