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 | |