| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2015 The Qt Company Ltd. |
| 4 | ** Contact: http://www.qt.io/licensing/ |
| 5 | ** |
| 6 | ** This file is part of the Qt Speech module of the Qt Toolkit. |
| 7 | ** |
| 8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
| 9 | ** Commercial License Usage |
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 11 | ** accordance with the commercial license agreement provided with the |
| 12 | ** Software or, alternatively, in accordance with the terms contained in |
| 13 | ** a written agreement between you and The Qt Company. For licensing terms |
| 14 | ** and conditions see http://www.qt.io/terms-conditions. For further |
| 15 | ** information use the contact form at http://www.qt.io/contact-us. |
| 16 | ** |
| 17 | ** GNU Lesser General Public License Usage |
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 19 | ** General Public License version 3 as published by the Free Software |
| 20 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| 21 | ** packaging of this file. Please review the following information to |
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| 24 | ** |
| 25 | ** GNU General Public License Usage |
| 26 | ** Alternatively, this file may be used under the terms of the GNU |
| 27 | ** General Public License version 2.0 or later as published by the Free |
| 28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
| 29 | ** the packaging of this file. Please review the following information to |
| 30 | ** ensure the GNU General Public License version 2.0 requirements will be |
| 31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
| 32 | ** |
| 33 | ** $QT_END_LICENSE$ |
| 34 | ** |
| 35 | ****************************************************************************/ |
| 36 | |
| 37 | |
| 38 | |
| 39 | #include "qtexttospeech.h" |
| 40 | #include "qtexttospeech_p.h" |
| 41 | |
| 42 | #include <qdebug.h> |
| 43 | |
| 44 | #include <QtCore/private/qfactoryloader_p.h> |
| 45 | |
| 46 | QT_BEGIN_NAMESPACE |
| 47 | |
| 48 | Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader, |
| 49 | ("org.qt-project.qt.speech.tts.plugin/5.0" , |
| 50 | QLatin1String("/texttospeech" ))) |
| 51 | |
| 52 | QMutex QTextToSpeechPrivate::m_mutex; |
| 53 | |
| 54 | QTextToSpeechPrivate::QTextToSpeechPrivate(QTextToSpeech *speech, const QString &engine) |
| 55 | : m_engine(0), |
| 56 | m_speech(speech), |
| 57 | m_providerName(engine), |
| 58 | m_plugin(0) |
| 59 | { |
| 60 | qRegisterMetaType<QTextToSpeech::State>(); |
| 61 | if (m_providerName.isEmpty()) { |
| 62 | m_providerName = QTextToSpeech::availableEngines().value(i: 0); |
| 63 | if (m_providerName.isEmpty()) { |
| 64 | qCritical() << "No text-to-speech plug-ins were found." ; |
| 65 | return; |
| 66 | } |
| 67 | } |
| 68 | if (!loadMeta()) { |
| 69 | qCritical() << "Text-to-speech plug-in" << m_providerName << "is not supported." ; |
| 70 | return; |
| 71 | } |
| 72 | loadPlugin(); |
| 73 | if (m_plugin) { |
| 74 | QString errorString; |
| 75 | m_engine = m_plugin->createTextToSpeechEngine(parameters: QVariantMap(), parent: 0, errorString: &errorString); |
| 76 | if (!m_engine) { |
| 77 | qCritical() << "Error creating text-to-speech engine" << m_providerName |
| 78 | << (errorString.isEmpty() ? QStringLiteral("" ) : (QStringLiteral(": " ) + errorString)); |
| 79 | } |
| 80 | } else { |
| 81 | qCritical() << "Error loading text-to-speech plug-in" << m_providerName; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | QTextToSpeechPrivate::~QTextToSpeechPrivate() |
| 86 | { |
| 87 | m_speech->stop(); |
| 88 | delete m_engine; |
| 89 | } |
| 90 | |
| 91 | bool QTextToSpeechPrivate::loadMeta() |
| 92 | { |
| 93 | m_plugin = 0; |
| 94 | m_metaData = QJsonObject(); |
| 95 | m_metaData.insert(key: QLatin1String("index" ), value: -1); |
| 96 | |
| 97 | QList<QJsonObject> candidates = QTextToSpeechPrivate::plugins().values(akey: m_providerName); |
| 98 | |
| 99 | int versionFound = -1; |
| 100 | int idx = -1; |
| 101 | |
| 102 | // figure out which version of the plugin we want |
| 103 | for (int i = 0; i < candidates.size(); ++i) { |
| 104 | QJsonObject meta = candidates[i]; |
| 105 | if (meta.contains(key: QLatin1String("Version" )) |
| 106 | && meta.value(key: QLatin1String("Version" )).isDouble()) { |
| 107 | int ver = int(meta.value(key: QLatin1String("Version" )).toDouble()); |
| 108 | if (ver > versionFound) { |
| 109 | versionFound = ver; |
| 110 | idx = i; |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | if (idx != -1) { |
| 116 | m_metaData = candidates[idx]; |
| 117 | return true; |
| 118 | } |
| 119 | return false; |
| 120 | } |
| 121 | |
| 122 | void QTextToSpeechPrivate::loadPlugin() |
| 123 | { |
| 124 | if (int(m_metaData.value(key: QLatin1String("index" )).toDouble()) < 0) { |
| 125 | m_plugin = 0; |
| 126 | return; |
| 127 | } |
| 128 | int idx = int(m_metaData.value(key: QLatin1String("index" )).toDouble()); |
| 129 | m_plugin = qobject_cast<QTextToSpeechPlugin *>(object: loader()->instance(index: idx)); |
| 130 | } |
| 131 | |
| 132 | QMultiHash<QString, QJsonObject> QTextToSpeechPrivate::plugins(bool reload) |
| 133 | { |
| 134 | static QMultiHash<QString, QJsonObject> plugins; |
| 135 | static bool alreadyDiscovered = false; |
| 136 | QMutexLocker lock(&m_mutex); |
| 137 | |
| 138 | if (reload == true) |
| 139 | alreadyDiscovered = false; |
| 140 | |
| 141 | if (!alreadyDiscovered) { |
| 142 | loadPluginMetadata(list&: plugins); |
| 143 | alreadyDiscovered = true; |
| 144 | } |
| 145 | return plugins; |
| 146 | } |
| 147 | |
| 148 | void QTextToSpeechPrivate::loadPluginMetadata(QMultiHash<QString, QJsonObject> &list) |
| 149 | { |
| 150 | QFactoryLoader *l = loader(); |
| 151 | QList<QJsonObject> meta = l->metaData(); |
| 152 | for (int i = 0; i < meta.size(); ++i) { |
| 153 | QJsonObject obj = meta.at(i).value(key: QLatin1String("MetaData" )).toObject(); |
| 154 | obj.insert(key: QLatin1String("index" ), value: i); |
| 155 | list.insert(akey: obj.value(key: QLatin1String("Provider" )).toString(), avalue: obj); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /*! |
| 160 | \class QTextToSpeech |
| 161 | \brief The QTextToSpeech class provides a convenient access to text-to-speech engines. |
| 162 | \inmodule QtSpeech |
| 163 | |
| 164 | Use \l say() to start synthesizing text. |
| 165 | It is possible to specify the language with \l setLocale(). |
| 166 | To select between the available voices use \l setVoice(). |
| 167 | The languages and voices depend on the available synthesizers on each platform. |
| 168 | On Linux, \c speech-dispatcher is used by default. |
| 169 | */ |
| 170 | |
| 171 | /*! |
| 172 | \enum QTextToSpeech::State |
| 173 | \value Ready The synthesizer is ready to start a new text. This is |
| 174 | also the state after a text was finished. |
| 175 | \value Speaking The current text is being spoken. |
| 176 | \value Paused The synthesis was paused and can be resumed with \l resume(). |
| 177 | \value BackendError The backend was unable to synthesize the current string. |
| 178 | */ |
| 179 | |
| 180 | /*! |
| 181 | \property QTextToSpeech::state |
| 182 | This property holds the current state of the speech synthesizer. |
| 183 | Use \l say() to start synthesizing text with the current voice and locale. |
| 184 | |
| 185 | */ |
| 186 | |
| 187 | /*! |
| 188 | Loads a text-to-speech engine from a plug-in that uses the default |
| 189 | engine plug-in and constructs a QTextToSpeech object as the child |
| 190 | of \a parent. |
| 191 | |
| 192 | The default engine may be platform-specific. |
| 193 | |
| 194 | If the plugin fails to load, QTextToSpeech::state() returns |
| 195 | QTextToSpeech::BackendError. |
| 196 | |
| 197 | \sa availableEngines() |
| 198 | */ |
| 199 | QTextToSpeech::QTextToSpeech(QObject *parent) |
| 200 | : QObject(*new QTextToSpeechPrivate(this, QString()), parent) |
| 201 | { |
| 202 | Q_D(QTextToSpeech); |
| 203 | // Connect state change signal directly from the engine to the public API signal |
| 204 | if (d->m_engine) |
| 205 | connect(sender: d->m_engine, signal: &QTextToSpeechEngine::stateChanged, receiver: this, slot: &QTextToSpeech::stateChanged); |
| 206 | } |
| 207 | |
| 208 | /*! |
| 209 | Loads a text-to-speech engine from a plug-in that matches parameter \a engine and |
| 210 | constructs a QTextToSpeech object as the child of \a parent. |
| 211 | |
| 212 | If \a engine is empty, the default engine plug-in is used. The default |
| 213 | engine may be platform-specific. |
| 214 | |
| 215 | If the plugin fails to load, QTextToSpeech::state() returns QTextToSpeech::BackendError. |
| 216 | |
| 217 | \sa availableEngines() |
| 218 | */ |
| 219 | QTextToSpeech::QTextToSpeech(const QString &engine, QObject *parent) |
| 220 | : QObject(*new QTextToSpeechPrivate(this, engine), parent) |
| 221 | { |
| 222 | Q_D(QTextToSpeech); |
| 223 | // Connect state change signal directly from the engine to the public API signal |
| 224 | if (d->m_engine) |
| 225 | connect(sender: d->m_engine, signal: &QTextToSpeechEngine::stateChanged, receiver: this, slot: &QTextToSpeech::stateChanged); |
| 226 | } |
| 227 | |
| 228 | /*! |
| 229 | Gets the list of supported text-to-speech engine plug-ins. |
| 230 | */ |
| 231 | QStringList QTextToSpeech::availableEngines() |
| 232 | { |
| 233 | return QTextToSpeechPrivate::plugins().keys(); |
| 234 | } |
| 235 | |
| 236 | QTextToSpeech::State QTextToSpeech::state() const |
| 237 | { |
| 238 | Q_D(const QTextToSpeech); |
| 239 | if (d->m_engine) |
| 240 | return d->m_engine->state(); |
| 241 | return QTextToSpeech::BackendError; |
| 242 | } |
| 243 | |
| 244 | /*! |
| 245 | Start synthesizing the \a text. |
| 246 | This function will start the asynchronous reading of the text. |
| 247 | The current state is available using the \l state property. Once the |
| 248 | synthesis is done, a \l stateChanged() signal with the \l Ready state |
| 249 | is emitted. |
| 250 | */ |
| 251 | void QTextToSpeech::say(const QString &text) |
| 252 | { |
| 253 | Q_D(QTextToSpeech); |
| 254 | if (d->m_engine) |
| 255 | d->m_engine->say(text); |
| 256 | } |
| 257 | |
| 258 | /*! |
| 259 | Stop the text that is being read. |
| 260 | */ |
| 261 | void QTextToSpeech::stop() |
| 262 | { |
| 263 | Q_D(QTextToSpeech); |
| 264 | if (d->m_engine) |
| 265 | d->m_engine->stop(); |
| 266 | } |
| 267 | |
| 268 | /*! |
| 269 | Pauses the current speech. |
| 270 | |
| 271 | Note: |
| 272 | \list |
| 273 | \li This function depends on the platform and the backend. It may not |
| 274 | work at all, it may take several seconds before it takes effect, |
| 275 | or it may pause instantly. |
| 276 | Some synthesizers will look for a break that they can later resume |
| 277 | from, such as a sentence end. |
| 278 | \li Due to Android platform limitations, pause() stops what is presently |
| 279 | being said, while resume() starts the previously queued sentence from |
| 280 | the beginning. |
| 281 | \endlist |
| 282 | |
| 283 | \sa resume() |
| 284 | */ |
| 285 | void QTextToSpeech::pause() |
| 286 | { |
| 287 | Q_D(QTextToSpeech); |
| 288 | if (d->m_engine) |
| 289 | d->m_engine->pause(); |
| 290 | } |
| 291 | |
| 292 | /*! |
| 293 | Resume speaking after \l pause() has been called. |
| 294 | \sa pause() |
| 295 | */ |
| 296 | void QTextToSpeech::resume() |
| 297 | { |
| 298 | Q_D(QTextToSpeech); |
| 299 | if (d->m_engine) |
| 300 | d->m_engine->resume(); |
| 301 | } |
| 302 | |
| 303 | //QVector<QString> QTextToSpeech::availableVoiceTypes() const |
| 304 | //{ |
| 305 | // Q_D(const QTextToSpeech); |
| 306 | // return d->availableVoiceTypes(); |
| 307 | //} |
| 308 | |
| 309 | //void QTextToSpeech::setVoiceType(const QString& type) |
| 310 | //{ |
| 311 | // Q_D(QTextToSpeech); |
| 312 | // d->setVoiceType(type); |
| 313 | //} |
| 314 | //QString QTextToSpeech::currentVoiceType() const |
| 315 | //{ |
| 316 | // Q_D(const QTextToSpeech); |
| 317 | // return d->currentVoiceType(); |
| 318 | //} |
| 319 | |
| 320 | |
| 321 | /*! |
| 322 | \property QTextToSpeech::pitch |
| 323 | This property holds the voice pitch, ranging from -1.0 to 1.0. |
| 324 | The default of 0.0 is the normal speech pitch. |
| 325 | */ |
| 326 | |
| 327 | void QTextToSpeech::setPitch(double pitch) |
| 328 | { |
| 329 | Q_D(QTextToSpeech); |
| 330 | if (d->m_engine && d->m_engine->setPitch(pitch)) |
| 331 | emit pitchChanged(pitch); |
| 332 | } |
| 333 | |
| 334 | double QTextToSpeech::pitch() const |
| 335 | { |
| 336 | Q_D(const QTextToSpeech); |
| 337 | if (d->m_engine) |
| 338 | return d->m_engine->pitch(); |
| 339 | return 0.0; |
| 340 | } |
| 341 | |
| 342 | /*! |
| 343 | \property QTextToSpeech::rate |
| 344 | This property holds the current voice rate, ranging from -1.0 to 1.0. |
| 345 | The default value of 0.0 is normal speech flow. |
| 346 | */ |
| 347 | void QTextToSpeech::setRate(double rate) |
| 348 | { |
| 349 | Q_D(QTextToSpeech); |
| 350 | if (d->m_engine && d->m_engine->setRate(rate)) |
| 351 | emit rateChanged(rate); |
| 352 | } |
| 353 | |
| 354 | double QTextToSpeech::rate() const |
| 355 | { |
| 356 | Q_D(const QTextToSpeech); |
| 357 | if (d->m_engine) |
| 358 | return d->m_engine->rate(); |
| 359 | return 0.0; |
| 360 | } |
| 361 | |
| 362 | /*! |
| 363 | \property QTextToSpeech::volume |
| 364 | This property holds the current volume, ranging from 0.0 to 1.0. |
| 365 | The default value is the platform's default volume. |
| 366 | */ |
| 367 | void QTextToSpeech::setVolume(double volume) |
| 368 | { |
| 369 | Q_D(QTextToSpeech); |
| 370 | volume = qMin(a: qMax(a: volume, b: 0.0), b: 1.0); |
| 371 | if (d->m_engine && d->m_engine->setVolume(volume)) { |
| 372 | emit volumeChanged(volume); |
| 373 | emit volumeChanged(volume: static_cast<int>(volume)); |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | double QTextToSpeech::volume() const |
| 378 | { |
| 379 | Q_D(const QTextToSpeech); |
| 380 | if (d->m_engine) |
| 381 | return d->m_engine->volume(); |
| 382 | return 0.0; |
| 383 | } |
| 384 | |
| 385 | /*! |
| 386 | Sets the \a locale to a given locale if possible. |
| 387 | The default is the system locale. |
| 388 | */ |
| 389 | void QTextToSpeech::setLocale(const QLocale &locale) |
| 390 | { |
| 391 | Q_D(QTextToSpeech); |
| 392 | if (d->m_engine && d->m_engine->setLocale(locale)) { |
| 393 | emit localeChanged(locale); |
| 394 | emit voiceChanged(voice: d->m_engine->voice()); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | /*! |
| 399 | \property QTextToSpeech::locale |
| 400 | This property holds the current locale in use. By default, the system locale |
| 401 | is used. |
| 402 | */ |
| 403 | QLocale QTextToSpeech::locale() const |
| 404 | { |
| 405 | Q_D(const QTextToSpeech); |
| 406 | if (d->m_engine) |
| 407 | return d->m_engine->locale(); |
| 408 | return QLocale(); |
| 409 | } |
| 410 | |
| 411 | /*! |
| 412 | Gets a vector of locales that are currently supported. |
| 413 | \note On some platforms these can change, for example, |
| 414 | when the backend changes synthesizers. |
| 415 | */ |
| 416 | QVector<QLocale> QTextToSpeech::availableLocales() const |
| 417 | { |
| 418 | Q_D(const QTextToSpeech); |
| 419 | if (d->m_engine) |
| 420 | return d->m_engine->availableLocales(); |
| 421 | return QVector<QLocale>(); |
| 422 | } |
| 423 | |
| 424 | /*! |
| 425 | Sets the \a voice to use. |
| 426 | |
| 427 | \note On some platforms, setting the voice changes other voice attributes |
| 428 | such as locale, pitch, and so on. These changes trigger the emission of signals. |
| 429 | */ |
| 430 | void QTextToSpeech::setVoice(const QVoice &voice) |
| 431 | { |
| 432 | Q_D(QTextToSpeech); |
| 433 | if (d->m_engine && d->m_engine->setVoice(voice)) |
| 434 | emit voiceChanged(voice); |
| 435 | } |
| 436 | |
| 437 | /*! |
| 438 | \property QTextToSpeech::voice |
| 439 | This property holds the current voice used for the speech. |
| 440 | */ |
| 441 | QVoice QTextToSpeech::voice() const |
| 442 | { |
| 443 | Q_D(const QTextToSpeech); |
| 444 | if (d->m_engine) |
| 445 | return d->m_engine->voice(); |
| 446 | return QVoice(); |
| 447 | } |
| 448 | |
| 449 | /*! |
| 450 | Gets a vector of voices available for the current locale. |
| 451 | \note if no locale has been set, the system locale is used. |
| 452 | */ |
| 453 | QVector<QVoice> QTextToSpeech::availableVoices() const |
| 454 | { |
| 455 | Q_D(const QTextToSpeech); |
| 456 | if (d->m_engine) |
| 457 | return d->m_engine->availableVoices(); |
| 458 | return QVector<QVoice>(); |
| 459 | } |
| 460 | |
| 461 | QT_END_NAMESPACE |
| 462 | |