-
Notifications
You must be signed in to change notification settings - Fork 102
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
262c3c1
commit 8ef6933
Showing
21 changed files
with
840 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,4 +57,8 @@ GoldenDict_resource.rc | |
*.TMP | ||
*.orig | ||
|
||
node_modules | ||
node_modules | ||
|
||
# tts testing files | ||
*.ogg | ||
*.mp3 |
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,59 @@ | ||
# Cloud TTS | ||
|
||
## Add a new service checklist | ||
|
||
* Read to `service.h`. | ||
* Implement `Service::speak`. | ||
* Implement `Service::stop`. | ||
* Implement `ServiceConfigWidget`+`ServiceConfigWidget::save`. | ||
* 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. | ||
|
||
(Conversely, old services won't be obstacles of modifying currently maintained services.) | ||
|
||
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 file serialization and Service state mutating will not happen in parallel or successively. | ||
* Service construct only based on config file. One single deterministic routine. | ||
|
||
(1) will neither mutate nor access (3). | ||
|
||
construct (3) only according to (2). | ||
|
||
(1) only write to (2). | ||
|
||
### Object management | ||
|
||
* Service construction will be done on the service consumer side to avoid sharing constructor. | ||
* Service can be cast to `Service`, which only has `speak/stop` and destructor. The service consumer should not care | ||
anything else after construction. | ||
* Services don't share global config, don't share config mechanisms | ||
|
||
### 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. | ||
|
||
## Rational | ||
|
||
* Services are different. | ||
* Testing services are hard (cloud tts usually needs an account). | ||
* Abstracting seemingly similar but different things will eventually blunder. | ||
* Do not assume services have any similarity other than the fact they may `speak`. | ||
* Construction materials needed by services are different. | ||
* 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. |
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,157 @@ | ||
#include "tts/config_window.hh" | ||
#include "tts/services/azure.hh" | ||
#include "tts/services/dummy.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( "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( Azure::Service::Construct( this->configRootDir ) ); | ||
} | ||
else { | ||
previewService.reset( new dummy::Service() ); | ||
} | ||
|
||
if ( previewService != nullptr ) { | ||
auto _ = 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 Azure::ConfigWidget( this, this->configRootDir ) ); | ||
} | ||
else { | ||
serviceConfigUI.reset( new dummy::ConfigWidget( 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; | ||
|
||
QScopedPointer< TTS::Service > previewService; | ||
QScopedPointer< 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,49 @@ | ||
#pragma once | ||
|
||
#include <QWidget> | ||
#include <optional> | ||
|
||
/* | ||
* | ||
* Do not add anything new to this header to ensure maximum decouping between different services. | ||
* | ||
* Things needed by Services, should be added to specific services. | ||
* | ||
* If something is needed by multiple services, | ||
* it should be implemented as a component that | ||
* can be plugged into needed services. | ||
* | ||
* Consider other options before modifying this file. | ||
* | ||
*/ | ||
|
||
namespace TTS { | ||
class Service: public QObject | ||
{ | ||
Q_OBJECT | ||
public slots: | ||
/// | ||
/// @return If failed, return a string that contains Error message. | ||
[[nodiscard]] virtual std::optional< std::string > speak( QUtf8StringView s ) noexcept | ||
{ | ||
return {}; | ||
} | ||
virtual void stop() noexcept {} // TODO: does here need error handling? | ||
}; | ||
|
||
class ServiceConfigWidget: public QWidget | ||
{ | ||
public: | ||
explicit ServiceConfigWidget( QWidget * parent ): | ||
QWidget( parent ) | ||
{ | ||
} | ||
|
||
/// Ask the service to save it's config. | ||
/// @return if failed, return a string that contains Error message. | ||
virtual std::optional< std::string > save() noexcept | ||
{ | ||
return {}; | ||
} | ||
}; | ||
} // namespace TTS |
Oops, something went wrong.