diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 00bb99ac80741fc50ef8a289719373032f2391eb (patch) | |
tree | 3a5a9bf72f942784b38bf77dd66c534662fab5f2 /kttsd/libkttsd | |
download | tdeaccessibility-00bb99ac80741fc50ef8a289719373032f2391eb.tar.gz tdeaccessibility-00bb99ac80741fc50ef8a289719373032f2391eb.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdeaccessibility@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kttsd/libkttsd')
25 files changed, 4926 insertions, 0 deletions
diff --git a/kttsd/libkttsd/Makefile.am b/kttsd/libkttsd/Makefile.am new file mode 100644 index 0000000..3b2c099 --- /dev/null +++ b/kttsd/libkttsd/Makefile.am @@ -0,0 +1,45 @@ +# Include paths. +INCLUDES = $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# LIBRARY SECTION +######################################################################### +# This is the library that gets installed. It's name is used for all +# of the other Makefile.am variables. +lib_LTLIBRARIES = libkttsd.la + +# The source, library search path, and link libraries. +libkttsd_la_SOURCES = \ + pluginproc.cpp \ + pluginconf.cpp \ + testplayer.cpp \ + stretcher.cpp \ + talkercode.cpp \ + filterproc.cpp \ + filterconf.cpp \ + utils.cpp \ + selecttalkerdlg.cpp \ + selecttalkerwidget.ui \ + notify.cpp + +libkttsd_la_LDFLAGS = -version-info 1:0:0 $(all_libraries) -no-undefined +libkttsd_la_LIBADD = $(LIB_KIO) + +# Header files that should not be installed. +noinst_HEADERS = \ + pluginproc.h \ + pluginconf.h \ + player.h \ + testplayer.h \ + stretcher.h \ + talkercode.h \ + utils.h \ + kdeexportfix.h \ + selecttalkerdlg.h + +# This library is installed as a service. +servicetypes_DATA = kttsd_synthplugin.desktop +servicetypesdir = $(kde_servicetypesdir) diff --git a/kttsd/libkttsd/filterconf.cpp b/kttsd/libkttsd/filterconf.cpp new file mode 100644 index 0000000..43e62bc --- /dev/null +++ b/kttsd/libkttsd/filterconf.cpp @@ -0,0 +1,166 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Configuration class. + This is the interface definition for text filter configuration dialogs. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +// C++ library includes. +#include <stdlib.h> +#include <sys/param.h> + +// Qt includes. +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> + +// PluginConf includes. +#include "filterconf.h" +#include "filterconf.moc" + +/** +* Constructor +*/ +KttsFilterConf::KttsFilterConf( QWidget *parent, const char *name) : QWidget(parent, name){ + // kdDebug() << "KttsFilterConf::KttsFilterConf: Running" << endl; + QString systemPath(getenv("PATH")); + // kdDebug() << "Path is " << systemPath << endl; + KGlobal::locale()->insertCatalogue("kttsd"); + m_path = QStringList::split(":", systemPath); +} + +/** +* Destructor. +*/ +KttsFilterConf::~KttsFilterConf(){ + // kdDebug() << "KttsFilterConf::~KttsFilterConf: Running" << endl; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void KttsFilterConf::load(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "KttsFilterConf::load: Running" << endl; +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void KttsFilterConf::save(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "KttsFilterConf::save: Running" << endl; +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void KttsFilterConf::defaults(){ + // kdDebug() << "KttsFilterConf::defaults: Running" << endl; +} + +/** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ +bool KttsFilterConf::supportsMultiInstance() { return false; } + +/** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ +QString KttsFilterConf::userPlugInName() { return QString::null; } + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * @return True if this filter is a SBD. + */ +bool KttsFilterConf::isSBD() { return false; } + +/** +* Return the full path to any program in the $PATH environmental variable +* @param name The name of the file to search for. +* @returns The path to the file on success, a blank QString +* if its not found. +*/ +QString KttsFilterConf::getLocation(const QString &name) { + // Iterate over the path and see if 'name' exists in it. Return the + // full path to it if it does. Else return an empty QString. + if (QFile::exists(name)) return name; + // kdDebug() << "KttsFilterConf::getLocation: Searching for " << name << " in the path.." << endl; + // kdDebug() << m_path << endl; + for(QStringList::iterator it = m_path.begin(); it != m_path.end(); ++it) { + QString fullName = *it; + fullName += "/"; + fullName += name; + // The user either has the directory of the file in the path... + if(QFile::exists(fullName)) { + // kdDebug() << "KttsFilterConf:getLocation: " << fullName << endl; + return fullName; + } + // ....Or the file itself + else if(QFileInfo(*it).baseName().append(QString(".").append(QFileInfo(*it).extension())) == name) { + // kdDebug() << "KttsFilterConf:getLocation: " << fullName << endl; + return fullName; + } + } + return ""; +} + +/*static*/ QString KttsFilterConf::realFilePath(const QString &filename) +{ + char realpath_buffer[MAXPATHLEN + 1]; + memset(realpath_buffer, 0, MAXPATHLEN + 1); + + /* If the path contains symlinks, get the real name */ + if (realpath( QFile::encodeName(filename).data(), realpath_buffer) != 0) { + //succes, use result from realpath + return QFile::decodeName(realpath_buffer); + } + return filename; +} diff --git a/kttsd/libkttsd/filterconf.h b/kttsd/libkttsd/filterconf.h new file mode 100644 index 0000000..42055d8 --- /dev/null +++ b/kttsd/libkttsd/filterconf.h @@ -0,0 +1,149 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Configuration class. + This is the interface definition for text filter configuration dialogs. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +#ifndef _FILTERCONF_H_ +#define _FILTERCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> +#include <kdemacros.h> +#include "kdeexportfix.h" + +class KDE_EXPORT KttsFilterConf : public QWidget{ + Q_OBJECT + + public: + /** + * Constructor + */ + KttsFilterConf( QWidget *parent = 0, const char *name = 0); + + /** + * Destructor + */ + virtual ~KttsFilterConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ + virtual QString userPlugInName(); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + static QString realFilePath(const QString &filename); + + public slots: + /** + * This slot is used internally when the configuration is changed. It is + * typically connected to signals from the widgets of the configuration + * and should emit the @ref changed signal. + */ + void configChanged(){ + // kdDebug() << "KttsFilterConf::configChanged: Running"<< endl; + emit changed(true); + }; + + signals: + /** + * This signal indicates that the configuration has been changed. + * It should be emitted whenever user changes something in the configuration widget. + */ + void changed(bool); + + protected: + /** + * Searches the $PATH variable for any file. If that file exists in the PATH, or + * is contained in any directory in the PATH, it returns the full path to it. + * @param name The name of the file to search for. + * @returns The path to the file on success, a blank QString + * if its not found. + */ + QString getLocation(const QString &name); + + /// The system path in a QStringList. + QStringList m_path; +}; + +#endif //_FILTERCONF_H_ diff --git a/kttsd/libkttsd/filterproc.cpp b/kttsd/libkttsd/filterproc.cpp new file mode 100644 index 0000000..178dec2 --- /dev/null +++ b/kttsd/libkttsd/filterproc.cpp @@ -0,0 +1,153 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Processing class. + This is the interface definition for text filters. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +// KDE includes. +// #include <kdebug.h> + +// FilterProc includes. +#include "filterproc.h" + +/** + * Constructor. + */ +KttsFilterProc::KttsFilterProc( QObject *parent, const char *name) : + QObject(parent, name) +{ + // kdDebug() << "KttsFilterProc::KttsFilterProc: Running" << endl; +} + +/** + * Destructor. + */ +KttsFilterProc::~KttsFilterProc() +{ + // kdDebug() << "KttsFilterProc::~KttsFilterProc: Running" << endl; +} + +/** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ +bool KttsFilterProc::init(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "PlugInProc::init: Running" << endl; + return false; +} + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ +/*virtual*/ bool KttsFilterProc::isSBD() { return false; } + +/** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ +/*virtual*/ bool KttsFilterProc::supportsAsync() { return false; } + +/** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ +/*virtual*/ QString KttsFilterProc::convert(const QString& inputText, TalkerCode* /*talkerCode*/, + const QCString& /*appId*/) +{ + return inputText; +} + +/** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ +/*virtual*/ bool KttsFilterProc::asyncConvert(const QString& /*inputText*/, + TalkerCode* /*talkerCode*/, const QCString& /*appId*/) { return false; } + +/** + * Waits for a previous call to asyncConvert to finish. + */ +/*virtual*/ void KttsFilterProc::waitForFinished() { } + +/** + * Returns the state of the Filter. + */ +/*virtual*/ int KttsFilterProc::getState() { return fsIdle; } + +/** + * Returns the filtered output. + */ +/*virtual*/ QString KttsFilterProc::getOutput() { return QString::null; } + +/** + * Acknowledges the finished filtering. + */ +/*virtual*/ void KttsFilterProc::ackFinished() { } + +/** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ +/*virtual*/ void KttsFilterProc::stopFiltering() { } + +/** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ +/*virtual*/ bool KttsFilterProc::wasModified() { return true; } + +/** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ +/*virtual*/ void KttsFilterProc::setSbRegExp(const QString& /*re*/) { } + +#include "filterproc.moc" diff --git a/kttsd/libkttsd/filterproc.h b/kttsd/libkttsd/filterproc.h new file mode 100644 index 0000000..ef7972c --- /dev/null +++ b/kttsd/libkttsd/filterproc.h @@ -0,0 +1,179 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Processing class. + This is the interface definition for text filters. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +#ifndef _FILTERPROC_H_ +#define _FILTERPROC_H_ + +// Qt includes. +#include <qobject.h> +#include <qstringlist.h> + +// KDE includes. +#include <kdemacros.h> +#include "kdeexportfix.h" + +class TalkerCode; +class KConfig; + +class KDE_EXPORT KttsFilterProc : virtual public QObject +{ + Q_OBJECT + +public: + enum FilterState { + fsIdle = 0, // Not doing anything. Ready to filter. + fsFiltering = 1, // Filtering. + fsStopping = 2, // Stop of filtering is in progress. + fsFinished = 3 // Filtering finished. + }; + + /** + * Constructor. + */ + KttsFilterProc( QObject *parent, const char *name ); + + /** + * Destructor. + */ + virtual ~KttsFilterProc(); + + /** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ + virtual bool supportsAsync(); + + /** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ + virtual bool asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Waits for a previous call to asyncConvert to finish. + */ + virtual void waitForFinished(); + + /** + * Returns the state of the Filter. + */ + virtual int getState(); + + /** + * Returns the filtered output. + */ + virtual QString getOutput(); + + /** + * Acknowledges the finished filtering. + */ + virtual void ackFinished(); + + /** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ + virtual void stopFiltering(); + + /** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ + virtual bool wasModified(); + + /** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ + virtual void setSbRegExp(const QString& re); + +signals: + /** + * Emitted when asynchronous filtering has completed. + */ + void filteringFinished(); + + /** + * Emitted when stopFiltering has been called and filtering has in fact stopped. + */ + void filteringStopped(); + + /** + * If an error occurs, Filter should signal the error and return input as output in + * convert method. If Filter should not be called in the future, perhaps because + * it could not find its configuration file, return False for keepGoing. + * @param keepGoing False if the filter should not be called in the future. + * @param msg Error message. + */ + void error(bool keepGoing, const QString &msg); +}; + +#endif // _FILTERPROC_H_ diff --git a/kttsd/libkttsd/kdeexportfix.h b/kttsd/libkttsd/kdeexportfix.h new file mode 100644 index 0000000..7c80d9c --- /dev/null +++ b/kttsd/libkttsd/kdeexportfix.h @@ -0,0 +1,27 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + kdelibs < 3.3.2 had a bug in the KDE_EXPORT macro. This file fixes this + by undefining it. + ------------------- + Copyright : (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _KDEEXPORTFIX_H_ +#define _KDEEXPORTFIX_H_ + +#include <kdeversion.h> +#if KDE_VERSION < KDE_MAKE_VERSION (3,3,2) +#undef KDE_EXPORT +#define KDE_EXPORT +#endif + +#endif // _KDEEXPORTFIX_H_ diff --git a/kttsd/libkttsd/kttsd_synthplugin.desktop b/kttsd/libkttsd/kttsd_synthplugin.desktop new file mode 100644 index 0000000..c6367af --- /dev/null +++ b/kttsd/libkttsd/kttsd_synthplugin.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=KTTSD +Name[zh_TW]=KTTSd +Type=ServiceType +X-KDE-ServiceType=KTTSD/SynthPlugin + +[PropertyDef::X-KDE-Languages] +Type=QStringList diff --git a/kttsd/libkttsd/notify.cpp b/kttsd/libkttsd/notify.cpp new file mode 100644 index 0000000..fd28587 --- /dev/null +++ b/kttsd/libkttsd/notify.cpp @@ -0,0 +1,177 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Notification Action constants and utility functions. + ------------------- + Copyright : (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + Current Maintainer: 2004 by 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; version 2 of the License. * + * * + ***************************************************************************/ + +// Qt includes. + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> + +// KDE includes. +#include <kconfig.h> +#include <klocale.h> +#include <kstaticdeleter.h> + +// KTTS includes. +#include "notify.h" + +static QStringList* s_actionNames = 0; +static KStaticDeleter<QStringList> s_actionNames_sd; + +static QStringList* s_actionDisplayNames = 0; +static KStaticDeleter<QStringList> s_actionDisplayNames_sd; + +static void notifyaction_init() +{ + if ( !s_actionNames ) + { + s_actionNames_sd.setObject(s_actionNames, new QStringList); + s_actionNames->append( "SpeakEventName" ); + s_actionNames->append( "SpeakMsg" ); + s_actionNames->append( "DoNotSpeak" ); + s_actionNames->append( "SpeakCustom" ); + + s_actionDisplayNames_sd.setObject(s_actionDisplayNames, new QStringList); + s_actionDisplayNames->append( i18n("Speak event name") ); + s_actionDisplayNames->append( i18n("Speak the notification message") ); + s_actionDisplayNames->append( i18n("Do not speak the notification") ); + s_actionDisplayNames->append( i18n("Speak custom text:") ); + } +} + +/*static*/ int NotifyAction::count() +{ + notifyaction_init(); + return s_actionNames->count(); +} + +/*static*/ QString NotifyAction::actionName( const int action ) +{ + notifyaction_init(); + return (*s_actionNames)[ action ]; +} + +/*static*/ int NotifyAction::action( const QString& actionName ) +{ + notifyaction_init(); + return s_actionNames->findIndex( actionName ); +} + +/*static*/ QString NotifyAction::actionDisplayName( const int action ) +{ + notifyaction_init(); + return (*s_actionDisplayNames)[ action ]; +} + +/*static*/ QString NotifyAction::actionDisplayName( const QString& actionName ) +{ + notifyaction_init(); + return (*s_actionDisplayNames)[ action( actionName ) ]; +} + +// -------------------------------------------------------------------- + +static QStringList* s_presentNames = 0; +static KStaticDeleter<QStringList> s_presentNames_sd; + +static QStringList* s_presentDisplayNames = 0; +static KStaticDeleter<QStringList> s_presentDisplayNames_sd; + +static void notifypresent_init() +{ + if ( !s_presentNames ) + { + s_presentNames_sd.setObject( s_presentNames, new QStringList ); + s_presentNames->append( "None" ); + s_presentNames->append( "Dialog" ); + s_presentNames->append( "Passive" ); + s_presentNames->append( "DialogAndPassive" ); + s_presentNames->append( "All" ); + + s_presentDisplayNames_sd.setObject( s_presentDisplayNames, new QStringList ); + s_presentDisplayNames->append( i18n("none") ); + s_presentDisplayNames->append( i18n("notification dialogs") ); + s_presentDisplayNames->append( i18n("passive popups") ); + s_presentDisplayNames->append( i18n("notification dialogs and passive popups") ); + s_presentDisplayNames->append( i18n("all notifications" ) ); + } +} + +/*static*/ int NotifyPresent::count() +{ + notifypresent_init(); + return s_presentNames->count(); +} + +/*static*/ QString NotifyPresent::presentName( const int present ) +{ + notifypresent_init(); + return (*s_presentNames)[ present ]; +} + +/*static*/ int NotifyPresent::present( const QString& presentName ) +{ + notifypresent_init(); + return s_presentNames->findIndex( presentName ); +} + +/*static*/ QString NotifyPresent::presentDisplayName( const int present ) +{ + notifypresent_init(); + return (*s_presentDisplayNames)[ present ]; +} + +/*static*/ QString NotifyPresent::presentDisplayName( const QString& presentName ) +{ + notifypresent_init(); + return (*s_presentDisplayNames)[ present( presentName ) ]; +} + +// -------------------------------------------------------------------- + +/** + * Retrieves the displayable name for an event source. + */ +/*static*/ QString NotifyEvent::getEventSrcName(const QString& eventSrc, QString& iconName) +{ + QString configFilename = eventSrc + QString::fromLatin1( "/eventsrc" ); + KConfig* config = new KConfig( configFilename, true, false, "data" ); + config->setGroup( QString::fromLatin1( "!Global!" ) ); + QString appDesc = config->readEntry( "Comment", i18n("No description available") ); + iconName = config->readEntry( "IconName" ); + delete config; + return appDesc; +} + +/** + * Retrieves the displayable name for an event from an event source. + */ +/*static*/ QString NotifyEvent::getEventName(const QString& eventSrc, const QString& event) +{ + QString eventName; + QString configFilename = eventSrc + QString::fromLatin1( "/eventsrc" ); + KConfig* config = new KConfig( configFilename, true, false, "data" ); + if ( config->hasGroup( event ) ) + { + config->setGroup( event ); + eventName = config->readEntry( QString::fromLatin1( "Comment" ), + config->readEntry( QString::fromLatin1( "Name" ))); + } + delete config; + return eventName; +} + diff --git a/kttsd/libkttsd/notify.h b/kttsd/libkttsd/notify.h new file mode 100644 index 0000000..2a8162d --- /dev/null +++ b/kttsd/libkttsd/notify.h @@ -0,0 +1,85 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Notification Action constants and utility functions. + ------------------- + Copyright : (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + Current Maintainer: 2004 by 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; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _NOTIFYACTION_H +#define _NOTIFYACTION_H + +#include <kdemacros.h> + +class QString; + +class KDE_EXPORT NotifyAction +{ + +public: + + enum NotifyActions + { + SpeakEventName, + SpeakMsg, + DoNotSpeak, + SpeakCustom + }; + + static int count(); + static QString actionName( const int action ); + static int action( const QString& actionName ); + static QString actionDisplayName( const int action ); + static QString actionDisplayName( const QString& actionName ); +}; + +// -------------------------------------------------------------------- + +class KDE_EXPORT NotifyPresent +{ + +public: + + enum NotifyPresentations + { + None, + Dialog, + Passive, + DialogAndPassive, + All + }; + + static int count(); + static QString presentName( const int present ); + static int present( const QString& presentName ); + static QString presentDisplayName( const int present ); + static QString presentDisplayName( const QString& presentName ); +}; + +// -------------------------------------------------------------------- + +class KDE_EXPORT NotifyEvent +{ + +public: + /** + * Retrieves the displayable name for an event source. + */ + static QString getEventSrcName(const QString& eventSrc, QString& iconName); + + /** + * Retrieves the displayable name for an event from an event source. + */ + static QString getEventName(const QString& eventSrc, const QString& event); +}; + +#endif // _NOTIFYACTION_H diff --git a/kttsd/libkttsd/player.h b/kttsd/libkttsd/player.h new file mode 100644 index 0000000..ff4d240 --- /dev/null +++ b/kttsd/libkttsd/player.h @@ -0,0 +1,76 @@ +/*************************************************************************** + begin : Sun Feb 17 2002 + copyright : (C) 2002 - 2004 by Scott Wheeler + email : wheeler@kde.org +***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef PLAYER_H +#define PLAYER_H + +#include <qobject.h> +#include <qstringlist.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include "kglobal.h" +#include "klocale.h" + +// #include "filehandle.h" + +class KDE_EXPORT Player : virtual public QObject +{ + Q_OBJECT + +public: + virtual ~Player() {} + +// virtual void play(const FileHandle &file = FileHandle::null()) = 0; + virtual void startPlay(const QString& file) = 0; + virtual void pause() = 0; + virtual void stop() = 0; + + virtual void setVolume(float volume = 1.0) = 0; + virtual float volume() const = 0; + + virtual bool playing() const = 0; + virtual bool paused() const = 0; + + virtual int totalTime() const = 0; + virtual int currentTime() const = 0; + virtual int position() const = 0; // in this case not really the percent + + virtual void seek(int seekTime) = 0; + virtual void seekPosition(int position) = 0; + + virtual QStringList getPluginList( const QCString& classname ) { + Q_UNUSED(classname); + return QStringList(); + } + virtual void setSinkName(const QString &sinkName) { Q_UNUSED(sinkName); } + virtual bool requireVersion(uint major, uint minor, uint micro) { + Q_UNUSED(major); + Q_UNUSED(minor); + Q_UNUSED(micro); + return true; + } + virtual void setDebugLevel(uint level) { Q_UNUSED(level); } + virtual void setPeriodSize(uint periodSize) { Q_UNUSED(periodSize); } + virtual void setPeriods(uint periods) {Q_UNUSED(periods); } + +protected: + Player(QObject* parent = 0, const char* name = 0, const QStringList& args=QStringList() ) : + QObject(parent, name) { + Q_UNUSED(args); + } + +}; + +#endif diff --git a/kttsd/libkttsd/pluginconf.cpp b/kttsd/libkttsd/pluginconf.cpp new file mode 100644 index 0000000..d2fb4b6 --- /dev/null +++ b/kttsd/libkttsd/pluginconf.cpp @@ -0,0 +1,245 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the templates for the configuration plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: 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; version 2 of the License. * + * * + ***************************************************************************/ + +// C++ library includes. +#include <stdlib.h> +#include <sys/param.h> + +// Qt includes. +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> +#include <kstandarddirs.h> + +// PluginConf includes. +#include "pluginconf.h" +#include "pluginconf.moc" + +/** +* Constructor +*/ +PlugInConf::PlugInConf( QWidget *parent, const char *name) : QWidget(parent, name){ + kdDebug() << "PlugInConf::PlugInConf: Running" << endl; + KGlobal::locale()->insertCatalogue("kttsd"); + QString systemPath(getenv("PATH")); + // kdDebug() << "Path is " << systemPath << endl; + m_path = QStringList::split(":", systemPath); + m_player = 0; +} + +/** +* Destructor. +*/ +PlugInConf::~PlugInConf(){ + kdDebug() << "PlugInConf::~PlugInConf: Running" << endl; + delete m_player; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void PlugInConf::load(KConfig* /*config*/, const QString& /*configGroup*/){ + kdDebug() << "PlugInConf::load: Running" << endl; +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void PlugInConf::save(KConfig* /*config*/, const QString& /*configGroup*/){ + kdDebug() << "PlugInConf::save: Running" << endl; +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void PlugInConf::defaults(){ + kdDebug() << "PlugInConf::defaults: Running" << endl; +} + +/** +* Indicates whether the plugin supports multiple instances. Return +* False if only one instance of the plugin can run at a time. +* @return True if multiple instances are possible. +* +* It is assumed that most plugins can support multiple instances. +* A plugin must override this method and return false if it +* cannot support multiple instances. +*/ +bool PlugInConf::supportsMultiInstance() { return true; } + +/** +* This function informs the plugin of the desired language to be spoken +* by the plugin. The plugin should attempt to adapt itself to the +* specified language code, choosing sensible defaults if necessary. +* If the passed-in code is QString::null, no specific language has +* been chosen. +* @param lang The desired language code or Null if none. +* +* If the plugin is unable to support the desired language, that is OK. +*/ +void PlugInConf::setDesiredLanguage(const QString& /*lang*/ ) { } + +/** +* Return fully-specified talker code for the configured plugin. This code +* uniquely identifies the configured instance of the plugin and distinquishes +* one instance from another. If the plugin has not been fully configured, +* i.e., cannot yet synthesize, return QString::null. +* @return Fully-specified talker code. +*/ +QString PlugInConf::getTalkerCode() { return QString::null; } + +/** +* Return a list of all the languages currently supported by the plugin. +* Note that as the user configures your plugin, the language choices may become +* narrower. For example, once the user has picked a voice file, the language +* may be determined. If your plugin has just been added and no configuration +* choices have yet been made, return a list of all possible languages the +* plugin might support. +* If your plugin cannot yet determine the languages supported, return Null. +* If your plugin can support any language, return Null. +* @return A QStringList of supported language codes, or Null if unknown. +*/ +QStringList PlugInConf::getSupportedLanguages() { return QStringList(); } + +/** +* Return the full path to any program in the $PATH environmental variable +* @param name The name of the file to search for. +* @returns The path to the file on success, a blank QString +* if its not found. +*/ +QString PlugInConf::getLocation(const QString &name) { + // Iterate over the path and see if 'name' exists in it. Return the + // full path to it if it does. Else return an empty QString. + + // If it's a file or a symlink pointing to a file, that's cool. + QFileInfo fileinfo(name); + if (fileinfo.isFile() || (fileinfo.isSymLink() && QFileInfo(fileinfo.readLink()).isFile())) + return name; + kdDebug() << "PluginConf::getLocation: Searching for " << name << " in the path.." << endl; + kdDebug() << m_path << endl; + for(QStringList::iterator it = m_path.begin(); it != m_path.end(); ++it) { + QString fullName = *it; + + fullName += "/"; + fullName += name; + fileinfo.setFile(fullName); + // The user either has the directory of the file in the path... + if(fileinfo.isFile() || (fileinfo.isSymLink() && QFileInfo(fileinfo.readLink()).isFile())) { + return fullName; +// kdDebug() << "PluginConf:getLocation: " << fullName << endl; + } + // ....Or the file itself in the path (slightly freaky but hey.) + else if(QFileInfo(*it).baseName().append(QString(".").append(QFileInfo(*it).extension())) == name) { + return fullName; +// kdDebug() << "PluginConf:getLocation: " << fullName << endl; + } + } + return ""; +} + +/** +* Breaks a language code into the language code and country code (if any). +* @param languageCode Language code. +* @return countryCode Just the country code part (if any). +* @return Just the language code part. +*/ +QString PlugInConf::splitLanguageCode(const QString& languageCode, QString& countryCode) +{ + QString locale = languageCode; + QString langCode; + QString charSet; + KGlobal::locale()->splitLocale(locale, langCode, countryCode, charSet); + return langCode; +} + +/*static*/ QString PlugInConf::realFilePath(const QString &filename) +{ + char realpath_buffer[MAXPATHLEN + 1]; + memset(realpath_buffer, 0, MAXPATHLEN + 1); + + /* If the path contains symlinks, get the real name */ + if (realpath( QFile::encodeName(filename).data(), realpath_buffer) != 0) { + // succes, use result from realpath + return QFile::decodeName(realpath_buffer); + } + return filename; +} + +/*static*/ QString PlugInConf::testMessage(const QString& languageCode) +{ + QString key = "Name[" + languageCode + "]"; + QString result; + QString def; + QFile file(locate("data", "kttsd/kcmkttsd_testmessage.desktop")); + if (file.open(IO_ReadOnly)) + { + QTextStream stream(&file); + stream.setEncoding(QTextStream::UnicodeUTF8); + while ( !stream.atEnd() ) { + QString line = stream.readLine(); // line of text excluding '\n' + QStringList keyAndValue = QStringList::split("=", line); + if (keyAndValue.count() == 2) + { + if (keyAndValue[0] == key) + { + result = keyAndValue[1]; + break; + } + if (keyAndValue[0] == "Name") def = keyAndValue[1]; + } + } + file.close(); + } + if (result.isEmpty()) + { + result = def; + if (result.isEmpty()) result = "The text-to-speech system seems to be functioning properly."; + } + return result; +} + +/** +* Player object that can be used by the plugin for testing playback of synthed files. +*/ +void PlugInConf::setPlayer(TestPlayer* player) { m_player = player; } +TestPlayer* PlugInConf::getPlayer() { return m_player; } + diff --git a/kttsd/libkttsd/pluginconf.h b/kttsd/libkttsd/pluginconf.h new file mode 100644 index 0000000..4c4647c --- /dev/null +++ b/kttsd/libkttsd/pluginconf.h @@ -0,0 +1,378 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the template for the configuration plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: 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; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _PLUGINCONF_H_ +#define _PLUGINCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> +#include <kdemacros.h> +#include "kdeexportfix.h" + +// KTTS includes. +#include "testplayer.h" + +/** +* @interface PlugInConf +* +* pluginconf - the KDE Text-to-Speech Deamon Plugin Configuration API. +* +* @version 1.0 Draft 2 +* +* This class defines the interface that plugins to KTTSMGR must implement. +* +* @warning The pluginconf interface is still being developed and is likely +* to change in the future. +* +* A KTTSD Plugin interfaces between KTTSD and a speech engine. +* A PlugInConf provides an on-screen widget for configuring the plugin for use +* with KTTSD. +* +* @section guidelines General Guidelines +* +* - The configuration widget should be no larger than TODO pixels. +* - Do not supply Load, Save, Cancel, or OK buttons. Those are provided by KTTSMGR. +* - Try to supply a Test button so that users can test the configuration before +* saving it. +* - Your configuration widget will be running inside a KPart. +* - Whenever the user changes something in your on-screen widget, emit the +* @ref changed signal. +* - If a plugin can automatically configure itself, i.e., locate voice files, +* set default values, etc., it should do so when it is first added to KTTSMGR. +* +* @section multiinstance Multiple Instances +* +* If it is possible to run multiple simultaneous instances of your synthesis engine, +* return True from the @ref supportsMultiInstance method. The user will be able to +* configure multiple instances of your plugin, each with a different set of +* talker attributes. +* +* If you cannot run multiple simultaneous instances of your synthesis engine, +* or your plugin has a fixed set of talker attributes (only one language, voice, +* gender, volume, and speed), return False from @ref supportsMultiInstance. +* +* @section language Language Support +* +* Some plugins support only one language. For them, return the appropriate language +* code when @ref getSupportedLanguages is called. +* +* If your plugin can support multiple languages, your task is a little more +* complicated. The best way to handle this is to install a @e voices file with +* your plugin that lists all the supported languages, voice files, genders, etc. +* that are possible. When your plugin is added to KTTSMGR, +* @ref getSupportedLanguages will be called. Return a list of all the possible +* languages supported, even if the user hasn't yet picked a voice file in your +* configuration, or even told your where the voice files are. +* +* There are three ways that users and applications pick a language code for your +* plugin: +* - The user picks a code from among the languages you returned in +* @ref getSupportedLanguages, or +* - The user picks your plugin and uses your configuration widget to pick a voice +* file or other configuration option that determines the language, or +* - An application requests a plugin with specific language support. +* +* If possible, avoid making the user pick a language code in your plugin. +* +* In the first and third cases, the chosen language code will be passed to your +* plugin when @ref setDesiredLanguage is called. If you can satisfy this +* language code, good, but it is possible that once the user has begun +* configuring your plugin, you find that you cannot support the desired +* language. Perhaps a needed voice file is missing. That is OK. +* You'll inform KTTSMGR of the actual language code when KTTSMGR +* calls @ref getTalkerCode (see below). Note that you should not limit the +* users choices based on the @ref setDesiredLanguage. A user might start +* out configuring your plugin for one language, and then change his or her +* mind to a different language. +* +* Also note that language codes may also include an appended country code. +* For example, "en_GB" for British English. When @ref getSupportedLanguages is +* called, you should return as specific a list as possible. For example, +* if your plugin supports both American and British English, your returned +* list would include "en_GB" and "en_US". When @ref setDesiredLanguage is +* called, a country code may or may not be included. If included and your +* plugin supports the language, but not the specific country variant, +* your plugin should nevertheless attempt to satisfy the request, returning +* the actual supported language and country when @ref getTalkerCode is called. +* +* @section talkercodes Talker Codes +* +* Review the section on Talkers in kspeech.h. +* +* When your plugin is added to the KTTSMGR, @ref getSupportedLanguages +* will be called followed by @ref setDesiredLanguage and @ref load. +* Note that the configuration file will most likely be empty when +* @ref load is called. + +* Next, @ref getTalkerCode +* will be called. If your plugin can automatically configure itself to the desired +* language, it should do so and return a fully-specified talker code. If your +* plugin is not yet ready and requires user help, return QString::null. Note that +* @ref setDesiredLanguage may be Null, in which case, you should allow the +* user to configure your plugin to any of your supported languages. +* +* When your plugin has been configured enough to begin synthesis, return a +* fully-specified talker code in @ref getTalkerCode(). +* +* Here is guidance for what you should return for each of the talker attributes +* when @ref getTalkerCode is called: +* +* - @e lang. If user has completed configuring your plugin, i.e., it is +* ready to begin synthesizing, return the ISO 639-1 language code +* for the language it can synthesize. If your plugin is not yet +* fully configured, you should return QString::null for the entire +* talker code. If your plugin supports a specific national version +* of a language, that should also be included using the ISO 3166 +* country code separated from the language code by underscore (_). +* For example, if your plugin synthesizes American English, you would +* return "en_US". If British English, "en_BR". And if +* non-specific English, just "en". +* - @e synthesizer. The name of your plugin. Keep short, but long enough to +* distinquish different implementations. For example, +* Festival Int, Flite, Hadifix. Use only letters, numbers +* spaces, and underscores (_) in your plugin name. +* - @e gender. May be "male", "female", or "neutral". +* - @e name. The voice code. If your plugin does not support voices, +* return "fixed". +* - @e volume. May be "medium", "loud", or "soft". If your plugin does not support +* configurable volume, return "medium". +* - @e rate. May be "medium", "fast", or "slow". If your plugin does not support +* configurable speed, return "medium". +* +* The order of the attributes you return does not matter. Here is an example of +* a fully-specified talker code. +* +* lang="en" name="Kal" gender="male" volume="soft" rate="fast" +* synthesizer="Festival Interactive" +* +* Do not return translated values for the Talker Code attributes. All English. +* +* Each time your plugin emits the @ref changed signal, @ref getTalkerCode will be called. +* The configuration dialog OK button will be disabled until you return a non-null +* Talker Code. +* +* It is possible that your plugin does not know the language supported. The generic +* Command plugin is example of such a case, since the user enters an arbitrary command. +* In this case, return the value from the @ref setDesiredLanguage call. It is possible +* that @ref setDesiredLanguage is Null. That is OK. In this case, KTTSMGR will prompt +* the user for the language code. +* +* @section loadandsavemethods Load and Save Methods +* +* The @ref load and @ref save methods are called by KTTSMGR so that your plugin +* can load and save configuration options from the configuration file. +* These methods have two parameters, a @e config object and a @e configGroup string. +* +* Plugins that do not support multiple instances (return False from +* @ref supportsMultiInstance), should simply call config->setGroup(configGroup) +* before loading or saving their configuration. +* +* If your plugin supports multiple instances, it is slightly more complicated. +* Typically, there will be configuration options that apply to all instances +* of the plugin and there will be options that apply only to the specific +* configured instance of the plugin. To load or save the instance-specific +* options, call config->setGroup(configGroup). For options that apply +* to all instances of the plugin, call config->setGroup() with a group name +* that contains your plugin's name. For example, +* config->setGroup("Festival Defaults"). +* +* For example, when first added to KTTSMGR, the Festival plugin needs to know the path to +* the directory containing all the installed voice files. It is best for a plugin +* to try to locate these resources automatically, but if it can't find them, +* when the user has told it where they are, it is a good idea to save this information +* in the all-instances group. In this way, the next time the plugin +* is added to KTTSMGR, or another instance is added, it will be able to find them +* automatically. +* +* @ref setDesiredLanguage is always called just prior to @ref load, therefore +* it is not necessary to save the language code, unless your plugin needs it in +* order to synthesize speech. +*/ + +class KDE_EXPORT PlugInConf : public QWidget{ + Q_OBJECT + + public: + /** + * Constructor + */ + PlugInConf( QWidget *parent = 0, const char *name = 0); + + /** + * Destructor + */ + virtual ~PlugInConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + * + * @see loadandsavemethods + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + * + * @ref setDesiredLanguage is always called just prior to @ref load, therefore + * it is not necessary to save the language code, unless your plugin needs it in + * order to synthesize speech. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can run at a time, or + * if your plugin is limited to a single language, voice, gender, volume, + * and speed. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * This function informs the plugin of the desired language to be spoken + * by the plugin. The plugin should attempt to adapt itself to the + * specified language code, choosing sensible defaults if necessary. + * If the passed-in code is QString::null, no specific language has + * been chosen. + * @param lang The desired language code or Null if none. + * + * If the plugin is unable to support the desired language, that is OK. + * Language codes are given by ISO 639-1 and are in lowercase. + * The code may also include an ISO 3166 country code in uppercase + * separated from the language code by underscore (_). For + * example, en_GB. If your plugin supports the given language, but + * not the given country, treat it as though the country + * code were not specified, i.e., adapt to the given language. + */ + virtual void setDesiredLanguage(const QString &lang); + + /** + * Return fully-specified talker code for the configured plugin. This code + * uniquely identifies the configured instance of the plugin and distinquishes + * one instance from another. If the plugin has not been fully configured, + * i.e., cannot yet synthesize, return QString::null. + * @return Fully-specified talker code. + */ + virtual QString getTalkerCode(); + + /** + * Return a list of all the languages possibly supported by the plugin. + * If your plugin can support any language, return Null. + * @return A QStringList of supported language and optional country + * codes, or Null if any. + * + * The languge codes are given in ISO 639-1. Lowercase should be used. + * If your plugin supports various national forms of a language, ISO 3166 + * country codes should also be include in upperase and separated from + * the language code with underscore (_). Examples: + * en + * en_US + * en_GB + * es + * es_CL + * The list you return should be as specific as practicable. + */ + virtual QStringList getSupportedLanguages(); + + /** + * Player object that can be used by the plugin for testing playback of synthed files. + */ + void setPlayer(TestPlayer* player); + TestPlayer* getPlayer(); + + static QString realFilePath(const QString &filename); + + static QString testMessage(const QString& languageCode); + + public slots: + /** + * This slot is used internally when the configuration is changed. It is + * typically connected to signals from the widgets of the configuration + * and should emit the @ref changed signal. + */ + void configChanged(){ + kdDebug() << "PlugInConf::configChanged: Running"<< endl; + emit changed(true); + }; + + signals: + /** + * This signal indicates that the configuration has been changed. + * It should be emitted whenever user changes something in the configuration widget. + */ + void changed(bool); + + protected: + /** + * Searches the $PATH variable for any file. If that file exists in the PATH, or + * is contained in any directory in the PATH, it returns the full path to it. + * @param name The name of the file to search for. + * @returns The path to the file on success, a blank QString + * if its not found. + */ + QString getLocation(const QString &name); + + /** + * Breaks a language code into the language code and country code (if any). + * @param languageCode Language code. + * @return countryCode Just the country code part (if any). + * @return Just the language code part. + */ + QString splitLanguageCode(const QString& languageCode, QString& countryCode); + + /// The system path in a QStringList. + QStringList m_path; + + TestPlayer* m_player; +}; + +#endif //_PLUGINCONF_H_ diff --git a/kttsd/libkttsd/pluginproc.cpp b/kttsd/libkttsd/pluginproc.cpp new file mode 100644 index 0000000..5c835a6 --- /dev/null +++ b/kttsd/libkttsd/pluginproc.cpp @@ -0,0 +1,292 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the template for the processing plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: 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; version 2 of the License. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qstring.h> +#include <qtextcodec.h> + +// KDE includes. +#include <kdebug.h> +#include <kstandarddirs.h> +#include <klocale.h> + +// PlugInProc includes. +#include "pluginproc.h" +#include "pluginproc.moc" + +/** +* Constructor +*/ +PlugInProc::PlugInProc( QObject *parent, const char *name) : QObject(parent, name){ + // kdDebug() << "PlugInProc::PlugInProc: Running" << endl; +} + +/** +* Destructor +*/ +PlugInProc::~PlugInProc(){ + // kdDebug() << "PlugInProc::~PlugInProc: Running" << endl; +} + +/** +* Initializate the speech plugin. +*/ +bool PlugInProc::init(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "PlugInProc::init: Running" << endl; + return false; +} + +/** +* Say a text. Synthesize and audibilize it. +* @param text The text to be spoken. +* +* If the plugin supports asynchronous operation, it should return immediately. +*/ +void PlugInProc::sayText(const QString& /*text*/){ + // kdDebug() << "PlugInProc::sayText: Running" << endl; +} + +/** +* Synthesize text into an audio file, but do not send to the audio device. +* @param text The text to be synthesized. +* @param suggestedFilename Full pathname of file to create. The plugin +* may ignore this parameter and choose its own +* filename. KTTSD will query the generated +* filename using getFilename(). +* +* If the plugin supports asynchronous operation, it should return immediately. +*/ +void PlugInProc::synthText(const QString& /*text*/, const QString& /*suggestedFilename*/) { } + +/** +* Get the generated audio filename from synthText. +* @return Name of the audio file the plugin generated. +* Null if no such file. +* +* The plugin must not re-use the filename. +*/ +QString PlugInProc::getFilename() { return QString::null; } + +/** +* Stop current operation (saying or synthesizing text). +* This function only makes sense in asynchronus modes. +* The plugin should return to the psIdle state. +*/ +void PlugInProc::stopText(){ + // kdDebug() << "PlugInProc::stopText: Running" << endl; +} + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @ref pluginState +*/ +pluginState PlugInProc::getState() { return psIdle; } + +/** +* Acknowledges a finished state and resets the plugin state to psIdle. +* +* If the plugin is not in state psFinished, nothing happens. +* The plugin may use this call to do any post-processing cleanup, +* for example, blanking the stored filename (but do not delete the file). +* Calling program should call getFilename prior to ackFinished. +*/ +void PlugInProc::ackFinished() { } + +/** +* Returns True if the plugin supports asynchronous processing, +* i.e., returns immediately from sayText or synthText. +* @return True if this plugin supports asynchronous processing. +*/ +bool PlugInProc::supportsAsync() { return false; } + +/** +* Returns True if the plugin supports synthText method, +* i.e., is able to synthesize text to a sound file without +* audibilizing the text. +* @return True if this plugin supports synthText method. +*/ +bool PlugInProc::supportsSynth() { return false; } + +/** +* Returns the name of an XSLT stylesheet that will convert a valid SSML file +* into a format that can be processed by the synth. For example, +* The Festival plugin returns a stylesheet that will convert SSML into +* SABLE. Any tags the synth cannot handle should be stripped (leaving +* their text contents though). The default stylesheet strips all +* tags and converts the file to plain text. +* @return Name of the XSLT file. +*/ +QString PlugInProc::getSsmlXsltFilename() +{ + return KGlobal::dirs()->resourceDirs("data").last() + "kttsd/xslt/SSMLtoPlainText.xsl"; +} + +/** +* Given the name of a codec, returns the QTextCodec for the name. +* Handles the following "special" codec names: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +* @param codecName Name of desired codec. +* @return The codec object. Calling program must not delete this object +* as it is a reference to an existing QTextCodec object. +* +* Caution: Do not pass translated codec names to this routine. +*/ +/*static*/ QTextCodec* PlugInProc::codecNameToCodec(const QString &codecName) +{ + QTextCodec* codec = 0; + if (codecName == "Local") + codec = QTextCodec::codecForLocale(); + else if (codecName == "Latin1") + codec = QTextCodec::codecForName("ISO8859-1"); + else if (codecName == "Unicode") + codec = QTextCodec::codecForName("utf16"); + else + codec = QTextCodec::codecForName(codecName.latin1()); + if (!codec) + { + kdDebug() << "PluginProc::codecNameToCodec: Invalid codec name " << codecName << endl; + kdDebug() << "PluginProc::codecNameToCodec: Defaulting to ISO 8859-1" << endl; + codec = QTextCodec::codecForName("ISO8859-1"); + } + return codec; +} + +/** +* Builds a list of codec names, suitable for display in a QComboBox. +* The list includes the 3 special codec names (translated) at the top: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +*/ +/*static*/ QStringList PlugInProc::buildCodecList() +{ + // kdDebug() << "PlugInConf::buildCodecList: Running" << endl; + QStringList codecList; + QString local = i18n("Local")+" ("; + local += QTextCodec::codecForLocale()->name(); + local += ")"; + codecList.append(local); + codecList.append(i18n("Latin1")); + codecList.append(i18n("Unicode")); + for (int i = 0; (QTextCodec::codecForIndex(i)); ++i ) + codecList.append(QTextCodec::codecForIndex(i)->name()); + return codecList; +} + +/** +* Given the name of a codec, returns index into the codec list. +* Handles the following "special" codec names: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +* @param codecName Name of the codec. +* @param codecList List of codec names. The first 3 entries may be translated names. +* @return QTextCodec object. Caller must not delete this object. +* +* Caution: Do not pass translated codec names to this routine in codecName parameter. +*/ +/*static*/ int PlugInProc::codecNameToListIndex(const QString &codecName, const QStringList &codecList) +{ + int codec; + if (codecName == "Local") + codec = PlugInProc::Local; + else if (codecName == "Latin1") + codec = PlugInProc::Latin1; + else if (codecName == "Unicode") + codec = PlugInProc::Unicode; + else { + codec = PlugInProc::Local; + const uint codecListCount = codecList.count(); + for (uint i = PlugInProc::UseCodec; i < codecListCount; ++i ) + if (codecName == codecList[i]) + codec = i; + } + return codec; +} + +/** +* Given index into codec list, returns the codec object. +* @param codecNum Index of the codec. +* @param codecList List of codec names. The first 3 entries may be translated names. +* @return QTextCodec object. Caller must not delete this object. +*/ +/*static*/ QTextCodec* PlugInProc::codecIndexToCodec(int codecNum, const QStringList &codecList) +{ + QTextCodec* codec = 0; + switch (codecNum) { + case PlugInProc::Local: + codec = QTextCodec::codecForLocale(); + break; + case PlugInProc::Latin1: + codec = QTextCodec::codecForName("ISO8859-1"); + break; + case PlugInProc::Unicode: + codec = QTextCodec::codecForName("utf16"); + break; + default: + codec = QTextCodec::codecForName(codecList[codecNum].latin1()); + break; + } + if (!codec) + { + kdDebug() << "PlugInProc::codecIndexToCodec: Invalid codec index " << codecNum << endl; + kdDebug() << "PlugInProc::codecIndexToCodec: Defaulting to ISO 8859-1" << endl; + codec = QTextCodec::codecForName("ISO8859-1"); + } + return codec; +} + +/** +* Given index into codec list, returns the codec Name. +* Handles the following "special" codec names: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +* @param codecNum Index of the codec. +* @param codecList List of codec names. The first 3 entries may be translated names. +* @return Untranslated name of the codec. +*/ +/*static*/ QString PlugInProc::codecIndexToCodecName(int codecNum, const QStringList &codecList) +{ + QString codecName; + switch (codecNum) { + case PlugInProc::Local: + codecName = "Local"; + break; + case PlugInProc::Latin1: + codecName = "Latin1"; + break; + case PlugInProc::Unicode: + codecName = "Unicode"; + break; + default: + if ((uint)codecNum < codecList.count()) + codecName = codecList[codecNum]; + else + { + kdDebug() << "PlugInProc::codecIndexToCodec: Invalid codec index " << codecNum << endl; + kdDebug() << "PlugInProc::codecIndexToCodec: Defaulting to ISO 8859-1" << endl; + codecName = "ISO8859-1"; + } + } + return codecName; +} diff --git a/kttsd/libkttsd/pluginproc.h b/kttsd/libkttsd/pluginproc.h new file mode 100644 index 0000000..c271ad3 --- /dev/null +++ b/kttsd/libkttsd/pluginproc.h @@ -0,0 +1,466 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the template for the processing plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + Copyright : (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: 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; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _PLUGINPROC_H_ +#define _PLUGINPROC_H_ + +#include <qobject.h> +#include <qstring.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include <kconfig.h> + +/** +* @interface PlugInProc +* +* pluginproc - the KDE Text-to-Speech Deamon Plugin API. +* +* @version 1.0 Draft 1 +* +* This class defines the interface that plugins to KTTSD must implement. +* +* @warning The pluginproc interface is still being developed and is likely +* to change in the future. +* +* A KTTSD Plugin interfaces between KTTSD and a speech engine. It provides the methods +* used by KTTSD to synthesize and/or audibilize text into speech. +* +* @section Goals +* +* The ideal plugin has the following features (listed most important to least important): +* - It can synthesize text into an audio file without sending the audio to +* the audio device. If the plugin can do this, the @ref supportsSynth function +* should return True. +* - When @ref stopText is called, is able to immediately stop an in-progress +* synthesis or saying operation. +* - It can operate asynchronously, i.e., returns immediately from a +* @ref sayText or @ref synthText call and emits signals @ref sayFinished or +* @ref synthFinished when completed. If the plugin can do this, the @ref supportsAsync +* function should return True. +* +* As a plugin author, your goal is to provide all 3 of these features. However, +* the speech engine you are working with might not be able to support all three +* features. +* +* If a plugin cannot do all 3 of the features above, the next best combinations +* are (from best to worst): +* +* - @ref supportsSynth returns True, @ref supportsAsync returns False, and +* @stopText is able to immediately stop synthesis. +* - @ref supportsSynth returns True, @ref supportsAsync returns False, and +* @stopText returns immediately without stopping synthesis. +* - @ref supportsAsync returns True, @ref supportsSynth returns False, and +* @ref stopText is able to immediately stop saying. +* - Both @ref supportsSynth and @ref supportsAsync both return False, and +* @ref stopText is able to immediately stop saying. +* - @ref supportsAsync returns True, @ref supportsSynth returns False, and +* @ref stopText returns immediately without stopping saying. +* - Both @ref supportsSynth and @ref supportsAsync both return False, and +* @ref stopText returns immediately without stopping saying. +* +* Notice that aynchronous support is not essential because KTTSD is able to +* provide aynchronous support by running the plugin in a separate thread. +* The ability to immediately stop audio output (or support separate synthesis +* only) is more important. +* +* @section Minimum Implementations +* +* All plugins should implement @ref init in order to initialize the speech engine, +* set language codes, etc. +* +* If @ref supportsSynth return False, a plugin must implement @ref sayText . +* +* If @ref supportsSynth returns True, a plugin must implement the following methods: +* - @ref synthText +* - @ref getFilename +* - @ref ackFinished +* The plugin need not implement @ref sayText . +* +* If @ref supportsAsync returns True, the plugin must implement @ref getState . +* +* If @ref supportsAsync returns True, and @ref supportsSynth returns True, +* a plugin must emit @ref synthFinished signal when synthesis is completed. +* +* If @ref supportsAsync returns True, and @ref supportsSynth returns False, +* a plugin must emit @ref sayFinished signal when saying is completed. +* +* If @ref supportsAsync returns False, do not emit signals @ref sayFinished +* or @ref synthFinished . +* +* @section Implementation Guidelines +* +* In no case, will a plugin need to perform more than one @ref sayText or +* @ref synthText at a time. In other words, in asynchronous mode, KTTSD will +* not call @ref sayText or @ref synthText again until @ref @getState returns +* psFinished. +* +* If @ref supportsAsync returns False, KTTSD will run the plugin in a separate +* QThread. As a consequence, the plugin must not make use of the KDE Library, +* when @ref sayText or @ref synthText is called, +* with the exception of KProcess and family (KProcIO, KShellProcess). +* This restriction comes about because the KDE Libraries make use of the +* main Qt event loop, which unfortunately, runs only in the main thread. +* This restriction will likely be lifted in Qt 4 and later. +* +* Since the KDE library is not available from the @ref sayText and @ref synthText methods, +* it is best if the plugin reads configuration settings in the @ref init method. +* The KConfig object is passed as an argument to @ref init . +* +* If the synthesis engine requires a long initialization time (more than a second), +* it is best if the plugin loads the speech engine from the @ref init method. +* Otherwise, it will be more memory efficient to wait until @ref sayText or +* @ref synthText is called, because it is possible that the plugin will be created +* and initialized, but never used. +* +* All plugins, whether @ref supportsAsync returns True or not, should try to +* implement @ref stopText . If a plugin returns False from @ref supportsAsync, +* @ref stopText will be called from the main thread, while @ref sayText and/or +* @ref synthText will be called from a separate thread. Hence, it will be +* possible for @ref stopText to be called while @ref sayText or @ref synthText is +* running. Keep this in mind when implementing the code. +* +* If the plugin returns True from @ref supportsAsync, you will of course +* need to deal with similar issues. If you have to use QThreads +* to implement asynchronous support, do not be concerned about emitting +* the @ref sayFinished or @ref synthFinished signals from your threads, since +* KTTSD will convert the received signals into postEvents and +* return immediately. +* +* If it is not possible for @ref stopText to stop an in-progress operation, it +* must not wait for the operation to complete, since this would block KTTSD. +* Instead, simply return immediately. Usually, KTTSD will perform other operations +* while waiting for the plugin to complete its operation. (See threadedplugin.cpp.) +* +* If the @ref stopText implementation returns before the operation has actually +* completed, it must emit the @ref stopped() signal when it is actually completed. +* +* If a plugin returns True from @ref supportsAsync, and @ref stopText is called, +* when the plugin has stopped or completed the operation, it must return psIdle +* on the next call to @ref getState ; not psFinished. The following state diagram +* might be helpful to understand this: +* + @verbatim + psIdle <<---------------------------------------------------------- + / \ ^ + psSaying psSynthing --- stopText called and operation completed -->> ^ + \ / ^ + psFinished --- ackFinished called ------------------------------->> ^ + @endverbatim +* +* If your plugin can't immediately stop an in-progress operation, the easiest +* way to handle this is to set a flag when stopText is called, and then in your +* getState() implementation, if the operation has completed, change the +* psFinished state to psIdle, if the flag is set. See the flite plugin for +* example code. +* +* If a plugin returns True from @ref supportsSynth, KTTSD will pass a suggested +* filename in the @ref synthText call. The plugin should synthesize the text +* into an audio file with the suggested name. If the synthesis engine does not +* permit this, i.e., it will pick a filename of its own, that is OK. In either +* case, the actual filename produced should be returned in @ref getFilename . +* In no case may the plugin re-use this filename once @ref getFilename has been +* called. If for some reason the synthesis engine cannot support this, the +* plugin should copy the file to the suggested filename. The file must not be +* locked when @ref getFilename is called. The file will be deleted when +* KTTSD is finished using it. +* +* The preferred audio file format is wave, since this is the only format +* guaranteed to be supported by KDE (aRts). Other formats may or may not be +* supported on a user's machine. +* +* The plugin destructor should take care of terminating the speech engine. +* +* @section Error-handling Error Handling +* +* Plugins may emit the @ref error signal when an error occurs. +* +* When an error occurs, plugins should attempt to recover as best they can and +* continue accepting @ref sayText or @ref synthText calls. For example, +* if a speech engine emits an error in response to certain characters embedded +* in synthesizing text, the plugin should discard the text and +* emit signal @ref error with True as the first argument and the speech +* engine's error message as the second argument. The plugin should then +* treat the operation as a completed operation, i.e., return psFinished when +* @ref getState is called. +* +* If the speech engine crashes, the plugin should emit signal @ref error with +* True as the first argument and then attempt to restart the speech engine. +* The plugin will need to implement some protection against an infinite +* restart loop and emit the @ref error signal with False as the first argument +* if this occurs. +* +* If a plugin emits the @ref error signal with False as the first argument, +* KTTSD will no longer call the plugin. +* +* @section PlugInConf +* +* The plugin should implement a configuration dialog where the user can specify +* options specific to the speech engine. This dialog is displayed in the KDE +* Control Center and also in the KTTS Manager (kttsmgr). See pluginconf.h. +* +* If the user changes any of the settings while the plugin is created, +* the plugin will be destroyed and re-created. +*/ + +class QTextCodec; + +enum pluginState +{ + psIdle = 0, /**< Plugin is not doing anything. */ + psSaying = 1, /**< Plugin is synthesizing and audibilizing. */ + psSynthing = 2, /**< Plugin is synthesizing. */ + psFinished = 3 /**< Plugin has finished synthesizing. Audio file is ready. */ +}; + +class KDE_EXPORT PlugInProc : virtual public QObject{ + Q_OBJECT + + public: + enum CharacterCodec { + Local = 0, + Latin1 = 1, + Unicode = 2, + UseCodec = 3 + }; + + /** + * Constructor. + */ + PlugInProc( QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + * Plugin must terminate the speech engine. + */ + virtual ~PlugInProc(); + + /** + * Initialize the speech engine. + * @param config Settings object. + * @param configGroup Settings Group. + * + * Sample code for reading configuration: + * + @verbatim + config->setGroup(configGroup); + m_fliteExePath = config->readEntry("FliteExePath", "flite"); + kdDebug() << "FliteProc::init: path to flite: " << m_fliteExePath << endl; + config->setGroup(configGroup); + @endverbatim + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Say a text. Synthesize and audibilize it. + * @param text The text to be spoken. + * + * If the plugin supports asynchronous operation, it should return immediately + * and emit sayFinished signal when synthesis and audibilizing is finished. + * It must also implement the @ref getState method, which must return + * psFinished, when saying is completed. + */ + virtual void sayText(const QString &text); + + /** + * Synthesize text into an audio file, but do not send to the audio device. + * @param text The text to be synthesized. + * @param suggestedFilename Full pathname of file to create. The plugin + * may ignore this parameter and choose its own + * filename. KTTSD will query the generated + * filename using getFilename(). + * + * If the plugin supports asynchronous operation, it should return immediately + * and emit @ref synthFinished signal when synthesis is completed. + * It must also implement the @ref getState method, which must return + * psFinished, when synthesis is completed. + */ + virtual void synthText(const QString &text, const QString &suggestedFilename); + + /** + * Get the generated audio filename from call to @ref synthText. + * @return Name of the audio file the plugin generated. + * Null if no such file. + * + * The plugin must not re-use or delete the filename. The file may not + * be locked when this method is called. The file will be deleted when + * KTTSD is finished using it. + */ + virtual QString getFilename(); + + /** + * Stop current operation (saying or synthesizing text). + * Important: This function may be called from a thread different from the + * one that called sayText or synthText. + * If the plugin cannot stop an in-progress @ref sayText or + * @ref synthText operation, it must not block waiting for it to complete. + * Instead, return immediately. + * + * If a plugin returns before the operation has actually been stopped, + * the plugin must emit the @ref stopped signal when the operation has + * actually stopped. + * + * The plugin should change to the psIdle state after stopping the + * operation. + */ + virtual void stopText(); + + /** + * Return the current state of the plugin. + * This function only makes sense in asynchronous mode. + * @return The pluginState of the plugin. + * + * @see pluginState + */ + virtual pluginState getState(); + + /** + * Acknowledges a finished state and resets the plugin state to psIdle. + * + * If the plugin is not in state psFinished, nothing happens. + * The plugin may use this call to do any post-processing cleanup, + * for example, blanking the stored filename (but do not delete the file). + * Calling program should call getFilename prior to ackFinished. + */ + virtual void ackFinished(); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., returns immediately from sayText or synthText. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref sayFinished or @ref synthFinished signals when + * saying or synthesis is completed. + */ + virtual bool supportsAsync(); + + /** + * Returns True if the plugin supports synthText method, + * i.e., is able to synthesize text to a sound file without + * audibilizing the text. + * @return True if this plugin supports synthText method. + * + * If the plugin returns True, it must also implement the following methods: + * - @ref synthText + * - @ref getFilename + * - @ref ackFinished + * + * If the plugin returns True, it need not implement @ref sayText . + */ + virtual bool supportsSynth(); + + /** + * Returns the name of an XSLT stylesheet that will convert a valid SSML file + * into a format that can be processed by the synth. For example, + * The Festival plugin returns a stylesheet that will convert SSML into + * SABLE. Any tags the synth cannot handle should be stripped (leaving + * their text contents though). The default stylesheet strips all + * tags and converts the file to plain text. + * @return Name of the XSLT file. + */ + virtual QString getSsmlXsltFilename(); + + /** + * Given the name of a codec, returns the QTextCodec for the name. + * Handles the following "special" codec names: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + * @param codecName Name of desired codec. + * @return The codec object. Calling program must not delete this object + * as it is a reference to an existing QTextCodec object. + * + * Caution: Do not pass translated codec names to this routine. + */ + static QTextCodec* codecNameToCodec(const QString &codecName); + + /** + * Builds a list of codec names, suitable for display in a QComboBox. + * The list includes the 3 special codec names (translated) at the top: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + */ + static QStringList buildCodecList(); + + /** + * Given the name of a codec, returns index into the codec list. + * Handles the following "special" codec names: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + * @param codecName Name of the codec. + * @param codecList List of codec names. The first 3 entries may be translated names. + * @return QTextCodec object. Caller must not delete this object. + * + * Caution: Do not pass translated codec names to this routine in codecName parameter. + */ + static int codecNameToListIndex(const QString &codecName, const QStringList &codecList); + + /** + * Given index into codec list, returns the codec object. + * @param codecNum Index of the codec. + * @param codecList List of codec names. The first 3 entries may be translated names. + * @return QTextCodec object. Caller must not delete this object. + */ + static QTextCodec* codecIndexToCodec(int codecNum, const QStringList &codecList); + + /** + * Given index into codec list, returns the codec Name. + * Handles the following "special" codec names: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + * @param codecNum Index of the codec. + * @param codecList List of codec names. The first 3 entries may be translated names. + * @return Untranslated name of the codec. + */ + static QString codecIndexToCodecName(int codecNum, const QStringList &codecList); + + signals: + /** + * Emitted when synthText() finishes and plugin supports asynchronous mode. + */ + void synthFinished(); + /** + * Emitted when sayText() finishes and plugin supports asynchronous mode. + */ + void sayFinished(); + /** + * Emitted when stopText() has been called and plugin stops asynchronously. + */ + void stopped(); + /** + * Emitted if an error occurs. + * @param keepGoing True if the plugin can continue processing. + * False if the plugin cannot continue, for example, + * the speech engine could not be started. + * @param msg Error message. + * + * When an error occurs, plugins should attempt to recover as best they can + * and continue accepting @ref sayText or @ref synthText calls. For example, + * if the speech engine emits an error while synthesizing text, the plugin + * should return True along with error message. + * + * @see Error-handling + * + */ + void error(bool keepGoing, const QString &msg); +}; + +#endif // _PLUGINPROC_H_ diff --git a/kttsd/libkttsd/selecttalkerdlg.cpp b/kttsd/libkttsd/selecttalkerdlg.cpp new file mode 100644 index 0000000..d74339e --- /dev/null +++ b/kttsd/libkttsd/selecttalkerdlg.cpp @@ -0,0 +1,365 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + A dialog for user to select a Talker, either by specifying + selected Talker attributes, or by specifying all attributes + of an existing configured Talker. + + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +// Qt includes. +#include <qcheckbox.h> +#include <qradiobutton.h> +#include <qhbox.h> +#include <qgroupbox.h> + +// KDE includes. +#include <kcombobox.h> +#include <ktrader.h> +#include <kpushbutton.h> +#include <klistview.h> +#include <klineedit.h> +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "utils.h" +#include "selecttalkerdlg.h" +#include "selecttalkerdlg.moc" + +SelectTalkerDlg::SelectTalkerDlg( + QWidget* parent, + const char* name, + const QString& caption, + const QString& talkerCode, + bool runningTalkers) : + + KDialogBase( + parent, + name, + true, + caption, + KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok) +{ + m_widget = new SelectTalkerWidget( this ); + // TODO: How do I do this in a general way and still get KDialogBase to properly resize? + m_widget->setMinimumSize( QSize(700,500) ); + // setInitialSize( QSize(700,600) ); + setMainWidget( m_widget ); + m_runningTalkers = runningTalkers; + m_talkerCode = TalkerCode( talkerCode, false ); + + // Fill combo boxes. + KComboBox* cb = m_widget->genderComboBox; + cb->insertItem( QString::null ); + cb->insertItem( TalkerCode::translatedGender("male") ); + cb->insertItem( TalkerCode::translatedGender("female") ); + cb->insertItem( TalkerCode::translatedGender("neutral") ); + + cb = m_widget->volumeComboBox; + cb->insertItem( QString::null ); + cb->insertItem( TalkerCode::translatedVolume("medium") ); + cb->insertItem( TalkerCode::translatedVolume("loud") ); + cb->insertItem( TalkerCode::translatedVolume("soft") ); + + cb = m_widget->rateComboBox; + cb->insertItem( QString::null ); + cb->insertItem( TalkerCode::translatedRate("medium") ); + cb->insertItem( TalkerCode::translatedRate("fast") ); + cb->insertItem( TalkerCode::translatedRate("slow") ); + + cb = m_widget->synthComboBox; + cb->insertItem( QString::null ); + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin"); + for(unsigned int i=0; i < offers.count() ; ++i) + cb->insertItem(offers[i]->name()); + + // Fill List View with list of Talkers. + m_widget->talkersListView->setSorting( -1 ); + loadTalkers( m_runningTalkers ); + + // Set initial radio button state. + if ( talkerCode.isEmpty() ) + m_widget->useDefaultRadioButton->setChecked(true); + else + { + QString dummy; + if (talkerCode == TalkerCode::normalizeTalkerCode(talkerCode, dummy)) + m_widget->useSpecificTalkerRadioButton->setChecked(true); + else + m_widget->useClosestMatchRadioButton->setChecked(true); + } + + applyTalkerCodeToControls(); + enableDisableControls(); + + connect(m_widget->useDefaultRadioButton, SIGNAL(clicked()), + this, SLOT(configChanged())); + connect(m_widget->useClosestMatchRadioButton, SIGNAL(clicked()), + this, SLOT(configChanged())); + connect(m_widget->useSpecificTalkerRadioButton, SIGNAL(clicked()), + this, SLOT(configChanged())); + + connect(m_widget->languageBrowseButton, SIGNAL(clicked()), + this, SLOT(slotLanguageBrowseButton_clicked())); + + connect(m_widget->synthComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->genderComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->volumeComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->rateComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + + connect(m_widget->synthCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->genderCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->volumeCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->rateCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + + connect(m_widget->talkersListView, SIGNAL(selectionChanged()), + this, SLOT(slotTalkersListView_selectionChanged())); + + m_widget->talkersListView->setMinimumHeight( 120 ); +} + +SelectTalkerDlg::~SelectTalkerDlg() { } + +QString SelectTalkerDlg::getSelectedTalkerCode() +{ + return m_talkerCode.getTalkerCode(); +} + +QString SelectTalkerDlg::getSelectedTranslatedDescription() +{ + return m_talkerCode.getTranslatedDescription(); +} + +void SelectTalkerDlg::slotLanguageBrowseButton_clicked() +{ + // Create a QHBox to host KListView. + QHBox* hBox = new QHBox(m_widget, "SelectLanguage_hbox"); + // Create a KListView and fill with all known languages. + KListView* langLView = new KListView(hBox, "SelectLanguage_lview"); + langLView->addColumn(i18n("Language")); + langLView->addColumn(i18n("Code")); + langLView->setSelectionMode(QListView::Single); + QStringList allLocales = KGlobal::locale()->allLanguagesTwoAlpha(); + QString locale; + QString language; + // Blank line so user can select no language. + // Note: Don't use QString::null, which gets displayed at bottom of list, rather than top. + QListViewItem* item = new KListViewItem(langLView, "", ""); + if (m_talkerCode.languageCode().isEmpty()) item->setSelected(true); + int allLocalesCount = allLocales.count(); + for (int ndx=0; ndx < allLocalesCount; ++ndx) + { + locale = allLocales[ndx]; + language = TalkerCode::languageCodeToLanguage(locale); + item = new KListViewItem(langLView, language, locale); + if (m_talkerCode.fullLanguageCode() == locale) item->setSelected(true); + } + // Sort by language. + langLView->setSorting(0); + langLView->sort(); + // Display the box in a dialog. + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Select Languages"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_widget, + "SelectLanguage_dlg", + true, + true); + dlg->setMainWidget(hBox); + dlg->setHelp("", "kttsd"); + dlg->setInitialSize(QSize(300, 500), false); + // TODO: This isn't working. Furthermore, item appears selected but is not. + langLView->ensureItemVisible(langLView->selectedItem()); + int dlgResult = dlg->exec(); + language = QString::null; + if (dlgResult == QDialog::Accepted) + { + if (langLView->selectedItem()) + { + language = langLView->selectedItem()->text(0); + m_talkerCode.setFullLanguageCode( langLView->selectedItem()->text(1) ); + } + } + delete dlg; + m_widget->languageLineEdit->setText(language); + m_widget->languageCheckBox->setChecked( !language.isEmpty() ); + configChanged(); +} + +void SelectTalkerDlg::slotTalkersListView_selectionChanged() +{ + QListViewItem* item = m_widget->talkersListView->selectedItem(); + if ( !item ) return; + if (!m_widget->useSpecificTalkerRadioButton->isChecked()) return; + configChanged(); +} + +void SelectTalkerDlg::configChanged() +{ + applyControlsToTalkerCode(); + applyTalkerCodeToControls(); + enableDisableControls(); +} + +void SelectTalkerDlg::applyTalkerCodeToControls() +{ + bool preferred = false; + QString code = m_talkerCode.getTalkerCode(); + + // TODO: Need to display translated Synth names. + KttsUtils::setCbItemFromText(m_widget->synthComboBox, + TalkerCode::stripPrefer( m_talkerCode.plugInName(), preferred) ); + m_widget->synthCheckBox->setEnabled( !m_talkerCode.plugInName().isEmpty() ); + m_widget->synthCheckBox->setChecked( preferred ); + + KttsUtils::setCbItemFromText(m_widget->genderComboBox, + TalkerCode::translatedGender( TalkerCode::stripPrefer( m_talkerCode.gender(), preferred ) ) ); + m_widget->genderCheckBox->setEnabled( !m_talkerCode.gender().isEmpty() ); + m_widget->genderCheckBox->setChecked( preferred ); + + KttsUtils::setCbItemFromText(m_widget->volumeComboBox, + TalkerCode::translatedVolume( TalkerCode::stripPrefer( m_talkerCode.volume(), preferred ) ) ); + m_widget->volumeCheckBox->setEnabled( !m_talkerCode.volume().isEmpty() ); + m_widget->volumeCheckBox->setChecked( preferred ); + + KttsUtils::setCbItemFromText(m_widget->rateComboBox, + TalkerCode::translatedRate( TalkerCode::stripPrefer( m_talkerCode.rate(), preferred ) ) ); + m_widget->rateCheckBox->setEnabled( !m_talkerCode.rate().isEmpty() ); + m_widget->rateCheckBox->setChecked( preferred ); + + // Select closest matching specific Talker. + int talkerIndex = TalkerCode::findClosestMatchingTalker(m_talkers, m_talkerCode.getTalkerCode(), false); + KListView* lv = m_widget->talkersListView; + QListViewItem* item = lv->firstChild(); + if ( item ) + { + while ( talkerIndex > 0 ) + { + item = item->nextSibling(); + --talkerIndex; + } + lv->setSelected( item, true ); + } +} + +void SelectTalkerDlg::applyControlsToTalkerCode() +{ + if ( m_widget->useDefaultRadioButton->isChecked() ) + m_talkerCode = TalkerCode(QString::null, false); + else if ( m_widget->useClosestMatchRadioButton->isChecked() ) + { + // Language already stored in talker code. + + QString t = m_widget->synthComboBox->currentText(); + if ( !t.isEmpty() && m_widget->synthCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setPlugInName( t ); + + t = TalkerCode::untranslatedGender( m_widget->genderComboBox->currentText() ); + if ( !t.isEmpty() && m_widget->genderCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setGender( t ); + + t = TalkerCode::untranslatedVolume( m_widget->volumeComboBox->currentText() ); + if ( !t.isEmpty() && m_widget->volumeCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setVolume( t ); + + t = TalkerCode::untranslatedRate( m_widget->rateComboBox->currentText() ); + if ( !t.isEmpty() && m_widget->rateCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setRate( t ); + } + else if (m_widget->useSpecificTalkerRadioButton->isChecked() ) + { + QListViewItem* item = m_widget->talkersListView->selectedItem(); + if ( item ) + { + int itemIndex = -1; + while ( item ) + { + item = item->itemAbove(); + itemIndex++; + } + m_talkerCode = TalkerCode( &(m_talkers[itemIndex]), false ); + } + } +} + +void SelectTalkerDlg::loadTalkers(bool /*runningTalkers*/) +{ + m_talkers.clear(); + KListView* lv = m_widget->talkersListView; + lv->clear(); + QListViewItem* item; + KConfig* config = new KConfig("kttsdrc"); + config->setGroup("General"); + QStringList talkerIDsList = config->readListEntry("TalkerIDs", ','); + if (!talkerIDsList.isEmpty()) + { + QStringList::ConstIterator itEnd(talkerIDsList.constEnd()); + for( QStringList::ConstIterator it = talkerIDsList.constBegin(); it != itEnd; ++it ) + { + QString talkerID = *it; + config->setGroup("Talker_" + talkerID); + QString talkerCode = config->readEntry("TalkerCode", QString::null); + // Parse and normalize the talker code. + TalkerCode talker = TalkerCode(talkerCode, true); + m_talkers.append(talker); + QString desktopEntryName = config->readEntry("DesktopEntryName", QString::null); + QString synthName = TalkerCode::TalkerDesktopEntryNameToName(desktopEntryName); + // Display in List View using translated strings. + item = new KListViewItem(lv, item); + QString fullLanguageCode = talker.fullLanguageCode(); + QString language = TalkerCode::languageCodeToLanguage(fullLanguageCode); + item->setText(tlvcLanguage, language); + // Don't update the Synthesizer name with plugInName. The former is a translated + // name; the latter an English name. + // if (!plugInName.isEmpty()) talkerItem->setText(tlvcSynthName, plugInName); + if (!synthName.isEmpty()) + item->setText(tlvcSynthName, synthName); + if (!talker.voice().isEmpty()) + item->setText(tlvcVoice, talker.voice()); + if (!talker.gender().isEmpty()) + item->setText(tlvcGender, TalkerCode::translatedGender(talker.gender())); + if (!talker.volume().isEmpty()) + item->setText(tlvcVolume, TalkerCode::translatedVolume(talker.volume())); + if (!talker.rate().isEmpty()) + item->setText(tlvcRate, TalkerCode::translatedRate(talker.rate())); + } + } + delete config; +} + +void SelectTalkerDlg::enableDisableControls() +{ + bool enableClosest = ( m_widget->useClosestMatchRadioButton->isChecked() ); + bool enableSpecific = ( m_widget->useSpecificTalkerRadioButton->isChecked() ); + m_widget->closestMatchGroupBox->setEnabled( enableClosest ); + m_widget->talkersListView->setEnabled( enableSpecific ); +} diff --git a/kttsd/libkttsd/selecttalkerdlg.h b/kttsd/libkttsd/selecttalkerdlg.h new file mode 100644 index 0000000..bc236e2 --- /dev/null +++ b/kttsd/libkttsd/selecttalkerdlg.h @@ -0,0 +1,110 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + A dialog for user to select a Talker, either by specifying + selected Talker attributes, or by specifying all attributes + of an existing configured Talker. + + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +#ifndef _SELECTTALKERDLG_H_ +#define _SELECTTALKERDLG_H_ + +// Qt includes. +#include <qvaluelist.h> + +// KDE includes. +#include <kdialogbase.h> +#include <klocale.h> +#include <kdemacros.h> + +// KTTS includes. +#include "talkercode.h" +#include "selecttalkerwidget.h" + +class KDE_EXPORT SelectTalkerDlg : public KDialogBase +{ + Q_OBJECT + + public: + /** + * Constructor. + * @param parent The parent for this dialog. + * @param name Name for this dialog. + * @param caption Displayed title for this dialog. + * @param talkerCode A suggested starting Talker Code. + * @param runningTalkers If true, lists configured and Applied Talkers in the running + * KTTSD. If false, lists Talkers in the KTTSMgr Talker tab + * (which may not yet have been Applied). + */ + SelectTalkerDlg( + QWidget* parent = 0, + const char* name = "selecttalkerdialog", + const QString& caption = i18n("Select Talker"), + const QString& talkerCode = QString::null, + bool runningTalkers = false); + + /** + * Destructor. + */ + ~SelectTalkerDlg(); + + /** + * Returns the Talker Code user chose. QString::null if default Talker chosen. + * Note that if user did not choose a specific Talker, this will be a partial Talker Code. + */ + QString getSelectedTalkerCode(); + /** + * Returns the Talker user chose in a translated displayable format. + */ + QString getSelectedTranslatedDescription(); + + private slots: + void slotLanguageBrowseButton_clicked(); + void slotTalkersListView_selectionChanged(); + void configChanged(); + + private: + enum TalkerListViewColumn + { + tlvcLanguage, + tlvcSynthName, + tlvcVoice, + tlvcGender, + tlvcVolume, + tlvcRate + }; + + void applyTalkerCodeToControls(); + void applyControlsToTalkerCode(); + void loadTalkers(bool runningTalkers); + void enableDisableControls(); + + // Main dialog widget. + SelectTalkerWidget* m_widget; + // True if list of Talkers should be taken from config file. + bool m_runningTalkers; + // Current Talker Code. + TalkerCode m_talkerCode; + // List of parsed talker codes for the configured Talkers. + TalkerCode::TalkerCodeList m_talkers; +}; + +#endif // _SELECTTALKERDLG_H_ diff --git a/kttsd/libkttsd/selecttalkerwidget.ui b/kttsd/libkttsd/selecttalkerwidget.ui new file mode 100644 index 0000000..adcc8a9 --- /dev/null +++ b/kttsd/libkttsd/selecttalkerwidget.ui @@ -0,0 +1,572 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>SelectTalkerWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>SelectTalkerWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>558</width> + <height>447</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Select Talker</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>buttonGroup1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>useDefaultRadioButton</cstring> + </property> + <property name="text"> + <string>&Use default Talker</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, will use the default Talker, which is the topmost Talker listed in the Talkers tab.</string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>useClosestMatchRadioButton</cstring> + </property> + <property name="text"> + <string>Use closest &matching Talker having</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, will use a configured Talker most closely matching the attributes you choose. Attributes with checks next to them will be preferred over unchecked attributes. Language is always preferred.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer15</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QGroupBox"> + <property name="name"> + <cstring>closestMatchGroupBox</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel" row="3" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>preferredLabel</cstring> + </property> + <property name="text"> + <string>Checked items are preferred over unchecked items.</string> + </property> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout11_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>synthLabel</cstring> + </property> + <property name="text"> + <string>&Synthesizer:</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter|AlignLeft</set> + </property> + <property name="buddy" stdset="0"> + <cstring>synthComboBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>synthComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>synthCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="0" column="2"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>genderLabel</cstring> + </property> + <property name="text"> + <string>&Gender:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>genderCheckBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>genderComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>genderCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="Line" row="0" column="1" rowspan="2" colspan="1"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>VLine</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + <property name="lineWidth"> + <number>2</number> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>volumeLabel</cstring> + </property> + <property name="text"> + <string>&Volume:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>volumeCheckBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>volumeComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>volumeCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="2"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>rateLabel</cstring> + </property> + <property name="text"> + <string>&Rate:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>rateCheckBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>rateComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>rateCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>languageLabel</cstring> + </property> + <property name="text"> + <string>&Language:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>languageLineEdit</cstring> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>languageLineEdit</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="paletteForegroundColor"> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank, the filter applies to all text jobs of any language.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>languageBrowseButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select one or more languages. This filter will be applied to text jobs of those languages.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>languageCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + </hbox> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>useSpecificTalkerRadioButton</cstring> + </property> + <property name="text"> + <string>Use specific &Talker</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, will use the specific Talker (if it is still configured), otherwise the Talker most closely matching.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer15_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KListView"> + <column> + <property name="text"> + <string>Language</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Speech Synthesizer</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Voice</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Gender</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Volume</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Rate</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>talkersListView</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="resizeMode"> + <enum>LastColumn</enum> + </property> + <property name="whatsThis" stdset="0"> + <string>This is a list of all the configured Talkers. A Talker is a speech synthesizer that has been configured with a language, voice, gender, speaking rate, and volume. Talkers higher in the list have higher priority. The topmost Talker will be used when no talker attributes have been specified by an application.</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>useDefaultRadioButton</tabstop> + <tabstop>useClosestMatchRadioButton</tabstop> + <tabstop>synthComboBox</tabstop> + <tabstop>synthCheckBox</tabstop> + <tabstop>genderComboBox</tabstop> + <tabstop>genderCheckBox</tabstop> + <tabstop>volumeComboBox</tabstop> + <tabstop>volumeCheckBox</tabstop> + <tabstop>rateComboBox</tabstop> + <tabstop>rateCheckBox</tabstop> + <tabstop>languageLineEdit</tabstop> + <tabstop>languageBrowseButton</tabstop> + <tabstop>languageCheckBox</tabstop> + <tabstop>useSpecificTalkerRadioButton</tabstop> + <tabstop>talkersListView</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="0"/> +<includehints> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistview.h</includehint> +</includehints> +</UI> diff --git a/kttsd/libkttsd/stretcher.cpp b/kttsd/libkttsd/stretcher.cpp new file mode 100644 index 0000000..d3a3984 --- /dev/null +++ b/kttsd/libkttsd/stretcher.cpp @@ -0,0 +1,99 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + Speeds up or slows down an audio file by stretching the audio stream. + Uses the sox program to do the stretching. + + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +// KDE includes. +#include <kprocess.h> +#include <kdebug.h> + +// Stretcher includes. +#include "stretcher.h" +#include "stretcher.moc" + +/** + * Constructor. + */ +Stretcher::Stretcher(QObject *parent, const char *name) : + QObject(parent, name) +{ + m_state = 0; + m_stretchProc = 0; +} + +/** + * Destructor. + */ +Stretcher::~Stretcher() +{ + delete m_stretchProc; +} + +/** + * Stretch the given input file to an output file. + * @param inFilename Name of input audio file. + * @param outFilename Name of output audio file. + * @param stretchFactor Amount to stretch. 2.0 is twice as slow. 0.5 is twice as fast. + * @return False if an error occurs. + */ +bool Stretcher::stretch(const QString &inFilename, const QString &outFilename, float stretchFactor) +{ + if (m_stretchProc) return false; + m_outFilename = outFilename; + m_stretchProc = new KProcess; + QString stretchStr = QString("%1").arg(stretchFactor, 0, 'f', 3); + *m_stretchProc << "sox" << inFilename << outFilename << "stretch" << stretchStr; + connect(m_stretchProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + if (!m_stretchProc->start(KProcess::NotifyOnExit, KProcess::NoCommunication)) + { + kdDebug() << "Stretcher::stretch: Error starting audio stretcher process. Is sox installed?" << endl; + return false; + } + m_state = ssStretching; + return true; +} + +void Stretcher::slotProcessExited(KProcess*) +{ + m_stretchProc->deleteLater(); + m_stretchProc = 0; + m_state = ssFinished; + emit stretchFinished(); +} + +/** + * Returns the state of the Stretcher. + */ +int Stretcher::getState() { return m_state; } + +/** + * Returns the output filename (as given in call to stretch). + */ +QString Stretcher::getOutFilename() { return m_outFilename; } + +/** + * Acknowledges the finished stretching. + */ +void Stretcher::ackFinished() { m_state = ssIdle; } + diff --git a/kttsd/libkttsd/stretcher.h b/kttsd/libkttsd/stretcher.h new file mode 100644 index 0000000..e4d8686 --- /dev/null +++ b/kttsd/libkttsd/stretcher.h @@ -0,0 +1,97 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + Speeds up or slows down an audio file by stretching the audio stream. + Uses the sox program to do the stretching. + + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +#ifndef _STRETCHER_H_ +#define _STRETCHER_H_ + +#include <kdemacros.h> +#include "kdeexportfix.h" + +class KProcess; + +class KDE_EXPORT Stretcher : public QObject{ + Q_OBJECT + + public: + /** + * Constructor. + */ + Stretcher(QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + */ + ~Stretcher(); + + enum StretcherState { + ssIdle = 0, // Not doing anything. Ready to stretch. + ssStretching = 1, // Stretching. + ssFinished = 2 // Stretching finished. + }; + + /** + * Stretch the given input file to an output file. + * @param inFilename Name of input audio file. + * @param outFilename Name of output audio file. + * @param stretchFactor Amount to stretch. 2.0 is twice as slow. 0.5 is twice as fast. + */ + bool stretch(const QString &inFilename, const QString &outFilename, float stretchFactor); + + /** + * Returns the state of the Stretcher. + */ + int getState(); + + /** + * Returns the output filename (as given in call to stretch). + */ + QString getOutFilename(); + + /** + * Acknowledges the finished stretching. + */ + void ackFinished(); + + signals: + /** + * Emitted whenever stretching is completed. + */ + void stretchFinished(); + + private slots: + void slotProcessExited(KProcess* proc); + + private: + // Stretcher state. + int m_state; + + // Sox process. + KProcess* m_stretchProc; + + // Output file name. + QString m_outFilename; +}; + +#endif // _STRETCHER_H_ diff --git a/kttsd/libkttsd/talkercode.cpp b/kttsd/libkttsd/talkercode.cpp new file mode 100644 index 0000000..d25cf7f --- /dev/null +++ b/kttsd/libkttsd/talkercode.cpp @@ -0,0 +1,517 @@ +/***************************************************** 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: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> +#include <ktrader.h> +#include <kdebug.h> + +// TalkerCode includes. +#include "talkercode.h" + +/** + * Constructor. + */ +TalkerCode::TalkerCode(const QString &code/*=QString::null*/, bool normal /*=false*/) +{ + if (!code.isEmpty()) + parseTalkerCode(code); + if (normal) normalize(); +} + +/** + * Copy Constructor. + */ +TalkerCode::TalkerCode(TalkerCode* talker, bool normal /*=false*/) +{ + m_languageCode = talker->languageCode(); + m_countryCode = talker->countryCode(); + m_voice = talker->voice(); + m_gender = talker->gender(); + m_volume = talker->volume(); + m_rate = talker->rate(); + m_plugInName = talker->plugInName(); + if (normal) normalize(); +} + +/** + * Destructor. + */ +TalkerCode::~TalkerCode() { } + +/** + * Properties. + */ +QString TalkerCode::languageCode() const { return m_languageCode; } +QString TalkerCode::countryCode() const { return m_countryCode; } +QString TalkerCode::voice() const { return m_voice; } +QString TalkerCode::gender() const { return m_gender; } +QString TalkerCode::volume() const { return m_volume; } +QString TalkerCode::rate() const { return m_rate; } +QString TalkerCode::plugInName() const { return m_plugInName; } + +void TalkerCode::setLanguageCode(const QString &languageCode) { m_languageCode = languageCode; } +void TalkerCode::setCountryCode(const QString &countryCode) { m_countryCode = countryCode; } +void TalkerCode::setVoice(const QString &voice) { m_voice = voice; } +void TalkerCode::setGender(const QString &gender) { m_gender = gender; } +void TalkerCode::setVolume(const QString &volume) { m_volume = volume; } +void TalkerCode::setRate(const QString &rate) { m_rate = rate; } +void TalkerCode::setPlugInName(const QString plugInName) { m_plugInName = plugInName; } + +/** + * Sets the language code and country code (if given). + */ +void TalkerCode::setFullLanguageCode(const QString &fullLanguageCode) +{ + splitFullLanguageCode(fullLanguageCode, m_languageCode, m_countryCode); +} + +/** + * Returns the language code plus country code (if any). + */ +QString TalkerCode::fullLanguageCode() const +{ + if (!m_countryCode.isEmpty()) + return m_languageCode + "_" + m_countryCode; + else + return m_languageCode; +} + +/** + * The Talker Code returned in XML format. + */ +QString TalkerCode::getTalkerCode() const +{ + QString code; + QString languageCode = m_languageCode; + if (!m_countryCode.isEmpty()) languageCode += "_" + m_countryCode; + if (!languageCode.isEmpty()) code = "lang=\"" + languageCode + "\" "; + if (!m_voice.isEmpty()) code += "name=\"" + m_voice + "\" "; + if (!m_gender.isEmpty()) code += "gender=\"" + m_gender + "\" "; + if (!code.isEmpty()) code = "<voice " + code + "/>"; + QString prosody; + if (!m_volume.isEmpty()) prosody = "volume=\"" + m_volume + "\" "; + if (!m_rate.isEmpty()) prosody += "rate=\"" + m_rate + "\" "; + if (!prosody.isEmpty()) code += "<prosody " + prosody + "/>"; + if (!m_plugInName.isEmpty()) code += "<kttsd synthesizer=\"" + m_plugInName + "\" />"; + return code; +} + +/** + * The Talker Code translated for display. + */ +QString TalkerCode::getTranslatedDescription() const +{ + QString code; + bool prefer; + QString fullLangCode = fullLanguageCode(); + 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 (!m_plugInName.isEmpty()) code += " " + stripPrefer(m_plugInName, prefer); + if (!m_voice.isEmpty()) code += " " + stripPrefer(m_voice, prefer); + if (!m_gender.isEmpty()) code += " " + translatedGender(stripPrefer(m_gender, prefer)); + if (!m_volume.isEmpty()) code += " " + translatedVolume(stripPrefer(m_volume, prefer)); + if (!m_rate.isEmpty()) code += " " + translatedRate(stripPrefer(m_rate, prefer)); + code = code.stripWhiteSpace(); + if (code.isEmpty()) code = i18n("default"); + return code; +} + +/** + * Normalizes the Talker Code by filling in defaults. + */ +void TalkerCode::normalize() +{ + if (m_voice.isEmpty()) m_voice = "fixed"; + if (m_gender.isEmpty()) m_gender = "neutral"; + if (m_volume.isEmpty()) m_volume = "medium"; + if (m_rate.isEmpty()) m_rate = "medium"; +} + +/** + * Given a talker code, normalizes it into a standard form and also returns + * the language code. + * @param talkerCode Unnormalized talker code. + * @return fullLanguageCode Language code from the talker code (including country code if any). + * @return Normalized talker code. + */ +/*static*/ QString TalkerCode::normalizeTalkerCode(const QString &talkerCode, QString &fullLanguageCode) +{ + TalkerCode tmpTalkerCode(talkerCode); + tmpTalkerCode.normalize(); + fullLanguageCode = tmpTalkerCode.fullLanguageCode(); + return tmpTalkerCode.getTalkerCode(); +} + +/** + * Given a language code that might contain a country code, splits the code into + * the two letter language code and country code. + * @param fullLanguageCode Language code to be split. + * @return languageCode Just the language part of the code. + * @return countryCode The country code part (if any). + * + * If the input code begins with an asterisk, it is ignored and removed from the returned + * languageCode. + */ +/*static*/ void TalkerCode::splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode) +{ + QString language = lang; + if (language.left(1) == "*") language = language.mid(1); + QString charSet; + KGlobal::locale()->splitLocale(language, languageCode, countryCode, charSet); +} + +/** + * Given a full language code and plugin name, returns a normalized default talker code. + * @param fullLanguageCode Language code. + * @param plugInName Name of the Synthesizer plugin. + * @return Full normalized talker code. + * + * Example returned from defaultTalkerCode("en", "Festival") + * <voice lang="en" name="fixed" gender="neutral"/> + * <prosody volume="medium" rate="medium"/> + * <kttsd synthesizer="Festival" /> + */ +/*static*/ QString TalkerCode::defaultTalkerCode(const QString &fullLanguageCode, const QString &plugInName) +{ + TalkerCode tmpTalkerCode; + tmpTalkerCode.setFullLanguageCode(fullLanguageCode); + tmpTalkerCode.setPlugInName(plugInName); + tmpTalkerCode.normalize(); + return tmpTalkerCode.getTalkerCode(); +} + +/** + * Converts a language code plus optional country code to language description. + */ +/*static*/ QString TalkerCode::languageCodeToLanguage(const QString &languageCode) +{ + QString twoAlpha; + QString countryCode; + QString language; + if (languageCode == "other") + language = i18n("Other"); + else + { + splitFullLanguageCode(languageCode, twoAlpha, countryCode); + language = KGlobal::locale()->twoAlphaToLanguageName(twoAlpha); + } + if (!countryCode.isEmpty()) + { + QString countryName = KGlobal::locale()->twoAlphaToCountryName(countryCode); + // Some abbreviations to save screen space. + if (countryName == i18n("full country name", "United States of America")) + countryName = i18n("abbreviated country name", "USA"); + if (countryName == i18n("full country name", "United Kingdom")) + countryName = i18n("abbreviated country name", "UK"); + language += " (" + countryName + ")"; + } + return language; +} + +/** + * These functions return translated Talker Code attributes. + */ +/*static*/ QString TalkerCode::translatedGender(const QString &gender) +{ + if (gender == "male") + return i18n("male"); + else if (gender == "female") + return i18n("female"); + else if (gender == "neutral") + return i18n("neutral gender", "neutral"); + else return gender; +} +/*static*/ QString TalkerCode::untranslatedGender(const QString &gender) +{ + if (gender == i18n("male")) + return "male"; + else if (gender == i18n("female")) + return "female"; + else if (gender == i18n("neutral gender", "neutral")) + return "neutral"; + else return gender; +} +/*static*/ QString TalkerCode::translatedVolume(const QString &volume) +{ + if (volume == "medium") + return i18n("medium sound", "medium"); + else if (volume == "loud") + return i18n("loud sound", "loud"); + else if (volume == "soft") + return i18n("soft sound", "soft"); + else return volume; +} +/*static*/ QString TalkerCode::untranslatedVolume(const QString &volume) +{ + if (volume == i18n("medium sound", "medium")) + return "medium"; + else if (volume == i18n("loud sound", "loud")) + return "loud"; + else if (volume == i18n("soft sound", "soft")) + return "soft"; + else return volume; +} +/*static*/ QString TalkerCode::translatedRate(const QString &rate) +{ + if (rate == "medium") + return i18n("medium speed", "medium"); + else if (rate == "fast") + return i18n("fast speed", "fast"); + else if (rate == "slow") + return i18n("slow speed", "slow"); + else return rate; +} +/*static*/ QString TalkerCode::untranslatedRate(const QString &rate) +{ + if (rate == i18n("medium speed", "medium")) + return "medium"; + else if (rate == i18n("fast speed", "fast")) + return "fast"; + else if (rate == i18n("slow speed", "slow")) + return "slow"; + else return rate; +} + +/** + * Given a talker code, parses out the attributes. + * @param talkerCode The talker code. + */ +void TalkerCode::parseTalkerCode(const QString &talkerCode) +{ + QString fullLanguageCode; + if (talkerCode.contains("\"")) + { + fullLanguageCode = talkerCode.section("lang=", 1, 1); + fullLanguageCode = fullLanguageCode.section('"', 1, 1); + } + else + fullLanguageCode = talkerCode; + QString languageCode; + QString countryCode; + splitFullLanguageCode(fullLanguageCode, languageCode, countryCode); + m_languageCode = languageCode; + if (fullLanguageCode.left(1) == "*") countryCode = "*" + countryCode; + m_countryCode = countryCode; + m_voice = talkerCode.section("name=", 1, 1); + m_voice = m_voice.section('"', 1, 1); + m_gender = talkerCode.section("gender=", 1, 1); + m_gender = m_gender.section('"', 1, 1); + m_volume = talkerCode.section("volume=", 1, 1); + m_volume = m_volume.section('"', 1, 1); + m_rate = talkerCode.section("rate=", 1, 1); + m_rate = m_rate.section('"', 1, 1); + m_plugInName = talkerCode.section("synthesizer=", 1, 1); + m_plugInName = m_plugInName.section('"', 1, 1); +} + +/** + * 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) +{ + // kdDebug() << "TalkerCode::findClosestMatchingTalker: matching on talker code " << talker << endl; + // 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.languageCode().isEmpty()) parsedTalkerCode.setLanguageCode( + talkers[0].languageCode()); + } + // The talker that matches on the most priority attributes wins. + int talkersCount = int(talkers.count()); + QMemArray<int> priorityMatch(talkersCount); + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + priorityMatch[ndx] = 0; + // kdDebug() << "Comparing language code " << parsedTalkerCode.languageCode() << " to " << m_loadedPlugIns[ndx].parsedTalkerCode.languageCode() << endl; + if (parsedTalkerCode.languageCode() == talkers[ndx].languageCode()) + { + ++priorityMatch[ndx]; + // kdDebug() << "TalkerCode::findClosestMatchingTalker: Match on language " << parsedTalkerCode.languageCode() << endl; + } + if (parsedTalkerCode.countryCode().left(1) == "*") + if (parsedTalkerCode.countryCode().mid(1) == + talkers[ndx].countryCode()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.voice().left(1) == "*") + if (parsedTalkerCode.voice().mid(1) == talkers[ndx].voice()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.gender().left(1) == "*") + if (parsedTalkerCode.gender().mid(1) == talkers[ndx].gender()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.volume().left(1) == "*") + if (parsedTalkerCode.volume().mid(1) == talkers[ndx].volume()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.rate().left(1) == "*") + if (parsedTalkerCode.rate().mid(1) == talkers[ndx].rate()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.plugInName().left(1) == "*") + if (parsedTalkerCode.plugInName().mid(1) == + talkers[ndx].plugInName()) + ++priorityMatch[ndx]; + } + // 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; + } + } + // kdDebug() << "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) + { + QMemArray<int> preferredMatch(talkersCount); + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + preferredMatch[ndx] = 0; + if (priorityMatch[ndx] == maxPriority) + { + if (parsedTalkerCode.countryCode().left(1) != "*") + if (!talkers[ndx].countryCode().isEmpty()) + if (parsedTalkerCode.countryCode() == talkers[ndx].countryCode()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.voice().left(1) != "*") + if (parsedTalkerCode.voice() == talkers[ndx].voice()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.gender().left(1) != "*") + if (parsedTalkerCode.gender() == talkers[ndx].gender()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.volume().left(1) != "*") + if (parsedTalkerCode.volume() == talkers[ndx].volume()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.rate().left(1) != "*") + if (parsedTalkerCode.rate() == talkers[ndx].rate()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.plugInName().left(1) != "*") + if (parsedTalkerCode.plugInName() == + talkers[ndx].plugInName()) + ++preferredMatch[ndx]; + } + } + // 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; + } + } + } + // kdDebug() << "Preferred phase: winnerCount = " << winnerCount + // << " winner = " << winner + // << " maxPreferred = " << maxPreferred << endl; + } + // If no winner found, use the first talker. + if (winner < 0) winner = 0; + // kdDebug() << "TalkerCode::findClosestMatchingTalker: returning winner = " << winner << endl; + return winner; +} + +/*static*/ QString TalkerCode::stripPrefer( const QString& code, bool& preferred) +{ + if ( code.left(1) == "*" ) + { + preferred = true; + return code.mid(1); + } else { + preferred = false; + return code; + } +} + +/** +* Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName. +* @param name The translated plugin name. From Name= line in .desktop file. +* @return DesktopEntryName. The name of the .desktop file (less .desktop). +* QString::null if not found. +*/ +/*static*/ QString TalkerCode::TalkerNameToDesktopEntryName(const QString& name) +{ + if (name.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin"); + for (uint ndx = 0; ndx < offers.count(); ++ndx) + if (offers[ndx]->name() == name) return offers[ndx]->desktopEntryName(); + return QString::null; +} + +/** +* Uses KTrader to convert a DesktopEntryName into a translated Synth Plugin Name. +* @param desktopEntryName The DesktopEntryName. +* @return The translated Name of the plugin, from Name= line in .desktop file. +*/ +/*static*/ QString TalkerCode::TalkerDesktopEntryNameToName(const QString& desktopEntryName) +{ + if (desktopEntryName.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin", + QString("DesktopEntryName == '%1'").arg(desktopEntryName)); + + if (offers.count() == 1) + return offers[0]->name(); + else + return QString::null; +} + diff --git a/kttsd/libkttsd/talkercode.h b/kttsd/libkttsd/talkercode.h new file mode 100644 index 0000000..45469af --- /dev/null +++ b/kttsd/libkttsd/talkercode.h @@ -0,0 +1,197 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Object containing a Talker Code and providing convenience + functions for manipulating Talker Codes. + For an explanation of what a Talker Code is, see speech.h. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +#ifndef _TALKERCODE_H_ +#define _TALKERCODE_H_ + +// Qt includes. +#include <qstring.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include <qvaluelist.h> + +class KDE_EXPORT TalkerCode +{ + public: + /** + * Constructor. + */ + TalkerCode(const QString &code=QString::null, bool normal=false); + /** + * Copy Constructor. + */ + TalkerCode(TalkerCode* talker, bool normal=false); + + /** + * Destructor. + */ + ~TalkerCode(); + + typedef QValueList<TalkerCode> TalkerCodeList; + + /** + * Properties. + */ + QString languageCode() const; /* lang="xx" */ + QString countryCode() const; /* lang="yy_xx */ + QString voice() const; /* name="xxx" */ + QString gender() const; /* gender="xxx" */ + QString volume() const; /* volume="xxx" */ + QString rate() const; /* rate="xxx" */ + QString plugInName() const; /* synthesizer="xxx" */ + + /** + * Returns the language code plus country code (if any). + */ + QString fullLanguageCode() const; + + void setLanguageCode(const QString &languageCode); + void setCountryCode(const QString &countryCode); + void setVoice(const QString &voice); + void setGender(const QString &gender); + void setVolume(const QString &volume); + void setRate(const QString &rate); + void setPlugInName(const QString plugInName); + + /** + * Sets the language code and country code (if given). + */ + void setFullLanguageCode(const QString &fullLanguageCode); + + /** + * The Talker Code returned in XML format. + */ + QString getTalkerCode() const; + + /** + * The Talker Code translated for display. + */ + QString getTranslatedDescription() const; + + /** + * Normalizes the Talker Code by filling in defaults. + */ + void normalize(); + + /** + * Given a talker code, normalizes it into a standard form and also returns + * the full language code. + * @param talkerCode Unnormalized talker code. + * @return fullLanguageCode Language code from the talker code (including country code if any). + * @return Normalized talker code. + */ + static QString normalizeTalkerCode(const QString &talkerCode, QString &fullLanguageCode); + + /** + * Given a language code that might contain a country code, splits the code into + * the two letter language code and country code. + * @param fullLanguageCode Language code to be split. + * @return languageCode Just the language part of the code. + * @return countryCode The country code part (if any). + * + * If the input code begins with an asterisk, it is ignored and removed from the returned + * languageCode. + */ + static void splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode); + + /** + * Given a language code and plugin name, returns a normalized default talker code. + * @param fullLanguageCode Language code. + * @param plugInName Name of the Synthesizer plugin. + * @return Full normalized talker code. + * + * Example returned from defaultTalkerCode("en", "Festival") + * <voice lang="en" name="fixed" gender="neutral"/> + * <prosody volume="medium" rate="medium"/> + * <kttsd synthesizer="Festival" /> + */ + static QString defaultTalkerCode(const QString &fullLanguageCode, const QString &plugInName); + + /** + * Converts a language code plus optional country code to language description. + */ + static QString languageCodeToLanguage(const QString &languageCode); + + /** + * These functions return translated Talker Code attributes. + */ + static QString translatedGender(const QString &gender); + static QString translatedVolume(const QString &volume); + static QString translatedRate(const QString &rate); + static QString untranslatedGender(const QString &gender); + static QString untranslatedVolume(const QString &volume); + static QString untranslatedRate(const QString &rate); + + /** + * 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 findClosestMatchingTalker( + const TalkerCodeList& talkers, + const QString& talker, + bool assumeDefaultLang = true); + + /** + * Strips leading * from a code. + */ + static QString stripPrefer( const QString& code, bool& preferred); + + /** + * Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName. + * @param name The translated plugin name. From Name= line in .desktop file. + * @return DesktopEntryName. The name of the .desktop file (less .desktop). + * QString::null if not found. + */ + static QString TalkerNameToDesktopEntryName(const QString& name); + + /** + * Uses KTrader to convert a DesktopEntryName into a translated Synth Plugin Name. + * @param desktopEntryName The DesktopEntryName. + * @return The translated Name of the plugin, from Name= line in .desktop file. + */ + static QString TalkerDesktopEntryNameToName(const QString& desktopEntryName); + + private: + /** + * Given a talker code, parses out the attributes. + * @param talkerCode The talker code. + */ + void parseTalkerCode(const QString &talkerCode); + + QString m_languageCode; /* lang="xx" */ + QString m_countryCode; /* lang="yy_xx */ + QString m_voice; /* name="xxx" */ + QString m_gender; /* gender="xxx" */ + QString m_volume; /* volume="xxx" */ + QString m_rate; /* rate="xxx" */ + QString m_plugInName; /* synthesizer="xxx" */ +}; + +#endif // _TALKERCODE_H_ diff --git a/kttsd/libkttsd/testplayer.cpp b/kttsd/libkttsd/testplayer.cpp new file mode 100644 index 0000000..06d05a7 --- /dev/null +++ b/kttsd/libkttsd/testplayer.cpp @@ -0,0 +1,209 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Player Object for playing synthesized audio files. Plays them + synchronously. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +// Qt includes. +#include <qfile.h> + +// KDE includes. +#include <kapplication.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kparts/componentfactory.h> +#include <ktrader.h> +#include <kdebug.h> + +// KTTS includes. +#include "player.h" +#include "stretcher.h" +#include "pluginconf.h" + +// TestPlayer includes. +#include "testplayer.h" + +/** + * Constructor. + */ +TestPlayer::TestPlayer(QObject *parent, const char *name, + const int playerOption, const float audioStretchFactor, const QString &sinkName) : + QObject(parent, name) +{ + m_playerOption = playerOption; + m_audioStretchFactor = audioStretchFactor; + m_stretcher = 0; + m_player = 0; + m_sinkName = sinkName; +} + +/** + * Destructor. + */ +TestPlayer::~TestPlayer() +{ + delete m_stretcher; + delete m_player; +} + +/** + * Sets which audio player to use. + * 0 = aRts + * 1 = gstreamer + * 2 = ALSA + * 3 = aKode + */ +void TestPlayer::setPlayerOption(const int playerOption) { m_playerOption = playerOption; } + +/** + * Sets the audio stretch factor (Speed adjustment). + * 1.0 = normal + * 0.5 = twice as fast + * 2.0 = twice as slow + */ +void TestPlayer::setAudioStretchFactor(const float audioStretchFactor) + { m_audioStretchFactor = audioStretchFactor; } + +void TestPlayer::setSinkName(const QString &sinkName) { m_sinkName = sinkName; } + +/** + * Plays the specifified audio file and waits for completion. + * The audio file speed is adjusted according to the stretch factor. + * @param waveFile Name of the audio file to play. + */ +void TestPlayer::play(const QString &waveFile) +{ + // kdDebug() << "TestPlayer::play: running" << endl; + // Create a Stretcher object to adjust the audio Speed. + QString playFile = waveFile; + QString tmpFile; + if (m_audioStretchFactor != 1.0) + { + tmpFile = makeSuggestedFilename(); + // kdDebug() << "TestPlayer::play: stretching file " << playFile << " by " << m_audioStretchFactor + // << " to file " << tmpFile << endl; + m_stretcher = new Stretcher(); + if (m_stretcher->stretch(playFile, tmpFile, m_audioStretchFactor)) + { + while (m_stretcher->getState() != Stretcher::ssFinished) qApp->processEvents(); + playFile = m_stretcher->getOutFilename(); + } + delete m_stretcher; + m_stretcher = 0; + } + + // Create player object based on player option. + // kdDebug() << "TestPlayer::play: creating Player object with playerOption " << m_playerOption << endl; + m_player = createPlayerObject(m_playerOption); + // If player object could not be created, avoid crash is the best we can do! + if (!m_player) return; + // kdDebug() << "TestPlayer::play: starting playback." << endl; + m_player->startPlay(playFile); + + // TODO: The following hunk of code would ideally be unnecessary. We would just + // return at this point and let take care of + // cleaning up the play object. However, because we've been called from DCOP, + // this seems to be necessary. The call to processEvents is problematic because + // it can cause re-entrancy. + while (m_player->playing()) qApp->processEvents(); + // kdDebug() << "TestPlayer::play: stopping playback." << endl; + m_player->stop(); + delete m_player; + m_player = 0; + if (!tmpFile.isEmpty()) QFile::remove(tmpFile); +} + +/** + * Creates and returns a player object based on user option. + */ +Player* TestPlayer::createPlayerObject(int playerOption) +{ + Player* player = 0; + QString plugInName; + switch(playerOption) + { + case 1 : + { + plugInName = "kttsd_gstplugin"; + break; + } + case 2 : + { + plugInName = "kttsd_alsaplugin"; + break; + } + case 3 : + { + plugInName = "kttsd_akodeplugin"; + break; + } + default: + { + plugInName = "kttsd_artsplugin"; + break; + } + } + KTrader::OfferList offers = KTrader::self()->query( + "KTTSD/AudioPlugin", QString("DesktopEntryName == '%1'").arg(plugInName)); + + if(offers.count() == 1) + { + // kdDebug() << "TestPlayer::createPlayerObject: Loading " << offers[0]->library() << endl; + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if (factory) + player = + KParts::ComponentFactory::createInstanceFromLibrary<Player>( + offers[0]->library().latin1(), this, offers[0]->library().latin1()); + else + kdDebug() << "TestPlayer::createPlayerObject: Could not create factory." << endl; + } + if (player == 0) + kdDebug() << "TestPlayer::createPlayerObject: Could not load " + plugInName + + ". Is KDEDIRS set correctly?" << endl; + else + // Must have GStreamer >= 0.8.7. + if (playerOption == 1) + { + if (!player->requireVersion(0, 8, 7)) + { + delete player; + player = 0; + } + } + if (player) player->setSinkName(m_sinkName); + return player; +} + +/** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ +QString TestPlayer::makeSuggestedFilename() +{ + KTempFile tempFile (locateLocal("tmp", "kttsmgr-"), ".wav"); + QString waveFile = tempFile.file()->name(); + tempFile.close(); + QFile::remove(waveFile); + // kdDebug() << "TestPlayer::makeSuggestedFilename: Suggesting filename: " << waveFile << endl; + return PlugInConf::realFilePath(waveFile); +} + diff --git a/kttsd/libkttsd/testplayer.h b/kttsd/libkttsd/testplayer.h new file mode 100644 index 0000000..2e7339c --- /dev/null +++ b/kttsd/libkttsd/testplayer.h @@ -0,0 +1,121 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Player Object for playing synthesized audio files. Plays them + synchronously. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + ******************************************************************************/ + +#ifndef _TESTPLAYER_H_ +#define _TESTPLAYER_H_ + +#include <kdemacros.h> +#include "kdeexportfix.h" + +class Player; +class Stretcher; + +// TODO: Make this work asynchronously. + +class KDE_EXPORT TestPlayer : public QObject{ + public: + /** + * Constructor. + * @param playerOption + * @param audioStretchFactor + */ + TestPlayer(QObject *parent = 0, const char *name = 0, + const int playerOption = 0, const float audioStretchFactor = 1.0, + const QString &sinkName = QString::null); + + /** + * Destructor. + */ + ~TestPlayer(); + + /** + * Sets which audio player to use. + * 0 = aRts + * 1 = gstreamer + */ + void setPlayerOption(const int playerOption); + + /** + * Sets the audio stretch factor (Speed adjustment). + * 1.0 = normal + * 0.5 = twice as fast + * 2.0 = twice as slow + */ + void setAudioStretchFactor(const float audioStretchFactor); + + /** + * Plays the specifified audio file and waits for completion. + * The audio file speed is adjusted according to the stretch factor. + * @param waveFile Name of the audio file to play. + */ + void play(const QString &waveFile); + + /** + * Sets the GStreamer Sink Name. Examples: "alsasink", "osssink", "nassink". + */ + void setSinkName(const QString &sinkName); + + /** + * Creates and returns a player object based on user option. + */ + Player* createPlayerObject(int playerOption); + + private: + + /** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ + QString makeSuggestedFilename(); + + /** + * Which audio player to use. + * 0 = aRts + * 1 = gstreamer + */ + int m_playerOption; + + /** + * Audio stretch factor (Speed). + */ + float m_audioStretchFactor; + + /** + * GStreamer sink name. + */ + QString m_sinkName; + + /** + * Stretcher object. + */ + Stretcher* m_stretcher; + + /** + * Player object. + */ + Player* m_player; +}; + +#endif // _TESTPLAYER_H_ diff --git a/kttsd/libkttsd/utils.cpp b/kttsd/libkttsd/utils.cpp new file mode 100644 index 0000000..dd9f98c --- /dev/null +++ b/kttsd/libkttsd/utils.cpp @@ -0,0 +1,132 @@ +/*************************************************************************** + Class of utility functions. + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ****************************************************************************/ + +/*************************************************************************** + * * + * 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; version 2 of the License. * + * * + ***************************************************************************/ + +#include <qstring.h> +#include <kdebug.h> +#include <qcombobox.h> + +#include "utils.h" + +KttsUtils::KttsUtils() { +} + + +KttsUtils::~KttsUtils() { +} + +/** + * Check if an XML document has a certain root element. + * @param xmldoc The document to check for the element. + * @param elementName The element to check for in the document. + * @returns True if the root element exists in the document, false otherwise. +*/ +bool KttsUtils::hasRootElement(const QString &xmldoc, const QString &elementName) { + // Strip all whitespace and go from there. + QString doc = xmldoc.simplifyWhiteSpace(); + // Take off the <?xml...?> if it exists + if(doc.startsWith("<?xml")) { + // Look for ?> and strip everything off from there to the start - effectively removing + // <?xml...?> + int xmlStatementEnd = doc.find("?>"); + if(xmlStatementEnd == -1) { + kdDebug() << "KttsUtils::hasRootElement: Bad XML file syntax\n"; + return false; + } + xmlStatementEnd += 2; // len '?>' == 2 + doc = doc.right(doc.length() - xmlStatementEnd); + } + // Take off leading comments, if they exist. + while(doc.startsWith("<!--") || doc.startsWith(" <!--")) { + int commentStatementEnd = doc.find("-->"); + if(commentStatementEnd == -1) { + kdDebug() << "KttsUtils::hasRootElement: Bad XML file syntax\n"; + return false; + } + commentStatementEnd += 3; // len '>' == 2 + doc = doc.right(doc.length() - commentStatementEnd); + } + // Take off the doctype statement if it exists. + while(doc.startsWith("<!DOCTYPE") || doc.startsWith(" <!DOCTYPE")) { + int doctypeStatementEnd = doc.find(">"); + if(doctypeStatementEnd == -1) { + kdDebug() << "KttsUtils::hasRootElement: Bad XML file syntax\n"; + return false; + } + doctypeStatementEnd += 1; // len '>' == 2 + doc = doc.right(doc.length() - doctypeStatementEnd); + } + // We should (hopefully) be left with the root element. + return (doc.startsWith("<" + elementName) || doc.startsWith(" <" + elementName)); +} + +/** + * Check if an XML document has a certain DOCTYPE. + * @param xmldoc The document to check for the doctype. + * @param name The doctype name to check for. Pass QString::null to not check the name. + * @param publicId The public ID to check for. Pass QString::null to not check the ID. + * @param systemId The system ID to check for. Pass QString::null to not check the ID. + * @returns True if the parameters match the doctype, false otherwise. +*/ +bool KttsUtils::hasDoctype(const QString &xmldoc, const QString &name/*, const QString &publicId, const QString &systemId*/) { + // Strip all whitespace and go from there. + QString doc = xmldoc.stripWhiteSpace(); + // Take off the <?xml...?> if it exists + if(doc.startsWith("<?xml")) { + // Look for ?> and strip everything off from there to the start - effectively removing + // <?xml...?> + int xmlStatementEnd = doc.find("?>"); + if(xmlStatementEnd == -1) { + kdDebug() << "KttsUtils::hasDoctype: Bad XML file syntax\n"; + return false; + } + xmlStatementEnd += 2; // len '?>' == 2 + doc = doc.right(doc.length() - xmlStatementEnd); + doc = doc.stripWhiteSpace(); + } + // Take off leading comments, if they exist. + while(doc.startsWith("<!--")) { + int commentStatementEnd = doc.find("-->"); + if(commentStatementEnd == -1) { + kdDebug() << "KttsUtils::hasDoctype: Bad XML file syntax\n"; + return false; + } + commentStatementEnd += 3; // len '>' == 2 + doc = doc.right(doc.length() - commentStatementEnd); + doc = doc.stripWhiteSpace(); + } + // Match the doctype statement if it exists. + // kdDebug() << "KttsUtils::hasDoctype: searching " << doc.left(20) << "... for " << "<!DOCTYPE " << name << endl; + return (doc.startsWith("<!DOCTYPE " + name)); +} + +/** + * Sets the current item in the given combobox to the item with the given text. + * If item with the text not found, does nothing. + */ +/*static*/ void KttsUtils::setCbItemFromText(QComboBox* cb, const QString& text) +{ + const int itemCount = cb->count(); + for (int ndx = 0; ndx < itemCount; ++ndx) + { + if (cb->text(ndx) == text) + { + cb->setCurrentItem(ndx); + return; + } + } +} + diff --git a/kttsd/libkttsd/utils.h b/kttsd/libkttsd/utils.h new file mode 100644 index 0000000..63e95bc --- /dev/null +++ b/kttsd/libkttsd/utils.h @@ -0,0 +1,61 @@ +/*************************************************************************** + Class of utility functions. + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ****************************************************************************/ + +/*************************************************************************** + * * + * 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; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#include <kdemacros.h> +#include "kdeexportfix.h" + +class QString; +class QComboBox; + +class KDE_EXPORT KttsUtils { + +public: + /// Constructor + KttsUtils(); + /// Destructor + ~KttsUtils(); + + /** + * Check if an XML document has a certain root element. + * @param xmldoc The document to check for the element. + * @param elementName The element to check for in the document. + * @returns true if the root element exists in the document, false otherwise. + */ + static bool hasRootElement(const QString &xmldoc, const QString &elementName); + + /** + * Check if an XML document has a certain DOCTYPE. + * @param xmldoc The document to check for the doctype. + * @param name The doctype name to check for. Pass QString::null to not check the name. + * @param publicId The public ID to check for. Pass QString::null to not check the ID. + * @param systemId The system ID to check for. Pass QString::null to not check the ID. + * @returns true if the parameters match the doctype, false otherwise. + */ + static bool hasDoctype(const QString &xmldoc, const QString &name/*, const QString &publicId, const QString &systemId*/); + + /** + * Sets the current item in the given combobox to the item with the given text. + * If item with the text not found, does nothing. + */ + static void setCbItemFromText(QComboBox* cb, const QString& text); + +}; + +#endif |