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
46QT_BEGIN_NAMESPACE
47
48Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
49 ("org.qt-project.qt.speech.tts.plugin/5.0",
50 QLatin1String("/texttospeech")))
51
52QMutex QTextToSpeechPrivate::m_mutex;
53
54QTextToSpeechPrivate::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
85QTextToSpeechPrivate::~QTextToSpeechPrivate()
86{
87 m_speech->stop();
88 delete m_engine;
89}
90
91bool 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
122void 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
132QMultiHash<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
148void 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*/
199QTextToSpeech::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*/
219QTextToSpeech::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*/
231QStringList QTextToSpeech::availableEngines()
232{
233 return QTextToSpeechPrivate::plugins().keys();
234}
235
236QTextToSpeech::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*/
251void 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*/
261void 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*/
285void 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*/
296void 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
327void 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
334double 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*/
347void 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
354double 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*/
367void 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
377double 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*/
389void 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*/
403QLocale 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*/
416QVector<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*/
430void 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*/
441QVoice 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*/
453QVector<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
461QT_END_NAMESPACE
462

source code of qtspeech/src/tts/qtexttospeech.cpp