rockbox/utils/rbutilqt/base/ttssapi.cpp
Dominik Riebeling 62108a9613 rbutil: Use references to avoid creating temporary objects.
Get rid of some unnecessary object creating / copying by using
references.

Change-Id: Ia44e34f6f66d230caa9af7ef7c0eca73be12de2a
2022-04-17 23:21:19 +02:00

274 lines
8.9 KiB
C++

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2007 by Dominik Wenger
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "ttssapi.h"
#include "utils.h"
#include "rbsettings.h"
#include "playerbuildinfo.h"
#include "Logger.h"
TTSSapi::TTSSapi(QObject* parent) : TTSBase(parent)
{
m_TTSTemplate = "cscript //nologo \"%exe\" /language:%lang "
"/voice:\"%voice\" /speed:%speed \"%options\"";
m_TTSVoiceTemplate = "cscript //nologo \"%exe\" /language:%lang /listvoices";
m_TTSType = "sapi";
defaultLanguage = "english";
m_started = false;
}
TTSBase::Capabilities TTSSapi::capabilities()
{
return None;
}
void TTSSapi::generateSettings()
{
// language
QMap<QString, QVariant> langmap = PlayerBuildInfo::instance()->value(
PlayerBuildInfo::LanguageList).toMap();
EncTtsSetting* setting = new EncTtsSetting(this,
EncTtsSetting::eSTRINGLIST, tr("Language:"),
RbSettings::subValue(m_TTSType, RbSettings::TtsLanguage),
langmap.keys());
connect(setting, &EncTtsSetting::dataChanged, this, &TTSSapi::updateVoiceList);
insertSetting(eLANGUAGE,setting);
// voice
setting = new EncTtsSetting(this,
EncTtsSetting::eSTRINGLIST, tr("Voice:"),
RbSettings::subValue(m_TTSType, RbSettings::TtsVoice),
getVoiceList(RbSettings::subValue(m_TTSType,
RbSettings::TtsLanguage).toString()),
EncTtsSetting::eREFRESHBTN);
connect(setting, &EncTtsSetting::refresh, this, &TTSSapi::updateVoiceList);
insertSetting(eVOICE,setting);
//speed
int speed = RbSettings::subValue(m_TTSType, RbSettings::TtsSpeed).toInt();
if(speed > 10 || speed < -10)
speed = 0;
insertSetting(eSPEED, new EncTtsSetting(this,
EncTtsSetting::eINT, tr("Speed:"), speed, -10, 10));
// options
insertSetting(eOPTIONS, new EncTtsSetting(this,
EncTtsSetting::eSTRING, tr("Options:"),
RbSettings::subValue(m_TTSType, RbSettings::TtsOptions)));
}
void TTSSapi::saveSettings()
{
//save settings in user config
RbSettings::setSubValue(m_TTSType, RbSettings::TtsLanguage,
getSetting(eLANGUAGE)->current().toString());
RbSettings::setSubValue(m_TTSType, RbSettings::TtsVoice,
getSetting(eVOICE)->current().toString());
RbSettings::setSubValue(m_TTSType, RbSettings::TtsSpeed,
getSetting(eSPEED)->current().toInt());
RbSettings::setSubValue(m_TTSType, RbSettings::TtsOptions,
getSetting(eOPTIONS)->current().toString());
RbSettings::sync();
}
void TTSSapi::updateVoiceList()
{
LOG_INFO() << "updating voicelist";
QStringList voiceList = getVoiceList(getSetting(eLANGUAGE)->current().toString());
getSetting(eVOICE)->setList(voiceList);
if(voiceList.size() > 0) getSetting(eVOICE)->setCurrent(voiceList.at(0));
else getSetting(eVOICE)->setCurrent("");
}
bool TTSSapi::start(QString *errStr)
{
m_TTSOpts = RbSettings::subValue(m_TTSType, RbSettings::TtsOptions).toString();
m_TTSLanguage =RbSettings::subValue(m_TTSType, RbSettings::TtsLanguage).toString();
m_TTSVoice=RbSettings::subValue(m_TTSType, RbSettings::TtsVoice).toString();
m_TTSSpeed=RbSettings::subValue(m_TTSType, RbSettings::TtsSpeed).toString();
QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
QFileInfo tts(m_TTSexec);
if(!tts.exists())
{
*errStr = tr("Could not copy the SAPI script");
return false;
}
// create the voice process
QString execstring = m_TTSTemplate;
execstring.replace("%exe",m_TTSexec);
execstring.replace("%options",m_TTSOpts);
execstring.replace("%lang",m_TTSLanguage);
execstring.replace("%voice",m_TTSVoice);
execstring.replace("%speed",m_TTSSpeed);
LOG_INFO() << "Start:" << execstring;
voicescript = new QProcess(nullptr);
//connect(voicescript,SIGNAL(readyReadStandardError()),this,SLOT(error()));
voicescript->start(execstring);
LOG_INFO() << "wait for process";
if(!voicescript->waitForStarted())
{
*errStr = tr("Could not start SAPI process");
LOG_ERROR() << "starting process timed out!";
return false;
}
if(!voicescript->waitForReadyRead(300))
{
*errStr = voicescript->readAllStandardError();
if(*errStr != "")
return false;
}
voicestream = new QTextStream(voicescript);
#if QT_VERSION < 0x060000
voicestream->setCodec("UTF16-LE");
#else
voicestream->setEncoding(QStringConverter::Utf16LE);
#endif
m_started = true;
return true;
}
QString TTSSapi::voiceVendor(void)
{
bool keeprunning = m_started;
QString vendor;
if(!m_started) {
QString error;
start(&error);
}
*voicestream << "QUERY\tVENDOR\r\n";
voicestream->flush();
while((vendor = voicestream->readLine()).isEmpty())
QCoreApplication::processEvents();
LOG_INFO() << "TTS vendor:" << vendor;
if(!keeprunning) {
stop();
}
return vendor;
}
QStringList TTSSapi::getVoiceList(QString language)
{
QStringList result;
QFile::copy(":/builtin/sapi_voice.vbs",QDir::tempPath() + "/sapi_voice.vbs");
m_TTSexec = QDir::tempPath() +"/sapi_voice.vbs";
QFileInfo tts(m_TTSexec);
if(!tts.exists())
return result;
// create the voice process
QString execstring = m_TTSVoiceTemplate;
execstring.replace("%exe",m_TTSexec);
execstring.replace("%lang",language);
LOG_INFO() << "Start:" << execstring;
voicescript = new QProcess(nullptr);
voicescript->start(execstring);
LOG_INFO() << "wait for process";
if(!voicescript->waitForStarted()) {
LOG_INFO() << "process startup timed out!";
return result;
}
voicescript->closeWriteChannel();
voicescript->waitForReadyRead();
const QString dataRaw = voicescript->readAllStandardError().constData();
if(dataRaw.startsWith("Error")) {
LOG_INFO() << "Error:" << dataRaw;
}
#if QT_VERSION >= 0x050e00
result = dataRaw.split(";", Qt::SkipEmptyParts);
#else
result = dataRaw.split(";", QString::SkipEmptyParts);
#endif
if(result.size() > 0)
{
result.sort();
result.removeFirst();
for(int i = 0; i< result.size();i++)
{
result[i] = result.at(i).simplified();
}
}
delete voicescript;
QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
| QFile::ReadUser | QFile::WriteUser | QFile::ExeUser
| QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup
| QFile::ReadOther | QFile::WriteOther | QFile::ExeOther );
QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
return result;
}
TTSStatus TTSSapi::voice(const QString& text, const QString& wavfile, QString *errStr)
{
(void) errStr;
QString query = "SPEAK\t"+wavfile+"\t"+text;
LOG_INFO() << "voicing" << query;
// append newline to query. Done now to keep debug output more readable.
query.append("\r\n");
*voicestream << query;
*voicestream << "SYNC\tbla\r\n";
voicestream->flush();
// do NOT poll the output with readLine(), this causes sync issues!
voicescript->waitForReadyRead();
if(!QFileInfo(wavfile).isFile()) {
LOG_ERROR() << "output file does not exist:" << wavfile;
return FatalError;
}
return NoError;
}
bool TTSSapi::stop()
{
*voicestream << "QUIT\r\n";
voicestream->flush();
voicescript->waitForFinished();
delete voicestream;
delete voicescript;
QFile::setPermissions(QDir::tempPath() +"/sapi_voice.vbs",
QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
| QFile::ReadUser | QFile::WriteUser | QFile::ExeUser
| QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup
| QFile::ReadOther | QFile::WriteOther | QFile::ExeOther );
QFile::remove(QDir::tempPath() +"/sapi_voice.vbs");
m_started = false;
return true;
}
bool TTSSapi::configOk()
{
if(RbSettings::subValue(m_TTSType, RbSettings::TtsVoice).toString().isEmpty())
return false;
return true;
}