-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ea4f45a
commit 8e41da4
Showing
24 changed files
with
1,059 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#pragma once | ||
|
||
#include <QNetworkAccessManager> | ||
Q_APPLICATION_STATIC( QNetworkAccessManager, globalNetworkAccessManager ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Cloud TTS | ||
|
||
## Add a new service checklist | ||
|
||
* Read `service.h`. | ||
* Implement `Service::speak`. | ||
* Implement `Service::stop`. | ||
* Implement `ServiceConfigWidget`, which will be embedded in `ConfigWindow`. | ||
* Add the `Service` to `ServiceController`. | ||
* Add the `ServiceConfigWidget` to `ConfigWindow`. | ||
* DONE. | ||
|
||
## Design Goals | ||
|
||
Allow modifying / evolving any one of the services arbitrarily without incurring the need to touch another. | ||
|
||
Avoid almost all temptation to do 💩 abstraction 💩 unless absolutely necessary. | ||
|
||
## Code | ||
|
||
### Config | ||
|
||
``` | ||
(1) Service ConfigWidet --write--> (2) Service's config file --create--> (3) Live Service Object | ||
``` | ||
|
||
* Config Serialization+Saving and Service state mutating will not happen in parallel or successively. | ||
* (1) will neither mutate nor access (3). | ||
* construct (3) only according to (2). | ||
|
||
### Object management | ||
|
||
* Service construction will be done on the service consumer side | ||
* Service can be cast to `Service`, which only has `speak/stop` and destructor. | ||
* The service consumer should not care | ||
anything else after construction. | ||
|
||
### Config Window | ||
|
||
Similar to KDE's Settings module (KCM). | ||
Every service simply provides a config widget on its own, and the config window simply loads the Widget. | ||
|
||
### No exception | ||
|
||
* Handle errors religiously and immediately, and report to users if user attention/action is required. | ||
|
||
## Rational | ||
|
||
* Services are different and testing them is hard (cloud tts usually needs an account). | ||
* Do not assume services have any similarity other than the fact they may `speak`. | ||
* Services on earth are limited, thus the boilerplate caused by fewer useless abstractions is also limited. | ||
* The service consumer will use services incredibly and insanely creative in the future. | ||
* Maintaining two code paths of object creation & mutating is a waste of time. | ||
* Just save config to disk, and construct objects according to what's in the disk. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
#include "config_file_main.hh" | ||
|
||
#include <QFileInfo> | ||
#include <QSaveFile> | ||
|
||
|
||
namespace TTS { | ||
|
||
auto current_service_txt = "current_service.txt"; | ||
|
||
QString get_service_name_from_path( const QDir & configPath ) | ||
{ | ||
qDebug() << configPath; | ||
if ( !QFileInfo::exists( configPath.absoluteFilePath( current_service_txt ) ) ) { | ||
save_service_name_to_path( configPath, "azure" ); | ||
} | ||
QFile f( configPath.filePath( current_service_txt ) ); | ||
if ( !f.open( QFile::ReadOnly ) ) { | ||
throw std::runtime_error( "cannot open service name" ); // TODO | ||
} | ||
QString ret = f.readAll(); | ||
f.close(); | ||
return ret; | ||
} | ||
|
||
void save_service_name_to_path( const QDir & configPath, QUtf8StringView serviceName ) | ||
{ | ||
QSaveFile f( configPath.absoluteFilePath( current_service_txt ) ); | ||
if ( !f.open( QFile::WriteOnly ) ) { | ||
throw std::runtime_error( "Cannot write service name" ); | ||
} | ||
f.write( serviceName.data(), serviceName.length() ); | ||
f.commit(); | ||
}; | ||
} // namespace TTS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#pragma once | ||
#include <QDir> | ||
|
||
namespace TTS { | ||
QString get_service_name_from_path( const QDir & configRootPath ); | ||
void save_service_name_to_path( const QDir & configPath, QUtf8StringView serviceName ); | ||
} // namespace TTS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
#include "tts/config_window.hh" | ||
#include "tts/services/azure.hh" | ||
#include "tts/services/dummy.hh" | ||
#include "tts/services/local_command.hh" | ||
#include "tts/config_file_main.hh" | ||
|
||
#include <QDialogButtonBox> | ||
#include <QGridLayout> | ||
#include <QGroupBox> | ||
#include <QLabel> | ||
#include <QPushButton> | ||
#include <QLineEdit> | ||
|
||
#include <QStringLiteral> | ||
|
||
namespace TTS { | ||
|
||
//TODO: split preview pane to a seprate file. | ||
void ConfigWindow::setupUi() | ||
{ | ||
setWindowTitle( "Service Config" ); | ||
this->setAttribute( Qt::WA_DeleteOnClose ); | ||
this->setWindowModality( Qt::WindowModal ); | ||
this->setWindowFlag( Qt::Dialog ); | ||
|
||
MainLayout = new QGridLayout( this ); | ||
|
||
configPane = new QGroupBox( "Service Config", this ); | ||
auto * previewPane = new QGroupBox( "Audio Preview", this ); | ||
|
||
configPane->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); | ||
previewPane->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding ); | ||
|
||
configPane->setLayout( new QVBoxLayout() ); | ||
previewPane->setLayout( new QVBoxLayout() ); | ||
|
||
auto * serviceSelectLayout = new QHBoxLayout( nullptr ); | ||
auto * serviceLabel = new QLabel( "Select service", this ); | ||
serviceSelector = new QComboBox(); | ||
serviceSelector->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); | ||
|
||
serviceSelectLayout->addWidget( serviceLabel ); | ||
serviceSelectLayout->addWidget( serviceSelector ); | ||
|
||
previewLineEdit = new QLineEdit( this ); | ||
previewButton = new QPushButton( "Preview", this ); | ||
|
||
previewPane->layout()->addWidget( previewLineEdit ); | ||
previewPane->layout()->addWidget( previewButton ); | ||
qobject_cast< QVBoxLayout * >( previewPane->layout() )->addStretch(); | ||
|
||
buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help, this ); | ||
MainLayout->addLayout( serviceSelectLayout, 0, 0, 1, 2 ); | ||
MainLayout->addWidget( configPane, 1, 0, 1, 1 ); | ||
MainLayout->addWidget( previewPane, 1, 1, 1, 1 ); | ||
MainLayout->addWidget( buttonBox, 2, 0, 1, 2 ); | ||
MainLayout->addWidget( | ||
new QLabel( | ||
R"(<font color="red">Experimental feature. The default API key may stop working at anytime. Feedback & Coding help are welcomed. </font>)", | ||
this ), | ||
3, | ||
0, | ||
1, | ||
2 ); | ||
} | ||
|
||
ConfigWindow::ConfigWindow( QWidget * parent, const QString & configRootPath ): | ||
QWidget( parent, Qt::Window ), | ||
configRootDir( configRootPath ) | ||
{ | ||
configRootDir.mkpath( QStringLiteral( "ctts" ) ); | ||
configRootDir.cd( QStringLiteral( "ctts" ) ); | ||
|
||
|
||
this->setupUi(); | ||
|
||
serviceSelector->addItem( "Azure Text to Speech", QStringLiteral( "azure" ) ); | ||
serviceSelector->addItem( "Local Command Line", QStringLiteral( "local_command" ) ); | ||
serviceSelector->addItem( "Dummy", QStringLiteral( "dummy" ) ); | ||
|
||
|
||
this->currentService = get_service_name_from_path( configRootDir ); | ||
|
||
if ( auto i = serviceSelector->findData( this->currentService ); i != -1 ) { | ||
serviceSelector->setCurrentIndex( i ); | ||
} | ||
|
||
|
||
connect( previewButton, &QPushButton::clicked, this, [ this ] { | ||
this->serviceConfigUI->save(); | ||
|
||
|
||
if ( currentService == "azure" ) { | ||
previewService.reset( TTS::AzureService::Construct( this->configRootDir ) ); | ||
} | ||
else if ( currentService == "local_command" ) { | ||
auto * s = new TTS::LocalCommandService( this->configRootDir ); | ||
s->loadCommandFromConfigFile(); // TODO:: error unhandled. | ||
previewService.reset( s ); | ||
} | ||
else { | ||
previewService.reset( new TTS::DummyService() ); | ||
} | ||
|
||
if ( previewService != nullptr ) { | ||
previewService->speak( previewLineEdit->text().toUtf8() ); | ||
} | ||
else { | ||
exit( 1 ); // TODO | ||
} | ||
} ); | ||
|
||
|
||
updateConfigPaneBasedOnCurrentService(); | ||
|
||
connect( serviceSelector, &QComboBox::currentIndexChanged, this, [ this ] { | ||
updateConfigPaneBasedOnCurrentService(); | ||
} ); | ||
|
||
connect( buttonBox, &QDialogButtonBox::accepted, this, [ this ]() { | ||
qDebug() << "accept"; | ||
this->serviceConfigUI->save(); | ||
save_service_name_to_path( configRootDir, this->serviceSelector->currentData().toByteArray() ); | ||
|
||
emit this->service_changed(); | ||
this->close(); | ||
} ); | ||
|
||
connect( buttonBox, &QDialogButtonBox::rejected, this, [ this ]() { | ||
qDebug() << "rejected"; | ||
this->close(); | ||
} ); | ||
|
||
connect( buttonBox->button( QDialogButtonBox::Help ), &QPushButton::clicked, this, [ this ]() { | ||
qDebug() << "help"; | ||
} ); | ||
} | ||
|
||
|
||
void ConfigWindow::updateConfigPaneBasedOnCurrentService() | ||
{ | ||
if ( serviceSelector->currentData() == "azure" ) { | ||
serviceConfigUI.reset( new TTS::AzureConfigWidget( this, this->configRootDir ) ); | ||
} | ||
else if ( serviceSelector->currentData() == "local_command" ) { | ||
serviceConfigUI.reset( new TTS::LocalCommandConfigWidget( this, this->configRootDir ) ); | ||
} | ||
else { | ||
serviceConfigUI.reset( new TTS::DummyConfigWidget( this ) ); | ||
} | ||
configPane->layout()->addWidget( serviceConfigUI.get() ); | ||
} | ||
} // namespace TTS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#pragma once | ||
|
||
#include "tts/services/azure.hh" | ||
#include <QDialogButtonBox> | ||
#include <QGridLayout> | ||
#include <QGroupBox> | ||
#include <QWidget> | ||
|
||
namespace TTS { | ||
class ConfigWindow: public QWidget | ||
{ | ||
Q_OBJECT | ||
|
||
public: | ||
explicit ConfigWindow( QWidget * parent, const QString & configRootPath ); | ||
|
||
signals: | ||
void service_changed(); | ||
|
||
private: | ||
QGridLayout * MainLayout; | ||
QGroupBox * configPane; | ||
|
||
QDialogButtonBox * buttonBox; | ||
QLineEdit * previewLineEdit; | ||
QPushButton * previewButton; | ||
|
||
QString currentService; | ||
QDir configRootDir; | ||
|
||
QComboBox * serviceSelector; | ||
|
||
std::unique_ptr< TTS::Service > previewService; | ||
std::unique_ptr< TTS::ServiceConfigWidget > serviceConfigUI; | ||
|
||
void setupUi(); | ||
|
||
private slots: | ||
void updateConfigPaneBasedOnCurrentService(); | ||
}; | ||
|
||
} // namespace TTS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Files to test various services. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
POST https://eastus.tts.speech.microsoft.com/cognitiveservices/v1 | ||
|
||
Ocp-Apim-Subscription-Key: b9885138792d4403a8ccf1a34553351d | ||
X-Microsoft-OutputFormat: audio-16khz-64kbitrate-mono-mp3 | ||
Content-Type: application/ssml+xml | ||
User-Agent: WhatEver | ||
<speak version='1.0' xml:lang='en-US'> | ||
<voice name='en-US-LunaNeural'> | ||
hello world | ||
</voice> | ||
</speak> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
GET https://eastus.tts.speech.microsoft.com/cognitiveservices/voices/list | ||
|
||
Ocp-Apim-Subscription-Key: b9885138792d4403a8ccf1a34553351d |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#pragma once | ||
|
||
#include <QMessageBox> | ||
|
||
namespace TTS { | ||
void reportError( const QString & errorString ) | ||
{ | ||
QMessageBox msgBox{}; | ||
msgBox.setText( "Text to speech failed: " % errorString ); | ||
msgBox.setIcon( QMessageBox::Warning ); | ||
msgBox.exec(); | ||
} | ||
} // namespace TTS |
Oops, something went wrong.