diff options
Diffstat (limited to 'kttsd/plugins/command')
-rw-r--r-- | kttsd/plugins/command/Makefile.am | 18 | ||||
-rw-r--r-- | kttsd/plugins/command/README | 8 | ||||
-rw-r--r-- | kttsd/plugins/command/commandconf.cpp | 217 | ||||
-rw-r--r-- | kttsd/plugins/command/commandconf.h | 121 | ||||
-rw-r--r-- | kttsd/plugins/command/commandconfwidget.ui | 227 | ||||
-rw-r--r-- | kttsd/plugins/command/commandplugin.cpp | 24 | ||||
-rw-r--r-- | kttsd/plugins/command/commandproc.cpp | 446 | ||||
-rw-r--r-- | kttsd/plugins/command/commandproc.h | 201 | ||||
-rw-r--r-- | kttsd/plugins/command/configure.in.in | 17 | ||||
-rw-r--r-- | kttsd/plugins/command/kttsd_commandplugin.desktop | 92 |
10 files changed, 1371 insertions, 0 deletions
diff --git a/kttsd/plugins/command/Makefile.am b/kttsd/plugins/command/Makefile.am new file mode 100644 index 0000000..b180955 --- /dev/null +++ b/kttsd/plugins/command/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_commandplugin.la + +libkttsd_commandplugin_la_SOURCES = \ + commandconfwidget.ui \ + commandconf.cpp \ + commandproc.cpp \ + commandplugin.cpp +libkttsd_commandplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_commandplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_commandplugin.desktop +servicesdir = $(kde_servicesdir) diff --git a/kttsd/plugins/command/README b/kttsd/plugins/command/README new file mode 100644 index 0000000..55839ae --- /dev/null +++ b/kttsd/plugins/command/README @@ -0,0 +1,8 @@ +This is the directory containing the Command plug in. +This plug in is developed and maintained by Gunnar Schmi Dt. +-- This is no longer necesary for this plug in -- +If you intend to edit the GUI (commandconfwidget.ui) you will have to first +copy kttsd/libkttsd/pluginconf.h to a standard include directory +like $KDEDIR/include/ since the interface has been tweaked to derive +from PlugInConf instead of QWidget and Qt Designer refuses to open it +if the header is not properly placed. diff --git a/kttsd/plugins/command/commandconf.cpp b/kttsd/plugins/command/commandconf.cpp new file mode 100644 index 0000000..9d70ff2 --- /dev/null +++ b/kttsd/plugins/command/commandconf.cpp @@ -0,0 +1,217 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration for the Command Plug in + ------------------- + Copyright : (C) 2002,2004 by Gunnar Schmi Dt and Gary Cramblitt + ------------------- + Original author: Gunnar Schmi Dt <kmouth@schmi-dt.de> + 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 <qlayout.h> +#include <qcheckbox.h> +#include <qfile.h> +#include <qapplication.h> +#include <qtextcodec.h> + +// KDE includes. +#include <kdialog.h> +#include <kdebug.h> +#include <klocale.h> +#include <kdialog.h> +#include <kcombobox.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kprogress.h> + +// KTTS includes. +#include <pluginconf.h> +#include <testplayer.h> + +// Command Plugin includes. +#include "commandproc.h" +#include "commandconf.h" + +/** Constructor */ +CommandConf::CommandConf( QWidget* parent, const char* name, const QStringList& /*args*/) : + PlugInConf(parent, name) +{ + // kdDebug() << "CommandConf::CommandConf: Running" << endl; + m_commandProc = 0; + m_progressDlg = 0; + + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "CommandConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new CommandConfWidget(this, "CommandConfigWidget"); + layout->addWidget(m_widget); + + // Build codec list and fill combobox. + m_codecList = PlugInProc::buildCodecList(); + m_widget->characterCodingBox->clear(); + m_widget->characterCodingBox->insertStringList(m_codecList); + + defaults(); + connect(m_widget->characterCodingBox, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->characterCodingBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->stdInButton, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->urlReq, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->commandTestButton, SIGNAL(clicked()), + this, SLOT(slotCommandTest_clicked())); +} + +/** Destructor */ +CommandConf::~CommandConf() +{ + // kdDebug() << "CommandConf::~CommandConf: Running" << endl; + if (!m_waveFile.isNull()) QFile::remove(m_waveFile); + delete m_commandProc; + delete m_progressDlg; +} + +void CommandConf::load(KConfig *config, const QString &configGroup) { + // kdDebug() << "CommandConf::load: Running" << endl; + config->setGroup(configGroup); + m_widget->urlReq->setURL (config->readEntry("Command", "cat -")); + m_widget->stdInButton->setChecked(config->readBoolEntry("StdIn", false)); + QString codecString = config->readEntry("Codec", "Local"); + m_languageCode = config->readEntry("LanguageCode", m_languageCode); + int codec = PlugInProc::codecNameToListIndex(codecString, m_codecList); + m_widget->characterCodingBox->setCurrentItem(codec); +} + +void CommandConf::save(KConfig *config, const QString &configGroup) { + // kdDebug() << "CommandConf::save: Running" << endl; + config->setGroup(configGroup); + config->writeEntry("Command", m_widget->urlReq->url()); + config->writeEntry("StdIn", m_widget->stdInButton->isChecked()); + int codec = m_widget->characterCodingBox->currentItem(); + config->writeEntry("Codec", PlugInProc::codecIndexToCodecName(codec, m_codecList)); +} + +void CommandConf::defaults(){ + // kdDebug() << "CommandConf::defaults: Running" << endl; + m_widget->urlReq->setURL("cat -"); + m_widget->stdInButton->setChecked(false); + m_widget->urlReq->setShowLocalProtocol (false); + m_widget->characterCodingBox->setCurrentItem(0); +} + +void CommandConf::setDesiredLanguage(const QString &lang) +{ + m_languageCode = lang; +} + +QString CommandConf::getTalkerCode() +{ + QString url = m_widget->urlReq->url(); + if (!url.isEmpty()) + { + // Must contain either text or file parameter, or StdIn checkbox must be checked, + // otherwise, does nothing! + if ((url.contains("%t") > 0) || (url.contains("%f") > 0) || m_widget->stdInButton->isChecked()) + { + return QString( + "<voice lang=\"%1\" name=\"%2\" gender=\"%3\" />" + "<prosody volume=\"%4\" rate=\"%5\" />" + "<kttsd synthesizer=\"%6\" />") + .arg(m_languageCode) + .arg("fixed") + .arg("neutral") + .arg("medium") + .arg("medium") + .arg("Command"); + } + } + return QString::null; +} + +void CommandConf::slotCommandTest_clicked() +{ + // kdDebug() << "CommandConf::slotCommandTest_clicked(): " << endl; + // If currently synthesizing, stop it. + if (m_commandProc) + m_commandProc->stopText(); + else + { + m_commandProc = new CommandProc(); + connect (m_commandProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + + // Create a temp file name for the wave file. + KTempFile tempFile (locateLocal("tmp", "commandplugin-"), ".wav"); + QString tmpWaveFile = tempFile.file()->name(); + tempFile.close(); + + // Get test message in the language of the voice. + QString testMsg = testMessage(m_languageCode); + + // Tell user to wait. + m_progressDlg = new KProgressDialog(m_widget, "kttsmgr_command_testdlg", + i18n("Testing"), + i18n("Testing."), + true); + m_progressDlg->progressBar()->hide(); + m_progressDlg->setAllowCancel(true); + + // TODO: Do codec names contain non-ASCII characters? + connect (m_commandProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + m_commandProc->synth( + testMsg, + tmpWaveFile, + m_widget->urlReq->url(), + m_widget->stdInButton->isChecked(), + PlugInProc::codecIndexToCodec(m_widget->characterCodingBox->currentItem(), m_codecList), + m_languageCode); + + // Display progress dialog modally. Processing continues when plugin signals synthFinished, + // or if user clicks Cancel button. + m_progressDlg->exec(); + disconnect (m_commandProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + if (m_progressDlg->wasCancelled()) m_commandProc->stopText(); + delete m_progressDlg; + m_progressDlg = 0; +} + +void CommandConf::slotSynthFinished() +{ + // If user canceled, progress dialog is gone, so exit. + if (!m_progressDlg) + { + m_commandProc->ackFinished(); + return; + } + // Hide the Cancel button so user can't cancel in the middle of playback. + m_progressDlg->showCancelButton(false); + // Get new wavefile name. + m_waveFile = m_commandProc->getFilename(); + // Tell synth we're done. + m_commandProc->ackFinished(); + // Play the wave file (possibly adjusting its Speed). + // Player object deletes the wave file when done. + if (m_player) m_player->play(m_waveFile); + QFile::remove(m_waveFile); + m_waveFile = QString::null; + if (m_progressDlg) m_progressDlg->close(); +} + +void CommandConf::slotSynthStopped() +{ + // Clean up after canceling test. + QString filename = m_commandProc->getFilename(); + if (!filename.isNull()) QFile::remove(filename); +} + +#include "commandconf.moc" diff --git a/kttsd/plugins/command/commandconf.h b/kttsd/plugins/command/commandconf.h new file mode 100644 index 0000000..7e9a573 --- /dev/null +++ b/kttsd/plugins/command/commandconf.h @@ -0,0 +1,121 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration for the Command Plug in + ------------------- + Copyright : (C) 2002,2004 by Gunnar Schmi Dt and Gary Cramblitt + ------------------- + Original author: Gunnar Schmi Dt <kmouth@schmi-dt.de> + 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 _COMMANDCONF_H_ +#define _COMMANDCONF_H_ + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> + +// KDE includes. +#include <kconfig.h> + +// KTTS includes. +#include <pluginconf.h> + +// Command Plugin includes. +#include "commandconfwidget.h" + +class CommandProc; +class KProgressDialog; + +class CommandConf : public PlugInConf { + Q_OBJECT + + public: + /** Constructor */ + CommandConf( QWidget* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~CommandConf(); + + /** 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 this is not called after the modules is loaded, + * so you probably want to call this method in the constructor. + */ + 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". + */ + 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. + */ + void defaults(); + + /** + * 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. + */ + 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. + */ + QString getTalkerCode(); + + private slots: + void configChanged(){ + // kdDebug() << "CommandConf::configChanged: Running" << endl; + emit changed(true); + }; + void slotCommandTest_clicked(); + void slotSynthFinished(); + void slotSynthStopped(); + + private: + QString m_languageCode; + + // Configuration Widget. + CommandConfWidget* m_widget; + + // Command synthesizer. + CommandProc* m_commandProc; + // Synthesized wave file name. + QString m_waveFile; + // Progress dialog. + KProgressDialog* m_progressDlg; + // Codec list. + QStringList m_codecList; +}; +#endif // _COMMANDCONF_H_ diff --git a/kttsd/plugins/command/commandconfwidget.ui b/kttsd/plugins/command/commandconfwidget.ui new file mode 100644 index 0000000..b0b749c --- /dev/null +++ b/kttsd/plugins/command/commandconfwidget.ui @@ -0,0 +1,227 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>CommandConfWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>CommandConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>582</width> + <height>322</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>confiurationBox</cstring> + </property> + <property name="title"> + <string>Co&mmand Configuration</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>11</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="Line" row="1" column="0"> + <property name="name"> + <cstring>line</cstring> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>urlLabel</cstring> + </property> + <property name="text"> + <string>Command &for speaking texts:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>urlReq</cstring> + </property> + </widget> + <widget class="KURLRequester" row="3" column="0"> + <property name="name"> + <cstring>urlReq</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This field specifies both the command used for speaking texts and its parameters. If you want to pass the text as a parameter, write %t at the place where the text should be inserted. To pass a file of the text, write %f. To synthesize only and let KTTSD play the synthesized text, write %w for the generated audio file.</string> + </property> + </widget> + <widget class="QLayoutWidget" row="5" column="0"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>stdInButton</cstring> + </property> + <property name="text"> + <string>&Send the data as standard input</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This check box specifies whether the text is sent as standard input (stdin) to the speech synthesizer.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>201</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>commandTestButton</cstring> + </property> + <property name="text"> + <string>&Test</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="4" column="0"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>characterCodingLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Character &encoding:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>characterCodingBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>characterCodingBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>This combo box specifies which character encoding is used for passing the text.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>explanationLabel</cstring> + </property> + <property name="text"> + <string>Parameters: + %t: Text to be spoken + %f: Filename of a temporary file containing the text + %l: Language (two letter code) + %w: Filename of a temporary file for generated audio</string> + </property> + </widget> + </grid> + </widget> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>30</height> + </size> + </property> + </spacer> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>urlReq</tabstop> + <tabstop>characterCodingBox</tabstop> + <tabstop>stdInButton</tabstop> + <tabstop>commandTestButton</tabstop> +</tabstops> +<includes> + <include location="global" impldecl="in declaration">kurlrequester.h</include> + <include location="global" impldecl="in declaration">klineedit.h</include> + <include location="global" impldecl="in declaration">kpushbutton.h</include> + <include location="global" impldecl="in declaration">kcombobox.h</include> + <include location="global" impldecl="in implementation">kurlrequester.h</include> + <include location="global" impldecl="in implementation">klineedit.h</include> + <include location="global" impldecl="in implementation">kpushbutton.h</include> + <include location="global" impldecl="in implementation">kcombobox.h</include> +</includes> +<signals> + <signal>configurationChanged()</signal> +</signals> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kcombobox.h</includehint> +</includehints> +</UI> diff --git a/kttsd/plugins/command/commandplugin.cpp b/kttsd/plugins/command/commandplugin.cpp new file mode 100644 index 0000000..119ca47 --- /dev/null +++ b/kttsd/plugins/command/commandplugin.cpp @@ -0,0 +1,24 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + ------------------- + Copyright : (C) 2002 by Gunnar Schmi Dt and 2004 by Gary Cramblitt + ------------------- + Original author: Gunnar Schmi Dt <kmouth@schmi-dt.de> + 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. * + * * + ***************************************************************************/ + +#include <kgenericfactory.h> + +#include "commandconf.h" +#include "commandproc.h" + +typedef K_TYPELIST_2( CommandProc, CommandConf ) Command; +K_EXPORT_COMPONENT_FACTORY( libkttsd_commandplugin, KGenericFactory<Command>("kttsd_command") ) + diff --git a/kttsd/plugins/command/commandproc.cpp b/kttsd/plugins/command/commandproc.cpp new file mode 100644 index 0000000..e8d3e72 --- /dev/null +++ b/kttsd/plugins/command/commandproc.cpp @@ -0,0 +1,446 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Main speaking functions for the Command Plug in + ------------------- + Copyright : (C) 2002 by Gunnar Schmi Dt and 2004 by Gary Cramblitt + ------------------- + Original author: Gunnar Schmi Dt <kmouth@schmi-dt.de> + 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 <qfile.h> +#include <qstring.h> +#include <qvaluelist.h> +#include <qstringlist.h> +#include <qregexp.h> +#include <qtextcodec.h> +#include <qvaluestack.h> + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> +#include <kprocess.h> +#include <ktempfile.h> +#include <kstandarddirs.h> + +// KTTS includes. +#include <pluginproc.h> + +// Command Plugin includes. +#include "commandproc.h" +#include "commandproc.moc" + +/** Constructor */ +CommandProc::CommandProc( QObject* parent, const char* name, const QStringList& /*args*/) : + PlugInProc( parent, name ) +{ + kdDebug() << "CommandProc::CommandProc: Running" << endl; + m_commandProc = 0; + m_state = psIdle; + m_stdin = true; + m_supportsSynth = false; + m_waitingStop = false; +} + +/** Destructor */ +CommandProc::~CommandProc() +{ + kdDebug() << "CommandProc::~CommandProc: Running" << endl; + if (m_commandProc) + { + if (m_commandProc->isRunning()) m_commandProc->kill(); + delete m_commandProc; + // Don't delete synth file. That is responsibility of caller. + if (!m_textFilename.isNull()) QFile::remove(m_textFilename); + } +} + +/** Initialize */ +bool CommandProc::init(KConfig *config, const QString &configGroup){ + kdDebug() << "CommandProc::init: Initializing plug in: Command " << endl; + + config->setGroup(configGroup); + m_ttsCommand = config->readEntry("Command", "cat -"); + m_stdin = config->readBoolEntry("StdIn", true); + m_language = config->readEntry("LanguageCode", "en"); + + // Support separate synthesis if the TTS command contains %w macro. + m_supportsSynth = (m_ttsCommand.contains("%w")); + + QString codecString = config->readEntry("Codec", "Local"); + m_codec = codecNameToCodec(codecString); + kdDebug() << "CommandProc::init: Initialized with command: " << m_ttsCommand << " codec: " << codecString << endl; + return true; +} + +/** +* 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 CommandProc::sayText(const QString &text) +{ + synth(text, QString::null, + m_ttsCommand, m_stdin, m_codec, m_language); +} + +/** +* 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 CommandProc::synthText(const QString& text, const QString& suggestedFilename) +{ + synth(text, suggestedFilename, + m_ttsCommand, m_stdin, m_codec, m_language); +} + +/** +* Say or Synthesize text. +* @param inputText The text that shall be spoken +* @param suggestedFilename If not Null, synthesize only to this filename, otherwise +* synthesize and audibilize the text. +* @param userCmd The program that shall be executed for speaking +* @param stdIn True if the program shall recieve its data via standard input +* @param codec The QTextCodec if encoding==UseCodec +* @param language The language code (used for the %l macro) +*/ +void CommandProc::synth(const QString& inputText, const QString& suggestedFilename, + const QString& userCmd, bool stdIn, QTextCodec *codec, QString& language) +{ + if (m_commandProc) + { + if (m_commandProc->isRunning()) m_commandProc->kill(); + delete m_commandProc; + m_commandProc = 0; + m_synthFilename = QString::null; + if (!m_textFilename.isNull()) QFile::remove(m_textFilename); + m_textFilename = QString::null; + } + QString command = userCmd; + QString text = inputText.stripWhiteSpace(); + if (text.isEmpty()) return; + // 1. prepare the text: + // 1.a) encode the text + text += "\n"; + QCString encodedText; + if (codec) + encodedText = codec->fromUnicode(text); + else + encodedText = text.latin1(); // Should not happen, but just in case. + + // 1.b) quote the text as one parameter + QString escText = KShellProcess::quote(text); + + // 1.c) create a temporary file for the text, if %f macro is used. + if (command.contains("%f")) + { + KTempFile tempFile(locateLocal("tmp", "commandplugin-"), ".txt"); + QTextStream* fs = tempFile.textStream(); + fs->setCodec(codec); + *fs << text; + *fs << endl; + m_textFilename = tempFile.file()->name(); + tempFile.close(); + } else m_textFilename = QString::null; + + // 2. replace variables with values + QValueStack<bool> stack; + bool issinglequote=false; + bool isdoublequote=false; + int noreplace=0; + QRegExp re_noquote("(\"|'|\\\\|`|\\$\\(|\\$\\{|\\(|\\{|\\)|\\}|%%|%t|%f|%l|%w)"); + QRegExp re_singlequote("('|%%|%t|%f|%l|%w)"); + QRegExp re_doublequote("(\"|\\\\|`|\\$\\(|\\$\\{|%%|%t|%f|%l|%w)"); + + for ( int i = re_noquote.search(command); + i != -1; + i = (issinglequote?re_singlequote.search(command,i) + :isdoublequote?re_doublequote.search(command,i) + :re_noquote.search(command,i)) + ) + { + if ((command[i]=='(') || (command[i]=='{')) // (...) or {...} + { + // assert(isdoublequote == false) + stack.push(isdoublequote); + if (noreplace > 0) + // count nested braces when within ${...} + noreplace++; + i++; + } + else if (command[i]=='$') + { + stack.push(isdoublequote); + isdoublequote = false; + if ((noreplace > 0) || (command[i+1]=='{')) + // count nested braces when within ${...} + noreplace++; + i+=2; + } + else if ((command[i]==')') || (command[i]=='}')) + // $(...) or (...) or ${...} or {...} + { + if (!stack.isEmpty()) + isdoublequote = stack.pop(); + else + qWarning("Parse error."); + if (noreplace > 0) + // count nested braces when within ${...} + noreplace--; + i++; + } + else if (command[i]=='\'') + { + issinglequote=!issinglequote; + i++; + } + else if (command[i]=='"') + { + isdoublequote=!isdoublequote; + i++; + } + else if (command[i]=='\\') + i+=2; + else if (command[i]=='`') + { + // Replace all `...` with safer $(...) + command.replace (i, 1, "$("); + QRegExp re_backticks("(`|\\\\`|\\\\\\\\|\\\\\\$)"); + for ( int i2=re_backticks.search(command,i+2); + i2!=-1; + i2=re_backticks.search(command,i2) + ) + { + if (command[i2] == '`') + { + command.replace (i2, 1, ")"); + i2=command.length(); // leave loop + } + else + { // remove backslash and ignore following character + command.remove (i2, 1); + i2++; + } + } + // Leave i unchanged! We need to process "$(" + } + else if (noreplace == 0) // do not replace macros within ${...} + { + QString match, v; + + // get match + if (issinglequote) + match=re_singlequote.cap(); + else if (isdoublequote) + match=re_doublequote.cap(); + else + match=re_noquote.cap(); + + // substitue %variables + if (match=="%%") + v="%"; + else if (match=="%t") + v=escText; + else if (match=="%f") + v=m_textFilename; + else if (match=="%l") + v=language; + else if (match=="%w") + v = suggestedFilename; + + // %variable inside of a quote? + if (isdoublequote) + v='"'+v+'"'; + else if (issinglequote) + v="'"+v+"'"; + + command.replace (i, match.length(), v); + i+=v.length(); + } + else + { + if (issinglequote) + i+=re_singlequote.matchedLength(); + else if (isdoublequote) + i+=re_doublequote.matchedLength(); + else + i+=re_noquote.matchedLength(); + } + } + + // 3. create a new process + kdDebug() << "CommandProc::synth: running command: " << command << endl; + m_commandProc = new KProcess; + m_commandProc->setUseShell(true); + m_commandProc->setEnvironment("LANG", language + "." + codec->mimeName()); + m_commandProc->setEnvironment("LC_CTYPE", language + "." + codec->mimeName()); + *m_commandProc << command; + connect(m_commandProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + connect(m_commandProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_commandProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + connect(m_commandProc, SIGNAL(wroteStdin(KProcess*)), + this, SLOT(slotWroteStdin(KProcess* ))); + + // 4. start the process + + if (suggestedFilename.isNull()) + m_state = psSaying; + else + { + m_synthFilename = suggestedFilename; + m_state = psSynthing; + } + if (stdIn) { + m_commandProc->start(KProcess::NotifyOnExit, KProcess::All); + if (encodedText.length() > 0) + m_commandProc->writeStdin(encodedText, encodedText.length()); + else + m_commandProc->closeStdin(); + } + else + m_commandProc->start(KProcess::NotifyOnExit, KProcess::AllOutput); +} + +/** +* 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 CommandProc::getFilename() +{ + kdDebug() << "CommandProc::getFilename: returning " << m_synthFilename << endl; + return m_synthFilename; +} + +/** +* 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. +*/ +void CommandProc::stopText(){ + kdDebug() << "CommandProc::stopText: Running" << endl; + if (m_commandProc) + { + if (m_commandProc->isRunning()) + { + kdDebug() << "CommandProc::stopText: killing Command." << endl; + m_waitingStop = true; + m_commandProc->kill(); + } else m_state = psIdle; + }else m_state = psIdle; + kdDebug() << "CommandProc::stopText: Command stopped." << endl; +} + +void CommandProc::slotProcessExited(KProcess*) +{ + kdDebug() << "CommandProc:slotProcessExited: Command process has exited." << endl; + pluginState prevState = m_state; + if (m_waitingStop) + { + m_waitingStop = false; + m_state = psIdle; + emit stopped(); + } else { + m_state = psFinished; + if (prevState == psSaying) + emit sayFinished(); + else + if (prevState == psSynthing) + emit synthFinished(); + } +} + +void CommandProc::slotReceivedStdout(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "CommandProc::slotReceivedStdout: Received output from Command: " << buf << endl; +} + +void CommandProc::slotReceivedStderr(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "CommandProc::slotReceivedStderr: Received error from Command: " << buf << endl; +} + +void CommandProc::slotWroteStdin(KProcess*) +{ + kdDebug() << "CommandProc::slotWroteStdin: closing Stdin" << endl; + m_commandProc->closeStdin(); +} + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @see pluginState +*/ +pluginState CommandProc::getState() { return m_state; } + +/** +* 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 CommandProc::ackFinished() +{ + if (m_state == psFinished) + { + m_state = psIdle; + m_synthFilename = QString::null; + if (!m_textFilename.isNull()) QFile::remove(m_textFilename); + m_textFilename = QString::null; + } +} + +/** +* 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. +*/ +bool CommandProc::supportsAsync() { return true; } + +/** +* 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 CommandProc::supportsSynth() { return m_supportsSynth; } diff --git a/kttsd/plugins/command/commandproc.h b/kttsd/plugins/command/commandproc.h new file mode 100644 index 0000000..4b46fe5 --- /dev/null +++ b/kttsd/plugins/command/commandproc.h @@ -0,0 +1,201 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Main speaking functions for the Command Plug in + ------------------- + Copyright : (C) 2002 by Gunnar Schmi Dt and 2004 by Gary Cramblitt + ------------------- + Original author: Gunnar Schmi Dt <kmouth@schmi-dt.de> + 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 _COMMANDPROC_H_ +#define _COMMANDPROC_H_ + +// Qt includes. +#include <qstringlist.h> + +// KTTS includes. +#include <pluginproc.h> + +class KProcess; +class QTextCodec; + +class CommandProc : public PlugInProc{ + Q_OBJECT + + public: + /** Constructor */ + CommandProc( QObject* parent = 0, const char* name = 0, + const QStringList &args = QStringList()); + + /** Destructor */ + ~CommandProc(); + + /** Initializate the speech */ + bool init (KConfig *config, const QString &configGroup); + + /** + * Say a text string. + * @param text The text to speak. + */ + 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. + */ + virtual void 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. + */ + 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. + */ + virtual bool supportsSynth(); + + /** + * Say or Synthesize text. + * @param inputText The text that shall be spoken + * @param suggestedFilename If not Null, synthesize only to this filename, otherwise + * synthesize and audibilize the text. + * @param userCmd The program that shall be executed for speaking + * @param stdIn True if the program shall recieve its data via + * standard input. + * @param codec Codec for encoding the text. + * @param language The language code (used for the %l macro) + */ + void synth(const QString& inputText, const QString& suggestedFilename, + const QString& userCmd, bool stdIn, + QTextCodec *codec, QString& language); + + private slots: + void slotProcessExited(KProcess* proc); + void slotReceivedStdout(KProcess* proc, char* buffer, int buflen); + void slotReceivedStderr(KProcess* proc, char* buffer, int buflen); + void slotWroteStdin(KProcess* proc); + + private: + + /** + * True if the plugin supports separate synthesis (option set by user). + */ + bool m_supportsSynth; + + /** + * TTS command + */ + QString m_ttsCommand; + + /** + * True if process should use Stdin. + */ + bool m_stdin; + + /** + * Language Group. + */ + QString m_language; + + /** + * Codec. + */ + QTextCodec* m_codec; + + /** + * Flite process + */ + KProcess* m_commandProc; + + /** + * Name of temporary file containing text. + */ + QString m_textFilename; + + /** + * Synthesis filename. + */ + QString m_synthFilename; + + /** + * Plugin state. + */ + pluginState m_state; + + /** + * True when stopText has been called. Used to force transition to psIdle when + * Flite exits. + */ + bool m_waitingStop; +}; + +#endif // _COMMANDPROC_H_ diff --git a/kttsd/plugins/command/configure.in.in b/kttsd/plugins/command/configure.in.in new file mode 100644 index 0000000..125ee26 --- /dev/null +++ b/kttsd/plugins/command/configure.in.in @@ -0,0 +1,17 @@ +dnl ========================== +dnl checks for Command plug in +dnl ========================== + +AC_ARG_ENABLE(kttsd-command, + AC_HELP_STRING([--enable-kttsd-command], + [build KTTSD Command Plugin [default=yes]]), + command_plugin=$enableval, + command_plugin=yes) + +compile_command_plugin="no" + +if test "x$command_plugin" = "xyes"; then + compile_command_plugin="yes" +fi + +AM_CONDITIONAL(include_kttsd_command, test "x$compile_command_plugin" = "xyes") diff --git a/kttsd/plugins/command/kttsd_commandplugin.desktop b/kttsd/plugins/command/kttsd_commandplugin.desktop new file mode 100644 index 0000000..c141790 --- /dev/null +++ b/kttsd/plugins/command/kttsd_commandplugin.desktop @@ -0,0 +1,92 @@ +[Desktop Entry] +Name=Command +Name[br]=Urzhiad +Name[bs]=Naredba +Name[ca]=Odre +Name[cs]=Příkaz +Name[cy]=Gorchymyn +Name[da]=Kommando +Name[de]=Befehl +Name[el]=Εντολή +Name[es]=Orden +Name[et]=Käsk +Name[fa]=فرمان +Name[fi]=Komento +Name[fr]=Commande +Name[ga]=Ordú +Name[gl]=Comando +Name[he]=פקודה +Name[hu]=Parancs +Name[is]=Skipun +Name[it]=Comando +Name[ja]=コマンド +Name[ka]=ბრძანება +Name[km]=ពាក្យបញ្ជា +Name[mk]=Командна линија +Name[ms]=Arahan +Name[nb]=Kommando +Name[nds]=Befehl +Name[ne]=आदेश +Name[pa]=ਕਮਾਂਡ +Name[pl]=Polecenie +Name[pt]=Comando +Name[ru]=Командная строка +Name[sk]=Príkaz +Name[sl]=Ukaz +Name[sr]=Наредба +Name[sr@Latn]=Naredba +Name[sv]=Kommando +Name[ta]=கட்டளை +Name[tg]=Сатри фармоишӣ +Name[tr]=Komut +Name[uk]=Команда +Name[vi]=Ra lệnh +Name[zh_TW]=命令 +Comment=Generic speech synthesizer from command line +Comment[bg]=Общ синтезатор на глас от командния ред +Comment[bs]=Generalna sinteza govora sa komandne linije +Comment[ca]=Sintetitzador de veu genèric de línia d'ordres +Comment[cs]=Hlasový syntetizátor pro příkazovou řádku +Comment[da]=Generisk tale-synthesizer fra kommandolinjen +Comment[de]=Generischer Sprachsynthesizer in der Befehlszeile +Comment[el]=Γενικός συνθέτης ομιλίας για τη γραμμή εντολών +Comment[es]=Sintetizador genérico de texto a voz para la línea de órdenes +Comment[et]=Üldine käsurea-kõnesüntesaator +Comment[eu]=Komando-lerroko hizketa-sintetizadore generikoa +Comment[fa]=ترکیبدهندۀ گفتار عمومی از خط فرمان +Comment[fi]=Yleinen komentorivipohjainen puhesyntetisaattori +Comment[fr]=Synthèse vocale générique en ligne de commande +Comment[ga]=Sintéiseoir cainte ginearálta ó líne na n-orduithe +Comment[gl]=Sintetizados de fala xenérico para a liña de comandos +Comment[hu]=Parancssoros kezelőprogram szövegfelolvasáshoz +Comment[is]=Almennur talgerfill frá skipanalínu +Comment[it]=Sintetizzatore vocale generico dalla riga di comando +Comment[ja]=コマンドラインからの汎用スピーチシンセサイザ +Comment[ka]=საზოგადო ხმის სინქრონიზატორი ბრძანების ველიდან +Comment[km]=កម្មវិធីសង្គ្រោះការនិយាយទូទៅពីបន្ទាត់ពាក្យបញ្ជា +Comment[mk]=Синтисајзер на општ говор од командната линија +Comment[ms]=Pensintesis tutur generik dari baris arahan +Comment[nb]=Generisk talesyntetisering fra kommandolinje +Comment[nds]=Blicksnuut för de Befehlsreeg +Comment[ne]=आदेश रेखाबाट जेनेरिक संवाद सिन्थेसाइजर +Comment[nl]=Generieke spraaksynthesizer voor de commandoregel +Comment[pa]=ਕਮਾਂਡ ਲਾਈਨ ਤੋਂ ਸਧਾਰਨ ਬੋਲੀ ਸੰਸਲੇਸ਼ਕ +Comment[pl]=Program syntezatora mowy uruchamiany z linii poleceń +Comment[pt]=Sintetizador de fala genérico através de uma linha de comandos +Comment[pt_BR]=Interface de linha de comando para o sintetizador de fala genérico +Comment[ru]=Консольный интерфейс к движкам синтеза речи +Comment[sk]=Všeobecný syntetizátor reči z príkazového riadka +Comment[sl]=Generični sintetizator govora iz ukazne vrstice +Comment[sr]=Генерички синтетизатор говора из командне линије +Comment[sr@Latn]=Generički sintetizator govora iz komandne linije +Comment[sv]=Generell talsyntes från kommandoraden +Comment[ta]=கட்டளை வரியில் இருந்து பொது பேச்சு கூட்டிணைப்பாளர் +Comment[tg]=Консоли интерфейс ба микшерҳо барои таҳлили овоз +Comment[tr]=Komut satırından genel konuşma bireştirici +Comment[uk]=Загальний синтезатор мовлення з командного рядка +Comment[vi]=Tổng hợp giọng nói chung cho việc ra lệnh +Comment[zh_TW]=從命令列的一般語音合成器 +Type=Service +ServiceTypes=KTTSD/SynthPlugin +X-KDE-Library=libkttsd_commandplugin +X-KDE-Languages=other |