mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 10:52:53 +00:00
552 lines
17 KiB
C++
552 lines
17 KiB
C++
/***************************************************** vim:set ts=4 sw=4 sts=4:
|
|
Convenience object for manipulating Talker Codes.
|
|
For an explanation of what a Talker Code is, see kspeech.h.
|
|
-------------------
|
|
Copyright 2005 by Gary Cramblitt <garycramblitt@comcast.net>
|
|
Copyright 2009 - 2010 by Jeremy Whiting <jpwhiting@kde.org>
|
|
-------------------
|
|
Original author: Gary Cramblitt <garycramblitt@comcast.net>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
******************************************************************************/
|
|
|
|
// TalkerCode includes.
|
|
#include "talkercode.h"
|
|
|
|
// Qt includes.
|
|
#include <QtCore/QVector>
|
|
#include <QtXml/qdom.h>
|
|
|
|
// KDE includes.
|
|
#include <kconfig.h>
|
|
#include <kconfiggroup.h>
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <kservicetypetrader.h>
|
|
|
|
#include <libspeechd.h>
|
|
|
|
class TalkerCodePrivate
|
|
{
|
|
public:
|
|
TalkerCodePrivate(TalkerCode *parent)
|
|
:q(parent)
|
|
{
|
|
}
|
|
|
|
~TalkerCodePrivate()
|
|
{
|
|
}
|
|
|
|
QString name; /* name="xxx" */
|
|
QString language; /* lang="xx" */
|
|
int voiceType; /* voiceType="xxx" */
|
|
int volume; /* volume="xxx" */
|
|
int rate; /* rate="xxx" */
|
|
int pitch; /* pitch="xxx" */
|
|
QString voiceName; /* voiceName="xxx" */
|
|
QString outputModule; /* synthesizer="xxx" */
|
|
int punctuation; /* punctuation="xxx" */
|
|
TalkerCode *q;
|
|
|
|
|
|
};
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
TalkerCode::TalkerCode(const QString &code/*=QString()*/, bool normal /*=false*/)
|
|
:d(new TalkerCodePrivate(this))
|
|
{
|
|
if (!code.isEmpty())
|
|
parseTalkerCode(code);
|
|
//if (normal)
|
|
// normalize();
|
|
}
|
|
|
|
/**
|
|
* Copy Constructor.
|
|
*/
|
|
TalkerCode::TalkerCode(const TalkerCode& other)
|
|
:d(new TalkerCodePrivate(this))
|
|
{
|
|
d->name = other.name();
|
|
d->language = other.language();
|
|
d->voiceType = other.voiceType();
|
|
d->volume = other.volume();
|
|
d->rate = other.rate();
|
|
d->pitch = other.pitch();
|
|
d->voiceName = other.voiceName();
|
|
d->outputModule = other.outputModule();
|
|
d->punctuation = other.punctuation();
|
|
}
|
|
|
|
/**
|
|
* Destructor.
|
|
*/
|
|
TalkerCode::~TalkerCode()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
TalkerCode &TalkerCode::operator=(const TalkerCode &other)
|
|
{
|
|
d->name = other.name();
|
|
d->language = other.language();
|
|
d->voiceType = other.voiceType();
|
|
d->volume = other.volume();
|
|
d->rate = other.rate();
|
|
d->pitch = other.pitch();
|
|
d->voiceName = other.voiceName();
|
|
d->outputModule = other.outputModule();
|
|
d->punctuation = other.punctuation();
|
|
return *this;
|
|
}
|
|
|
|
TalkerCode::TalkerCodeList TalkerCode::loadTalkerCodesFromConfig(KConfig* c)
|
|
{
|
|
TalkerCodeList list;
|
|
// Iterate through list of the TalkerCode IDs.
|
|
KConfigGroup config(c, "General");
|
|
QStringList talkerIDsList = config.readEntry("TalkerIDs", QStringList());
|
|
// kDebug() << "TalkerCode::loadTalkerCodesFromConfig: talkerIDsList = " << talkerIDsList;
|
|
if (!talkerIDsList.isEmpty())
|
|
{
|
|
QStringList::ConstIterator itEnd = talkerIDsList.constEnd();
|
|
for (QStringList::ConstIterator it = talkerIDsList.constBegin(); it != itEnd; ++it)
|
|
{
|
|
QString talkerID = *it;
|
|
kDebug() << "TalkerCode::loadTalkerCodesFromConfig: talkerID = " << talkerID;
|
|
KConfigGroup talkGroup(c, "Talkers");
|
|
QString talkerCode = talkGroup.readEntry(talkerID);
|
|
TalkerCode tc(talkerCode, true);
|
|
kDebug() << "TalkerCode::loadTalkerCodesFromConfig: talkerCode = " << talkerCode;
|
|
list.append(tc);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Properties.
|
|
*/
|
|
QString TalkerCode::name() const
|
|
{
|
|
return d->name;
|
|
}
|
|
|
|
QString TalkerCode::language() const
|
|
{
|
|
return d->language;
|
|
}
|
|
|
|
int TalkerCode::voiceType() const
|
|
{
|
|
return d->voiceType;
|
|
}
|
|
|
|
int TalkerCode::volume() const
|
|
{
|
|
return d->volume;
|
|
}
|
|
|
|
int TalkerCode::rate() const
|
|
{
|
|
return d->rate;
|
|
}
|
|
|
|
int TalkerCode::pitch() const
|
|
{
|
|
return d->pitch;
|
|
}
|
|
|
|
QString TalkerCode::voiceName() const
|
|
{
|
|
return d->voiceName;
|
|
}
|
|
|
|
QString TalkerCode::outputModule() const
|
|
{
|
|
return d->outputModule;
|
|
}
|
|
/* function to get the punctuation*/
|
|
int TalkerCode::punctuation() const
|
|
{
|
|
return d->punctuation;
|
|
}
|
|
|
|
QString TalkerCode::punctuationName() const
|
|
{
|
|
QString name;
|
|
switch (d->punctuation)
|
|
{
|
|
case SPD_PUNCT_ALL:
|
|
name = i18n("All");
|
|
break;
|
|
case SPD_PUNCT_SOME:
|
|
name = i18n("Some");
|
|
break;
|
|
case SPD_PUNCT_NONE:
|
|
name = i18n("None");
|
|
break;
|
|
}
|
|
return name;
|
|
}
|
|
|
|
/* function to set the punctuation*/
|
|
|
|
void TalkerCode::setPunctuation(int value)
|
|
{
|
|
d->punctuation=value;
|
|
}
|
|
|
|
|
|
void TalkerCode::setName(const QString &name)
|
|
{
|
|
d->name = name;
|
|
}
|
|
|
|
void TalkerCode::setLanguage(const QString &language)
|
|
{
|
|
d->language = language;
|
|
}
|
|
|
|
void TalkerCode::setVoiceType(int voiceType)
|
|
{
|
|
d->voiceType = voiceType;
|
|
}
|
|
|
|
void TalkerCode::setVolume(int volume)
|
|
{
|
|
d->volume = volume;
|
|
}
|
|
|
|
void TalkerCode::setRate(int rate)
|
|
{
|
|
d->rate = rate;
|
|
}
|
|
|
|
void TalkerCode::setPitch(int pitch)
|
|
{
|
|
d->pitch = pitch;
|
|
}
|
|
|
|
void TalkerCode::setVoiceName(const QString &voiceName)
|
|
{
|
|
d->voiceName = voiceName;
|
|
}
|
|
|
|
void TalkerCode::setOutputModule(const QString &moduleName)
|
|
{
|
|
d->outputModule = moduleName;
|
|
}
|
|
|
|
/**
|
|
* The Talker Code returned in XML format.
|
|
*/
|
|
void TalkerCode::setTalkerCode(const QString& code)
|
|
{
|
|
parseTalkerCode(code);
|
|
}
|
|
|
|
|
|
QString TalkerCode::getTalkerCode() const
|
|
{
|
|
QString xml(QLatin1String("<voice name=\"%1\" lang=\"%2\" outputModule=\"%3\""
|
|
" voiceName=\"%4\" voiceType=\"%5\" >"
|
|
"<prosody volume=\"%6\" rate=\"%7\" pitch=\"%8\" punctuation=\"%9\"/></voice>"));
|
|
QString code = xml.arg(d->name)
|
|
.arg(d->language)
|
|
.arg(d->outputModule)
|
|
.arg(d->voiceName)
|
|
.arg(d->voiceType)
|
|
.arg(d->volume)
|
|
.arg(d->rate)
|
|
.arg(d->pitch)
|
|
.arg(d->punctuation);
|
|
return code;
|
|
}
|
|
|
|
/**
|
|
* The Talker Code translated for display.
|
|
*/
|
|
QString TalkerCode::getTranslatedDescription() const
|
|
{
|
|
QString code;
|
|
if (!d->name.isEmpty())
|
|
{
|
|
code = d->name;
|
|
}
|
|
else
|
|
{
|
|
code = d->language;
|
|
bool prefer;
|
|
QString fullLangCode = d->language;
|
|
if (!fullLangCode.isEmpty()) code = languageCodeToLanguage( fullLangCode );
|
|
// TODO: The PlugInName is always English. Need a way to convert this to a translated
|
|
// name (possibly via DesktopEntryNameToName, but to do that, we need the desktopEntryName
|
|
// from the config file).
|
|
if (!d->outputModule.isEmpty()) code += QLatin1Char( ' ' ) + stripPrefer(d->outputModule, prefer);
|
|
code += QLatin1Char(' ') + translatedVoiceType(d->voiceType);
|
|
code += QString(QLatin1String(" volume: %1 rate: %2")).arg(d->volume).arg(d->rate);
|
|
code = code.trimmed();
|
|
}
|
|
if (code.isEmpty())
|
|
code = i18nc("Default language code", "default");
|
|
return code;
|
|
}
|
|
|
|
QString TalkerCode::translatedVoiceType(int voiceType)
|
|
{
|
|
switch (voiceType)
|
|
{
|
|
case 1: return i18nc("The name of the first Male voice","Male 1"); break;
|
|
case 2: return i18n("Male 2"); break;
|
|
case 3: return i18n("Male 3"); break;
|
|
case 4: return i18nc("The name of the first Female voice", "Female 1"); break;
|
|
case 5: return i18n("Female 2"); break;
|
|
case 6: return i18n("Female 3"); break;
|
|
case 7: return i18nc("The name of the male child voice", "Boy"); break;
|
|
case 8: return i18nc("The name of the female child voice", "Girl"); break;
|
|
}
|
|
return i18nc("Somehow user has gotten a voice type that is not valid, i.e. not Male1, Male2, etc.","Invalid voice type");
|
|
}
|
|
|
|
/*static*/ void TalkerCode::splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode)
|
|
{
|
|
QString language = lang;
|
|
if (language.left(1) == QLatin1String( "*" ))
|
|
language = language.mid(1);
|
|
QString modifier;
|
|
QString charSet;
|
|
KGlobal::locale()->splitLocale(language, languageCode, countryCode, modifier, charSet);
|
|
}
|
|
|
|
/*static*/ QString TalkerCode::defaultTalkerCode(const QString &fullLanguageCode, const QString &moduleName)
|
|
{
|
|
TalkerCode tmpTalkerCode;
|
|
//tmpTalkerCode.setFullLanguageCode(fullLanguageCode);
|
|
tmpTalkerCode.setOutputModule(moduleName);
|
|
//tmpTalkerCode.normalize();
|
|
return tmpTalkerCode.getTalkerCode();
|
|
}
|
|
|
|
/*static*/ QString TalkerCode::languageCodeToLanguage(const QString &languageCode)
|
|
{
|
|
QString langAlpha;
|
|
QString countryCode;
|
|
QString language;
|
|
if (languageCode == QLatin1String( "other" ))
|
|
language = i18nc("Other language", "Other");
|
|
else
|
|
{
|
|
splitFullLanguageCode(languageCode, langAlpha, countryCode);
|
|
language = KGlobal::locale()->languageCodeToName(langAlpha);
|
|
}
|
|
if (!countryCode.isEmpty())
|
|
{
|
|
QString countryName = KGlobal::locale()->countryCodeToName(countryCode);
|
|
// Some abbreviations to save screen space.
|
|
if (countryName == i18nc("full country name", "United States of America"))
|
|
countryName = i18nc("abbreviated country name", "USA");
|
|
if (countryName == i18nc("full country name", "United Kingdom"))
|
|
countryName = i18nc("abbreviated country name", "UK");
|
|
language += QLatin1String( " (" ) + countryName + QLatin1Char( ')' );
|
|
}
|
|
return language;
|
|
}
|
|
|
|
/**
|
|
* Given a talker code, parses out the attributes.
|
|
* @param talkerCode The talker code.
|
|
*/
|
|
void TalkerCode::parseTalkerCode(const QString &talkerCode)
|
|
{
|
|
QDomDocument doc;
|
|
doc.setContent(talkerCode);
|
|
|
|
QDomElement voice = doc.firstChildElement(QLatin1String( "voice" ));
|
|
if (!voice.isNull())
|
|
{
|
|
d->name = voice.attribute(QLatin1String( "name" ));
|
|
d->language = voice.attribute(QLatin1String( "lang" ));
|
|
d->outputModule = voice.attribute(QLatin1String( "outputModule" ));
|
|
d->voiceName = voice.attribute(QLatin1String( "voiceName" ));
|
|
bool result = false;
|
|
d->voiceType = voice.attribute(QLatin1String( "voiceType" )).toInt(&result);
|
|
if (!result)
|
|
d->voiceType = 1;
|
|
|
|
QDomElement prosody = voice.firstChildElement(QLatin1String( "prosody" ));
|
|
if (!prosody.isNull())
|
|
{
|
|
bool result = false;
|
|
d->volume = prosody.attribute(QLatin1String( "volume" )).toInt(&result);
|
|
if (!result)
|
|
d->volume = 0;
|
|
d->rate = prosody.attribute(QLatin1String( "rate" )).toInt(&result);
|
|
if (!result)
|
|
d->rate = 0;
|
|
d->pitch = prosody.attribute(QLatin1String( "pitch" )).toInt(&result);
|
|
if (!result)
|
|
d->pitch = 0;
|
|
d->punctuation = prosody.attribute(QLatin1String("punctuation")).toInt(&result);
|
|
if (!result)
|
|
d->punctuation = SPD_PUNCT_NONE; // Default to no punctuation
|
|
}
|
|
else
|
|
{
|
|
kDebug() << "got a voice with no prosody tag";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
kDebug() << "got a voice with no voice tag";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a list of parsed talker codes and a desired talker code, finds the closest
|
|
* matching talker in the list.
|
|
* @param talkers The list of parsed talker codes.
|
|
* @param talker The desired talker code.
|
|
* @param assumeDefaultLang If true, and desired talker code lacks a language code,
|
|
* the default language is assumed.
|
|
* @return Index into talkers of the closest matching talker.
|
|
*/
|
|
/*static*/ int TalkerCode::findClosestMatchingTalker(
|
|
const TalkerCodeList& talkers,
|
|
const QString& talker,
|
|
bool assumeDefaultLang)
|
|
{
|
|
// kDebug() << "TalkerCode::findClosestMatchingTalker: matching on talker code " << talker;
|
|
// If nothing to match on, winner is top in the list.
|
|
if (talker.isEmpty()) return 0;
|
|
// Parse the given talker.
|
|
TalkerCode parsedTalkerCode(talker);
|
|
// If no language code specified, use the language code of the default talker.
|
|
if (assumeDefaultLang)
|
|
{
|
|
if (parsedTalkerCode.language().isEmpty()) parsedTalkerCode.setLanguage(
|
|
talkers[0].language());
|
|
}
|
|
// The talker that matches on the most priority attributes wins.
|
|
int talkersCount = int(talkers.count());
|
|
QVector<int> priorityMatch(talkersCount);
|
|
for (int ndx = 0; ndx < talkersCount; ++ndx)
|
|
{
|
|
priorityMatch[ndx] = 0;
|
|
// kDebug() << "Comparing language code " << parsedTalkerCode.languageCode() << " to " << d->loadedPlugIns[ndx].parsedTalkerCode.languageCode();
|
|
}
|
|
// Determine the maximum number of priority attributes that were matched.
|
|
int maxPriority = -1;
|
|
for (int ndx = 0; ndx < talkersCount; ++ndx)
|
|
{
|
|
if (priorityMatch[ndx] > maxPriority) maxPriority = priorityMatch[ndx];
|
|
}
|
|
// Find the talker(s) that matched on most priority attributes.
|
|
int winnerCount = 0;
|
|
int winner = -1;
|
|
for (int ndx = 0; ndx < talkersCount; ++ndx)
|
|
{
|
|
if (priorityMatch[ndx] == maxPriority)
|
|
{
|
|
++winnerCount;
|
|
winner = ndx;
|
|
}
|
|
}
|
|
// kDebug() << "Priority phase: winnerCount = " << winnerCount
|
|
// << " winner = " << winner
|
|
// << " maxPriority = " << maxPriority << endl;
|
|
// If a tie, the one that matches on the most priority and preferred attributes wins.
|
|
// If there is still a tie, the one nearest the top of the kttsmgr display
|
|
// (first configured) will be chosen.
|
|
if (winnerCount > 1)
|
|
{
|
|
QVector<int> preferredMatch(talkersCount);
|
|
for (int ndx = 0; ndx < talkersCount; ++ndx)
|
|
{
|
|
preferredMatch[ndx] = 0;
|
|
if (priorityMatch[ndx] == maxPriority)
|
|
{
|
|
}
|
|
}
|
|
// Determine the maximum number of preferred attributes that were matched.
|
|
int maxPreferred = -1;
|
|
for (int ndx = 0; ndx < talkersCount; ++ndx)
|
|
{
|
|
if (preferredMatch[ndx] > maxPreferred) maxPreferred = preferredMatch[ndx];
|
|
}
|
|
winner = -1;
|
|
winnerCount = 0;
|
|
// Find the talker that matched on most priority and preferred attributes.
|
|
// Work bottom to top so topmost wins in a tie.
|
|
for (int ndx = talkersCount-1; ndx >= 0; --ndx)
|
|
{
|
|
if (priorityMatch[ndx] == maxPriority)
|
|
{
|
|
if (preferredMatch[ndx] == maxPreferred)
|
|
{
|
|
++winnerCount;
|
|
winner = ndx;
|
|
}
|
|
}
|
|
}
|
|
// kDebug() << "Preferred phase: winnerCount = " << winnerCount
|
|
// << " winner = " << winner
|
|
// << " maxPreferred = " << maxPreferred << endl;
|
|
}
|
|
// If no winner found, use the first talker.
|
|
if (winner < 0) winner = 0;
|
|
// kDebug() << "TalkerCode::findClosestMatchingTalker: returning winner = " << winner;
|
|
return winner;
|
|
}
|
|
|
|
/*static*/ QString TalkerCode::stripPrefer( const QString& code, bool& preferred)
|
|
{
|
|
if ( code.left(1) == QLatin1String( "*" ) )
|
|
{
|
|
preferred = true;
|
|
return code.mid(1);
|
|
} else {
|
|
preferred = false;
|
|
return code;
|
|
}
|
|
}
|
|
|
|
bool TalkerCode::operator==(TalkerCode &other) const
|
|
{
|
|
return d->language == other.language() &&
|
|
d->voiceType == other.voiceType() &&
|
|
d->rate == other.rate() &&
|
|
d->volume == other.volume() &&
|
|
d->pitch == other.pitch() &&
|
|
d->voiceName == other.voiceName() &&
|
|
d->outputModule == other.outputModule() &&
|
|
d->punctuation == other.punctuation();
|
|
}
|
|
|
|
bool TalkerCode::operator!=(TalkerCode &other) const
|
|
{
|
|
return d->language != other.language() ||
|
|
d->voiceType != other.voiceType() ||
|
|
d->rate != other.rate() ||
|
|
d->volume != other.volume() ||
|
|
d->pitch != other.pitch() ||
|
|
d->voiceName != other.voiceName() ||
|
|
d->outputModule != other.outputModule() ||
|
|
d->punctuation != other.punctuation();
|
|
}
|