diff options
Diffstat (limited to 'kttsd/plugins/hadifix')
-rw-r--r-- | kttsd/plugins/hadifix/Makefile.am | 23 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/README | 3 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl | 164 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/configure.in.bot | 37 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/configure.in.in | 37 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixconf.cpp | 406 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixconf.h | 83 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixconfigui.ui | 692 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixconfigui.ui.h | 114 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixplugin.cpp | 23 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixproc.cpp | 411 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/hadifixproc.h | 203 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/initialconfig.h | 164 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/kttsd_hadifixplugin.desktop | 51 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/voicefileui.ui | 119 | ||||
-rw-r--r-- | kttsd/plugins/hadifix/voicefileui.ui.h | 35 |
16 files changed, 2565 insertions, 0 deletions
diff --git a/kttsd/plugins/hadifix/Makefile.am b/kttsd/plugins/hadifix/Makefile.am new file mode 100644 index 0000000..35ca445 --- /dev/null +++ b/kttsd/plugins/hadifix/Makefile.am @@ -0,0 +1,23 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_hadifixplugin.la + +libkttsd_hadifixplugin_la_SOURCES = \ + hadifixconf.cpp \ + hadifixproc.cpp \ + hadifixplugin.cpp \ + hadifixconfigui.ui \ + voicefileui.ui +libkttsd_hadifixplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_hadifixplugin_la_LIBADD = $(LIB_KDECORE) $(LIB_KFILE) $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_hadifixplugin.desktop +servicesdir = $(kde_servicesdir) + +# Data files. +festivalintxsltdatadir = $(kde_datadir)/kttsd/hadifix/xslt/ +festivalintxsltdata_DATA = SSMLtoTxt2pho.xsl diff --git a/kttsd/plugins/hadifix/README b/kttsd/plugins/hadifix/README new file mode 100644 index 0000000..f41b967 --- /dev/null +++ b/kttsd/plugins/hadifix/README @@ -0,0 +1,3 @@ +This is the directory containing the Hadifix plug in. +This plug in is developed and maintained by Gunnar Schmi Dt. +Rework by Gary Cramblitt <garycramblitt@comcast.net> diff --git a/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl b/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl new file mode 100644 index 0000000..5a81c8f --- /dev/null +++ b/kttsd/plugins/hadifix/SSMLtoTxt2pho.xsl @@ -0,0 +1,164 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="text" encoding="ISO-8859-1" indent="no"/> + +<!-- XSLT stylesheet to convert SSML into a format that can be processed by the + Hadifix txt2pho processor. + + (c) 2004 by Gary Cramblitt + + Original author: Gary Cramblitt <garycramblitt@comcast.net> + 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; either version 2 of the License, or * + (at your option) any later version. * + + The txt2pho processor permits special markup to be embedded in text to control + speech attributes such as speed (duration), pitch, etc. + The markup must be inside curly braces and separated from other text by a space. + Spaces within the markup are not permitted. + See the txt2pho README file for details. + + Something the README does not say is that {Pitch} markup applies only to one + sentence and reverts back to normal when the sentence is completed. + It means that we must repeatedly ouput {Pitch} markup + for each sentence in the text. For example, the SSML + + <prosody pitch="high">Sentence one. Sentence two.</prosody> Sentence three. + + must be converted to + + {Pitch:300} Sentence one. {Pitch:300} Sentence two. Sentence three. + + {Duration} markup, on the other hand, is addative and stays in effect until + changed. For example, the SSML + + <prosody rate="fast">Sentence one. Sentence two.</prosody> Sentence three. + + must be converted to + + {Duration:-0.5} Sentence one. Sentence two. {Duration:0.5} Sentence three. + + txt2pho will also stop processing when it sees a newline. Therefore, we must take + care to strip all newlines and avoid inserting any newlines. + --> + +<!-- Strip all elements and attributes from output. --> +<xsl:strip-space elements="*" /> + +<xsl:template match="speak"> + <xsl:apply-templates/> +</xsl:template> + +<!-- Handle markup that maintains state. --> +<xsl:template match="prosody"> + <!-- Rate (speed), Rates are addative and stay in effect until changed. --> + <!-- TODO: SSML permits nesting of prosody elements. As coded below, + <prosody rate="slow">One<prosody rate="slow">Two</prosody</prosody> + will pronounce "Two" doubly slow, which it should not do. --> + <xsl:choose> + <xsl:when test="@rate='fast'"> + <!-- Increase speed. --> + <xsl:value-of select="'{Duration:-0.5} '"/> + <!-- Continue processing. --> + <xsl:apply-templates/> + <!-- Decrease speed. --> + <xsl:value-of select="'{Duration:0.5} '"/> + </xsl:when> + <xsl:when test="@rate='slow'"> + <!-- Decrease speed. --> + <xsl:value-of select="'{Duration:0.5} '"/> + <!-- Continue processing. --> + <xsl:apply-templates/> + <!-- Increase speed. --> + <xsl:value-of select="'{Duration:-0.5} '"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- Outputs a sentence with txt2pho markup. + Called in the context of a text node. Obtain markup by + extracting ancestor node attributes. + @param sentence The sentence to mark up. + @return Sentence preceeded by txt2pho markup. + --> +<xsl:template name="output-sentence"> + <xsl:param name="sentence"/> + <!-- Pitch --> + <xsl:if test="ancestor::prosody/@pitch='high'"> + <xsl:value-of select="'{Pitch:300} '"/> + </xsl:if> + <xsl:if test="ancestor::prosody/@pitch='low'"> + <xsl:value-of select="'{Pitch:-40} '"/> + </xsl:if> + <!-- Output the sentence itself. --> + <xsl:value-of select="$sentence"/> +</xsl:template> + +<!-- Return the first sentence of argument. + Sentence delimiters are '.:;!?' + @param paragraph Paragraph from which to extract first sentence. + @return The first sentence, or if no such sentence, the paragraph. + --> +<xsl:template name="parse-sentence"> + <xsl:param name="paragraph"/> + <!-- Copy paragraph, replacing all delimeters with period. --> + <xsl:variable name="tmp"> + <xsl:value-of select="translate($paragraph,':;!?','....')"/> + </xsl:variable> + <!-- Look for first period and space and extract corresponding substring from original. --> + <xsl:choose> + <xsl:when test="contains($tmp, '. ')"> + <xsl:value-of select="substring($paragraph, 1, string-length(substring-before($tmp, '. '))+2)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$paragraph"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- Process a paragraph, outputting each sentence with txt2pho markup. + @param paragraph The paragraph to process. + @return The paragraph with each sentence preceeded by txt2pho markup. + --> +<xsl:template name="output-paragraph"> + <xsl:param name="paragraph" /> + <!-- Stop when no more sentences to output. --> + <xsl:choose> + <xsl:when test="normalize-space($paragraph)!=''"> + <!-- Split the paragraph into first sentence and rest of paragraph (if any). --> + <xsl:variable name="sentence"> + <xsl:call-template name="parse-sentence"> + <xsl:with-param name="paragraph" select="$paragraph"/> + </xsl:call-template> + </xsl:variable> + <!-- debug: <xsl:value-of select="concat('[sentence: ',$sentence,']')"/> --> + <xsl:variable name="rest"> + <xsl:value-of select="substring-after($paragraph,$sentence)" /> + </xsl:variable> + <!-- Output the sentence with markup. --> + <xsl:call-template name="output-sentence" > + <xsl:with-param name="sentence" select="$sentence" /> + </xsl:call-template> + <!-- Recursively process the rest of the paragraph. --> + <xsl:call-template name="output-paragraph"> + <xsl:with-param name="paragraph" select="$rest" /> + </xsl:call-template> + </xsl:when> + </xsl:choose> +</xsl:template> + +<!-- Process each text node. --> +<xsl:template match="text()"> + <!-- debug: <xsl:value-of select="concat('[paragraph: ',normalize-space(.),']')"/> --> + <xsl:call-template name="output-paragraph"> + <xsl:with-param name="paragraph" select="concat(normalize-space(.),' ')"/> + </xsl:call-template> +</xsl:template> + +</xsl:stylesheet> diff --git a/kttsd/plugins/hadifix/configure.in.bot b/kttsd/plugins/hadifix/configure.in.bot new file mode 100644 index 0000000..ffada4f --- /dev/null +++ b/kttsd/plugins/hadifix/configure.in.bot @@ -0,0 +1,37 @@ +if test "x$compile_hadifix_plugin" = "xyes"; then + if test "x$hadifix_inst" = "xno"; then + echo "" + echo "======================================================" + echo "Some of the tools needed to run Hadifix do not appear" + echo "to be installed on this system. (mbrola and txt2pho" + echo "were not found in the PATH or /etc/txt2pho was not" + echo "found). The Hadifix plugin will be built, but you need" + echo "to install the required tools before you can use it:" + echo "" + if test "x$hadifix_mbrola_bin" = "xno"; then + echo "- mbrola was not found. You can get it from" + echo " http://tcts.fpms.ac.be/synthesis/mbrola.html" + fi + if test "x$hadifix_txt2pho_bin" = "xno"; then + echo "- txt2pho was not found. You can get it from" + echo " http://www.ikp.uni-bonn.de/dt/forsch/phonetik/" + echo " hadifix/HADIFIXforMBROLA.html" + fi + echo "" + echo "Please read the KTTS Handbook for further information." + echo "=====================================================" + all_tests=bad + fi + if test "x$hadifix_inst" = "xyes"; then + if test "x$hadifix_txt2pho" = "xno"; then + echo "" + echo "======================================================" + echo "The configuration file /etc/txt2pho was not found on" + echo "this system. This is no problem as long as all users" + echo "have a valid ~/.txt2phorc in their home directories if" + echo "they want to use the Hadifix plugin." + echo "======================================================" + fi + fi +fi + diff --git a/kttsd/plugins/hadifix/configure.in.in b/kttsd/plugins/hadifix/configure.in.in new file mode 100644 index 0000000..3c997c2 --- /dev/null +++ b/kttsd/plugins/hadifix/configure.in.in @@ -0,0 +1,37 @@ +dnl ========================== +dnl checks for Hadifix plug in +dnl ========================== + +AC_ARG_ENABLE(kttsd-hadifix, + AC_HELP_STRING([--enable-kttsd-hadifix], + [build KTTSD Hadifix Plugin [default=yes]]), + hadifix_plugin=$enableval, + hadifix_plugin=yes) + +compile_hadifix_plugin="no" + +if test "x$hadifix_plugin" = "xyes"; then + compile_hadifix_plugin="yes" +fi + +if test "x$compile_hadifix_plugin" = "xyes"; then + dnl Check for Hadifix installation. + dnl Note that Hadifix plugin is always built, + dnl unless user overrides with -disable-kttsd-hadifix. + AC_PATH_PROG(hadifix_mbrola_bin, "mbrola", "no", [$PATH:]) + AC_PATH_PROG(hadifix_txt2pho_bin, "txt2pho", "no", [$PATH:]) + + hadifix_inst="yes" + if test "x$hadifix_mbrola_bin" = "xno"; then + hadifix_inst="no" + fi + if test "x$hadifix_txt2pho_bin" = "xno"; then + hadifix_inst="no" + fi + + if test "x$hadifix_inst" = "xyes"; then + AC_CHECK_FILE(/etc/txt2pho, [hadifix_txt2pho="yes"], [hadifix_txt2pho="no"]) + fi +fi + +AM_CONDITIONAL(include_kttsd_hadifix, test "x$compile_hadifix_plugin" = "xyes") diff --git a/kttsd/plugins/hadifix/hadifixconf.cpp b/kttsd/plugins/hadifix/hadifixconf.cpp new file mode 100644 index 0000000..a2c5547 --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixconf.cpp @@ -0,0 +1,406 @@ +/*************************************************************************** + begin : Mon Okt 14 2002 + copyright : (C) 2002 by Gunnar Schmi Dt + email : gunnar@schmi-dt.de + current mainainer: : 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. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qlayout.h> +#include <qlabel.h> +#include <qgroupbox.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qfile.h> + +// KDE includes. +#include <ktempfile.h> +#include <kaboutdata.h> +#include <kaboutapplication.h> +#include <kdebug.h> +#include <klocale.h> +#include <kdialog.h> +#include <kcombobox.h> +#include <kstandarddirs.h> +#include <kurlrequester.h> +#include <kdialogbase.h> +#include <klineedit.h> +#include <knuminput.h> +#include <kprogress.h> + +// KTTS includes. +#include <pluginconf.h> +#include <testplayer.h> +#include <talkercode.h> + +// Hadifix includes. +#include "hadifixproc.h" +#include "voicefileui.h" +#include "hadifixconfigui.h" +#include "hadifixconf.h" +#include "hadifixconf.moc" + +class HadifixConfPrivate { + friend class HadifixConf; + private: + HadifixConfPrivate() { + hadifixProc = 0; + progressDlg = 0; + findInitialConfig(); + }; + + ~HadifixConfPrivate() { + if (hadifixProc) hadifixProc->stopText(); + delete hadifixProc; + if (!waveFile.isNull()) QFile::remove(waveFile); + delete progressDlg; + }; + + #include "initialconfig.h" + + void setConfiguration (QString hadifixExec, QString mbrolaExec, + QString voice, bool male, + int volume, int time, int pitch, + QString codecName) + { + configWidget->hadifixURL->setURL (hadifixExec); + configWidget->mbrolaURL->setURL (mbrolaExec); + configWidget->setVoice (voice, male); + + configWidget->volumeBox->setValue (volume); + configWidget->timeBox->setValue (time); + configWidget->frequencyBox->setValue (pitch); + int codec = PlugInProc::codecNameToListIndex(codecName, codecList); + configWidget->characterCodingBox->setCurrentItem(codec); + } + + void initializeVoices () { + QStringList::iterator it; + for (it = defaultVoices.begin(); it != defaultVoices.end(); ++it) { + HadifixProc::VoiceGender gender; + QString name = QFileInfo(*it).fileName(); + gender = HadifixProc::determineGender(defaultMbrolaExec, *it); + if (gender == HadifixProc::MaleGender) + configWidget->addVoice(*it, true, i18n("Male voice \"%1\"").arg(name)); + else if (gender == HadifixProc::FemaleGender) + configWidget->addVoice(*it, false, i18n("Female voice \"%1\"").arg(name)); + else { + if (name == "de1") + configWidget->addVoice(*it, false, i18n("Female voice \"%1\"").arg(name)); + else { + configWidget->addVoice(*it, true, i18n("Unknown voice \"%1\"").arg(name)); + configWidget->addVoice(*it, false, i18n("Unknown voice \"%1\"").arg(name)); + } + } + } + }; + + void initializeCharacterCodes() { + // Build codec list and fill combobox. + codecList = PlugInProc::buildCodecList(); + configWidget->characterCodingBox->clear(); + configWidget->characterCodingBox->insertStringList(codecList); + } + + void setDefaultEncodingFromVoice() { + QString voiceFile = configWidget->getVoiceFilename(); + QString voiceCode = QFileInfo(voiceFile).baseName(false); + voiceCode = voiceCode.left(2); + QString codecName = "Local"; + if (voiceCode == "de") codecName = "ISO 8859-1"; + if (voiceCode == "hu") codecName = "ISO 8859-2"; + configWidget->characterCodingBox->setCurrentItem(PlugInProc::codecNameToListIndex( + codecName, codecList)); +} + + void setDefaults () { + QStringList::iterator it = defaultVoices.begin(); + // Find a voice that matches language code, if any. + if (!languageCode.isEmpty()) + { + QString justLang = languageCode.left(2); + for (;it != defaultVoices.end();++it) + { + QString voiceCode = QFileInfo(*it).baseName(false).left(2); + if (voiceCode == justLang) break; + } + if (it == defaultVoices.end()) it = defaultVoices.begin(); + } + HadifixProc::VoiceGender gender; + gender = HadifixProc::determineGender(defaultMbrolaExec, *it); + + setConfiguration (defaultHadifixExec, defaultMbrolaExec, + *it, gender == HadifixProc::MaleGender, + 100, 100, 100, "Local"); + }; + + void load (KConfig *config, const QString &configGroup) { + config->setGroup(configGroup); + + QString voice = config->readEntry("voice", configWidget->getVoiceFilename()); + + HadifixProc::VoiceGender gender; + gender = HadifixProc::determineGender(defaultMbrolaExec, voice); + bool isMale = (gender == HadifixProc::MaleGender); + + QString defaultCodecName = "Local"; + // TODO: Need a better way to determine proper codec for each voice. + // This will do for now. + QString voiceCode = QFileInfo(voice).baseName(false); + if (voiceCode.left(2) == "de") defaultCodecName = "ISO 8859-1"; + if (voiceCode.left(2) == "hu") defaultCodecName = "ISO 8859-2"; + + setConfiguration ( + config->readEntry ("hadifixExec",defaultHadifixExec), + config->readEntry ("mbrolaExec", defaultMbrolaExec), + config->readEntry ("voice", voice), + config->readBoolEntry("gender", isMale), + config->readNumEntry ("volume", 100), + config->readNumEntry ("time", 100), + config->readNumEntry ("pitch", 100), + config->readEntry ("codec", defaultCodecName) + ); + }; + + void save (KConfig *config, const QString &configGroup) { + config->setGroup(configGroup); + config->writeEntry ("hadifixExec", PlugInConf::realFilePath(configWidget->hadifixURL->url())); + config->writeEntry ("mbrolaExec", PlugInConf::realFilePath(configWidget->mbrolaURL->url())); + config->writeEntry ("voice", configWidget->getVoiceFilename()); + config->writeEntry ("gender", configWidget->isMaleVoice()); + config->writeEntry ("volume", configWidget->volumeBox->value()); + config->writeEntry ("time", configWidget->timeBox->value()); + config->writeEntry ("pitch", configWidget->frequencyBox->value()); + config->writeEntry ("codec", PlugInProc::codecIndexToCodecName( + configWidget->characterCodingBox->currentItem(), codecList)); + } + + HadifixConfigUI *configWidget; + + QString languageCode; + QString defaultHadifixExec; + QString defaultMbrolaExec; + QStringList defaultVoices; + QStringList codecList; + + // Wave file playing on play object. + QString waveFile; + // Synthesizer. + HadifixProc* hadifixProc; + // Progress Dialog. + KProgressDialog* progressDlg; +}; + +/** Constructor */ +HadifixConf::HadifixConf( QWidget* parent, const char* name, const QStringList &) : + PlugInConf( parent, name ){ + // kdDebug() << "HadifixConf::HadifixConf: Running" << endl; + QVBoxLayout *layout = new QVBoxLayout (this, KDialog::marginHint(), KDialog::spacingHint(), "CommandConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + + d = new HadifixConfPrivate(); + d->configWidget = new HadifixConfigUI (this, "configWidget"); + + QString file = locate("data", "LICENSES/LGPL_V2"); + i18n("This plugin is distributed under the terms of the GPL v2 or later."); + + connect(d->configWidget->voiceButton, SIGNAL(clicked()), this, SLOT(voiceButton_clicked())); + connect(d->configWidget->testButton, SIGNAL(clicked()), this, SLOT(testButton_clicked())); + connect(d->configWidget, SIGNAL(changed(bool)), this, SLOT(configChanged (bool))); + connect(d->configWidget->characterCodingBox, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(d->configWidget->voiceCombo, SIGNAL(activated(int)), this, SLOT(voiceCombo_activated(int))); + d->initializeCharacterCodes(); + d->initializeVoices(); + d->setDefaults(); + layout->addWidget (d->configWidget); +} + +/** Destructor */ +HadifixConf::~HadifixConf(){ + // kdDebug() << "HadifixConf::~HadifixConf: Running" << endl; + delete d; +} + +void HadifixConf::load(KConfig *config, const QString &configGroup) { + // kdDebug() << "HadifixConf::load: Running" << endl; + d->setDefaults(); + d->load (config, configGroup); +} + +void HadifixConf::save(KConfig *config, const QString &configGroup) { + // kdDebug() << "HadifixConf::save: Running" << endl; + d->save (config, configGroup); +} + +void HadifixConf::defaults() { + // kdDebug() << "HadifixConf::defaults: Running" << endl; + d->setDefaults(); +} + +void HadifixConf::setDesiredLanguage(const QString &lang) +{ + d->languageCode = lang; +} + +QString HadifixConf::getTalkerCode() +{ + if (!d->configWidget->hadifixURL->url().isEmpty() && !d->configWidget->mbrolaURL->url().isEmpty()) + { + QString voiceFile = d->configWidget->getVoiceFilename(); + if (QFileInfo(voiceFile).exists()) + { + // mbrola voice file names usually start with two-letter language code, + // but this is by no means guaranteed. + QString voiceCode = QFileInfo(voiceFile).baseName(false); + QString voiceLangCode = voiceCode.left(2); + if (d->languageCode.left(2) != voiceLangCode) + { + // Verify that first two letters of voice filename are a valid language code. + // If they are, switch to that language. + if (!TalkerCode::languageCodeToLanguage(voiceLangCode).isEmpty()) + d->languageCode = voiceLangCode; + } + QString gender = "male"; + if (!d->configWidget->isMaleVoice()) gender = "female"; + QString volume = "medium"; + if (d->configWidget->volumeBox->value() < 75) volume = "soft"; + if (d->configWidget->volumeBox->value() > 125) volume = "loud"; + QString rate = "medium"; + if (d->configWidget->timeBox->value() < 75) rate = "slow"; + if (d->configWidget->timeBox->value() > 125) rate = "fast"; + return QString( + "<voice lang=\"%1\" name=\"%2\" gender=\"%3\" />" + "<prosody volume=\"%4\" rate=\"%5\" />" + "<kttsd synthesizer=\"%6\" />") + .arg(d->languageCode) + .arg(voiceCode) + .arg(gender) + .arg(volume) + .arg(rate) + .arg("Hadifix"); + } + } + return QString::null; +} + +void HadifixConf::voiceButton_clicked () { + KDialogBase *dialog = new KDialogBase (this, 0, true, + i18n("Voice File - Hadifix Plugin"), + KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok, true); + VoiceFileWidget *widget = new VoiceFileWidget(dialog); + dialog->setMainWidget (widget); + + widget->femaleOption->setChecked(!d->configWidget->isMaleVoice()); + widget->maleOption->setChecked(d->configWidget->isMaleVoice()); + widget->voiceFileURL->setURL(d->configWidget->getVoiceFilename()); + widget->mbrola = d->defaultMbrolaExec; + + if (dialog->exec() == QDialog::Accepted) { + d->configWidget->setVoice (widget->voiceFileURL->url(), + widget->maleOption->isChecked()); + d->setDefaultEncodingFromVoice(); + emit changed(true); + } + + delete dialog; +} + +void HadifixConf::voiceCombo_activated(int /*index*/) +{ + d->setDefaultEncodingFromVoice(); +} + +void HadifixConf::testButton_clicked () { + // If currently synthesizing, stop it. + if (d->hadifixProc) + d->hadifixProc->stopText(); + else + { + d->hadifixProc = new HadifixProc(); + connect (d->hadifixProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + // Create a temp file name for the wave file. + KTempFile tempFile (locateLocal("tmp", "hadifixplugin-"), ".wav"); + QString tmpWaveFile = tempFile.file()->name(); + tempFile.close(); + + // Tell user to wait. + d->progressDlg = new KProgressDialog(d->configWidget, "ktts_hadifix_testdlg", + i18n("Testing"), + i18n("Testing."), + true); + d->progressDlg->progressBar()->hide(); + d->progressDlg->setAllowCancel(true); + + // Speak a German sentence as hadifix is a German tts + // TODO: Actually, Hadifix does support English (and other languages?) as well, + // If you install the right voice files. The hard part is finding and installing + // a working txt2pho for the desired language. There seem to be some primitive french, + // italian, and a few others, written in perl, but they have many issues. + // Go to the mbrola website and click on "TTS" to learn more. + + // QString testMsg = "K D E ist eine moderne grafische Arbeitsumgebung für UNIX-Computer."; + QString testMsg = testMessage(d->languageCode); + connect (d->hadifixProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + d->hadifixProc->synth (testMsg, + realFilePath(d->configWidget->hadifixURL->url()), + d->configWidget->isMaleVoice(), + realFilePath(d->configWidget->mbrolaURL->url()), + d->configWidget->getVoiceFilename(), + d->configWidget->volumeBox->value(), + d->configWidget->timeBox->value(), + d->configWidget->frequencyBox->value(), + PlugInProc::codecIndexToCodec(d->configWidget->characterCodingBox->currentItem(), d->codecList), + tmpWaveFile); + + // Display progress dialog modally. Processing continues when plugin signals synthFinished, + // or if user clicks Cancel button. + d->progressDlg->exec(); + disconnect (d->hadifixProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + if (d->progressDlg->wasCancelled()) d->hadifixProc->stopText(); + delete d->progressDlg; + d->progressDlg = 0; +} + +void HadifixConf::slotSynthFinished() +{ + // If user canceled, progress dialog is gone, so exit. + if (!d->progressDlg) + { + d->hadifixProc->ackFinished(); + return; + } + // Hide the Cancel button so user can't cancel in the middle of playback. + d->progressDlg->showCancelButton(false); + // Get new wavefile name. + d->waveFile = d->hadifixProc->getFilename(); + // Tell synth we're done. + d->hadifixProc->ackFinished(); + // Play the wave file (possibly adjusting its Speed). + // Player object deletes the wave file when done. + if (m_player) m_player->play(d->waveFile); + QFile::remove(d->waveFile); + d->waveFile = QString::null; + if (d->progressDlg) d->progressDlg->close(); +} + +void HadifixConf::slotSynthStopped() +{ + // Clean up after canceling test. + QString filename = d->hadifixProc->getFilename(); + // kdDebug() << "HadifixConf::slotSynthStopped: filename = " << filename << endl; + if (!filename.isNull()) QFile::remove(filename); +} diff --git a/kttsd/plugins/hadifix/hadifixconf.h b/kttsd/plugins/hadifix/hadifixconf.h new file mode 100644 index 0000000..a53ecb1 --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixconf.h @@ -0,0 +1,83 @@ +#ifndef _HADIFIXCONF_H_ +#define _HADIFIXCONF_H_ + +#include <qstringlist.h> + +#include <kconfig.h> + +#include <pluginconf.h> + +class HadifixProc; +class HadifixConfPrivate; + +class HadifixConf : public PlugInConf { + Q_OBJECT + + public: + /** Constructor */ + HadifixConf( QWidget* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~HadifixConf(); + + /** 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(); + + public slots: + void configChanged(bool t = true){emit changed(t);}; + + private slots: + virtual void voiceButton_clicked(); + virtual void testButton_clicked(); + virtual void voiceCombo_activated(int index); + void slotSynthFinished(); + void slotSynthStopped(); + + private: + HadifixConfPrivate *d; +}; +#endif diff --git a/kttsd/plugins/hadifix/hadifixconfigui.ui b/kttsd/plugins/hadifix/hadifixconfigui.ui new file mode 100644 index 0000000..302c1de --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixconfigui.ui @@ -0,0 +1,692 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>HadifixConfigUI</class> +<widget class="QWidget"> + <property name="name"> + <cstring>HadifixConfigUI</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>578</width> + <height>388</height> + </rect> + </property> + <property name="caption"> + <string>Hadifix Configuration</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the configuration dialog for the Hadifix (txt2pho and Mbrola) speech synthesizer.</string> + </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>GroupBox4</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Had&ifix Configuration</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the configuration dialog for the Hadifix (txt2pho and Mbrola) speech synthesizer.</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="QGroupBox" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>basicOptions</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>Box</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="title"> + <string>&Basic Options</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>voiceLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Voice file:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>voiceCombo</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Select a voice for speaking text. If no voices are listed, check your Mbrola configuration. You must install at least one voice.</string> + </property> + </widget> + <widget class="KComboBox" row="0" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>voiceCombo</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>Select a voice for speaking text. If no voices are listed, check your Mbrola configuration. You must install at least one voice.</string> + </property> + </widget> + <widget class="KPushButton" row="0" column="3"> + <property name="name"> + <cstring>voiceButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Select...</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>volumeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Volume &ratio:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>volumeBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the volume of speech. Slide to left for softer speech; to the right for louder.</string> + </property> + </widget> + <widget class="KIntSpinBox" row="1" column="1"> + <property name="name"> + <cstring>volumeBox</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string> %</string> + </property> + <property name="maxValue"> + <number>200</number> + </property> + <property name="minValue"> + <number>50</number> + </property> + <property name="value"> + <number>100</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the volume of speech. Slide to left for softer speech; to the right for louder.</string> + </property> + </widget> + <widget class="QSlider" row="1" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>volumeSlider</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="focusPolicy"> + <enum>NoFocus</enum> + </property> + <property name="minValue"> + <number>0</number> + </property> + <property name="maxValue"> + <number>1000</number> + </property> + <property name="lineStep"> + <number>10</number> + </property> + <property name="pageStep"> + <number>100</number> + </property> + <property name="value"> + <number>500</number> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="tickmarks"> + <enum>NoMarks</enum> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the volume of speech. Slide to left for softer speech; to the right for louder.</string> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>timeLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Speed:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>timeBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the speed of speech. Slide to left for slower speech; to the right for faster.</string> + </property> + </widget> + <widget class="KIntSpinBox" row="2" column="1"> + <property name="name"> + <cstring>timeBox</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string> %</string> + </property> + <property name="maxValue"> + <number>200</number> + </property> + <property name="minValue"> + <number>50</number> + </property> + <property name="value"> + <number>100</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the speed of speech. Slide to left for slower speech; to the right for faster.</string> + </property> + </widget> + <widget class="QSlider" row="2" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>timeSlider</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="focusPolicy"> + <enum>NoFocus</enum> + </property> + <property name="maxValue"> + <number>1000</number> + </property> + <property name="lineStep"> + <number>10</number> + </property> + <property name="pageStep"> + <number>100</number> + </property> + <property name="value"> + <number>500</number> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the speed of speech. Slide to left for slower speech; to the right for faster.</string> + </property> + </widget> + <widget class="QLabel" row="3" column="0"> + <property name="name"> + <cstring>frequencyLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Pitch:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>frequencyBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the pitch (tone) of speech. Slide to left for lower speech; to the right for higher.</string> + </property> + </widget> + <widget class="KIntSpinBox" row="3" column="1"> + <property name="name"> + <cstring>frequencyBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="suffix"> + <string> %</string> + </property> + <property name="maxValue"> + <number>200</number> + </property> + <property name="minValue"> + <number>50</number> + </property> + <property name="value"> + <number>100</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the pitch (tone) of speech. Slide to left for lower speech; to the right for higher.</string> + </property> + </widget> + <widget class="QSlider" row="3" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>frequencySlider</cstring> + </property> + <property name="focusPolicy"> + <enum>NoFocus</enum> + </property> + <property name="maxValue"> + <number>1000</number> + </property> + <property name="lineStep"> + <number>10</number> + </property> + <property name="pageStep"> + <number>100</number> + </property> + <property name="value"> + <number>500</number> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="whatsThis" stdset="0"> + <string>Adjusts the pitch (tone) of speech. Slide to left for lower speech; to the right for higher.</string> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>advancedOptions</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>&Advanced Options</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="QLabel" row="0" column="0"> + <property name="name"> + <cstring>hadifixBinLabel</cstring> + </property> + <property name="text"> + <string>txt2pho e&xecutable:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>hadifixURL</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>If the txt2pho program is in your PATH environment variable, simply enter "txt2pho", otherwise specify the full path to the txt2pho executable program.</string> + </property> + </widget> + <widget class="KURLRequester" row="0" column="1"> + <property name="name"> + <cstring>hadifixURL</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>If the txt2pho program is in your PATH environment variable, simply enter "txt2pho", otherwise specify the full path to the txt2pho executable program.</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>mbrolaBinLabel</cstring> + </property> + <property name="text"> + <string>&Mbrola executable:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>mbrolaURL</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>If the Mbrola program is in your PATH environment variable, simply enter "mbrola", otherwise specify the full path to the Mbrola executable program.</string> + </property> + </widget> + <widget class="KURLRequester" row="1" column="1"> + <property name="name"> + <cstring>mbrolaURL</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>If the Mbrola program is in your PATH environment variable, simply enter "mbrola", otherwise specify the full path to the Mbrola executable program.</string> + </property> + </widget> + <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="2"> + <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. For most western languages, use ISO-8859-1. For Hungarian, use ISO-8859-2.</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + <spacer row="2" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Preferred</enum> + </property> + <property name="sizeHint"> + <size> + <width>240</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KPushButton" row="2" column="1"> + <property name="name"> + <cstring>testButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Test</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to test the configuration. You should hear a spoken sentence.</string> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>volumeBox</sender> + <signal>valueChanged(int)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>volumeBox_valueChanged(int)</slot> + </connection> + <connection> + <sender>volumeSlider</sender> + <signal>valueChanged(int)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>volumeSlider_valueChanged(int)</slot> + </connection> + <connection> + <sender>timeBox</sender> + <signal>valueChanged(int)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>timeBox_valueChanged(int)</slot> + </connection> + <connection> + <sender>timeSlider</sender> + <signal>valueChanged(int)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>timeSlider_valueChanged(int)</slot> + </connection> + <connection> + <sender>frequencyBox</sender> + <signal>valueChanged(int)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>frequencyBox_valueChanged(int)</slot> + </connection> + <connection> + <sender>frequencySlider</sender> + <signal>valueChanged(int)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>frequencySlider_valueChanged(int)</slot> + </connection> + <connection> + <sender>voiceCombo</sender> + <signal>activated(const QString&)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>changed(const QString&)</slot> + </connection> + <connection> + <sender>volumeBox</sender> + <signal>valueChanged(const QString&)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>changed(const QString&)</slot> + </connection> + <connection> + <sender>timeBox</sender> + <signal>valueChanged(const QString&)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>changed(const QString&)</slot> + </connection> + <connection> + <sender>frequencyBox</sender> + <signal>valueChanged(const QString&)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>changed(const QString&)</slot> + </connection> + <connection> + <sender>hadifixURL</sender> + <signal>textChanged(const QString&)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>changed(const QString&)</slot> + </connection> + <connection> + <sender>mbrolaURL</sender> + <signal>textChanged(const QString&)</signal> + <receiver>HadifixConfigUI</receiver> + <slot>changed(const QString&)</slot> + </connection> +</connections> +<includes> + <include location="global" impldecl="in declaration">qradiobutton.h</include> + <include location="global" impldecl="in declaration">qpixmap.h</include> + <include location="global" impldecl="in declaration">kurl.h</include> + <include location="global" impldecl="in declaration">qmap.h</include> + <include location="global" impldecl="in implementation">kglobal.h</include> + <include location="global" impldecl="in implementation">qstringlist.h</include> + <include location="global" impldecl="in implementation">math.h</include> + <include location="global" impldecl="in implementation">kiconloader.h</include> +</includes> +<forwards> + <forward>class QStringList;</forward> +</forwards> +<variables> + <variable>QMap<QString,int> maleVoices;</variable> + <variable>QMap<int,QString> defaultVoices;</variable> + <variable>QPixmap female;</variable> + <variable>QPixmap male;</variable> + <variable>QMap<QString,int> femaleVoices;</variable> +</variables> +<signals> + <signal>changed(bool)</signal> +</signals> +<slots> + <slot access="protected" specifier="non virtual">volumeBox_valueChanged( int percentValue )</slot> + <slot access="protected" specifier="non virtual">timeBox_valueChanged( int percentValue )</slot> + <slot access="protected" specifier="non virtual">frequencyBox_valueChanged( int percentValue )</slot> + <slot access="protected" specifier="non virtual">volumeSlider_valueChanged( int sliderValue )</slot> + <slot access="protected" specifier="non virtual">timeSlider_valueChanged( int sliderValue )</slot> + <slot access="protected" specifier="non virtual">frequencySlider_valueChanged( int sliderValue )</slot> + <slot access="protected">changed( const QString & )</slot> +</slots> +<functions> + <function access="protected" specifier="non virtual" returnType="int">percentToSlider( int percentValue )</function> + <function access="protected" specifier="non virtual" returnType="int">sliderToPercent( int sliderValue )</function> + <function access="private" specifier="non virtual">init()</function> + <function specifier="non virtual">addVoice( const QString &filename, bool isMale )</function> + <function specifier="non virtual">addVoice( const QString &filename, bool isMale, const QString &displayname )</function> + <function specifier="non virtual">setVoice( const QString &filename, bool isMale )</function> + <function specifier="non virtual" returnType="QString">getVoiceFilename()</function> + <function specifier="non virtual" returnType="bool">isMaleVoice()</function> +</functions> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kcombobox.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/plugins/hadifix/hadifixconfigui.ui.h b/kttsd/plugins/hadifix/hadifixconfigui.ui.h new file mode 100644 index 0000000..e3a7a04 --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixconfigui.ui.h @@ -0,0 +1,114 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename slots use Qt Designer which will +** update this file, preserving your code. Create an init() slot in place of +** a constructor, and a destroy() slot in place of a destructor. +*****************************************************************************/ + +// Basically the slider values are logarithmic (0,...,1000) whereas percent +// values are linear (50%,...,200%). +// +// slider = alpha * (log(percent)-log(50)) +// with alpha = 1000/(log(200)-log(50)) + +int HadifixConfigUI::percentToSlider (int percentValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor (0.5 + alpha * (log(percentValue)-log(50))); +} + +int HadifixConfigUI::sliderToPercent (int sliderValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor(0.5 + exp (sliderValue/alpha + log(50))); +} + +void HadifixConfigUI::volumeBox_valueChanged (int percentValue) { + volumeSlider->setValue (percentToSlider (percentValue)); +} + +void HadifixConfigUI::timeBox_valueChanged (int percentValue) { + timeSlider->setValue (percentToSlider (percentValue)); +} + +void HadifixConfigUI::frequencyBox_valueChanged (int percentValue) { + frequencySlider->setValue (percentToSlider (percentValue)); +} + +void HadifixConfigUI::volumeSlider_valueChanged (int sliderValue) { + volumeBox->setValue (sliderToPercent (sliderValue)); +} + +void HadifixConfigUI::timeSlider_valueChanged (int sliderValue) { + timeBox->setValue (sliderToPercent (sliderValue)); +} + +void HadifixConfigUI::frequencySlider_valueChanged (int sliderValue) { + frequencyBox->setValue (sliderToPercent (sliderValue)); +} + +void HadifixConfigUI::init () { + male = KGlobal::iconLoader()->loadIcon("male", KIcon::Small); + female = KGlobal::iconLoader()->loadIcon("female", KIcon::Small); +} + +void HadifixConfigUI::addVoice (const QString &filename, bool isMale) { + if (isMale) { + if (!maleVoices.contains(filename)) { + int id = voiceCombo->count(); + maleVoices.insert (filename, id); + voiceCombo->insertItem (male, filename, id); + } + } + else { + if (!femaleVoices.contains(filename)) { + int id = voiceCombo->count(); + femaleVoices.insert (filename, id); + voiceCombo->insertItem (female, filename, id); + } + } +} + +void HadifixConfigUI::addVoice (const QString &filename, bool isMale, const QString &displayname) { + addVoice (filename, isMale); + + if (isMale) { + defaultVoices [maleVoices [filename]] = filename; + voiceCombo->changeItem (male, displayname, maleVoices [filename]); + } + else{ + defaultVoices [femaleVoices [filename]] = filename; + voiceCombo->changeItem (female, displayname, femaleVoices [filename]); + } +} + +void HadifixConfigUI::setVoice (const QString &filename, bool isMale) { + addVoice (filename, isMale); + if (isMale) + voiceCombo->setCurrentItem (maleVoices[filename]); + else + voiceCombo->setCurrentItem (femaleVoices[filename]); +} + +QString HadifixConfigUI::getVoiceFilename() { + int curr = voiceCombo->currentItem(); + + QString filename = voiceCombo->text(curr); + if (defaultVoices.contains(curr)) + filename = defaultVoices[curr]; + + return filename; +} + +bool HadifixConfigUI::isMaleVoice() { + int curr = voiceCombo->currentItem(); + QString filename = getVoiceFilename(); + + if (maleVoices.contains(filename)) + return maleVoices[filename] == curr; + else + return false; +} + +void HadifixConfigUI::changed (const QString &) { + emit changed (true); +} diff --git a/kttsd/plugins/hadifix/hadifixplugin.cpp b/kttsd/plugins/hadifix/hadifixplugin.cpp new file mode 100644 index 0000000..b52052c --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixplugin.cpp @@ -0,0 +1,23 @@ +/*************************************************************************** + begin : Mon Okt 14 2002 + copyright : (C) 2002 by Gunnar Schmi Dt + email : gunnar@schmi-dt.de + current mainainer: : 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. * + * * + ***************************************************************************/ + +#include <kgenericfactory.h> + +#include "hadifixconf.h" +#include "hadifixproc.h" + +typedef K_TYPELIST_2( HadifixProc, HadifixConf ) Hadifix; +K_EXPORT_COMPONENT_FACTORY( libkttsd_hadifixplugin, KGenericFactory<Hadifix>("kttsd_hadifix") ) diff --git a/kttsd/plugins/hadifix/hadifixproc.cpp b/kttsd/plugins/hadifix/hadifixproc.cpp new file mode 100644 index 0000000..42730b3 --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixproc.cpp @@ -0,0 +1,411 @@ +/*************************************************************************** + hadifixproc.cpp - description + ------------------- + begin : Mon Okt 14 2002 + copyright : (C) 2002 by Gunnar Schmi Dt + email : gunnar@schmi-dt.de + current mainainer: : 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. * + * * + ***************************************************************************/ + +#include <qstring.h> +#include <qstringlist.h> +#include <qtextcodec.h> + +#include <kdebug.h> +#include <kconfig.h> +#include <kprocess.h> +#include <kstandarddirs.h> + +#include "hadifixproc.h" +#include "hadifixproc.moc" + +class HadifixProcPrivate { + friend class HadifixProc; + private: + HadifixProcPrivate () { + hadifixProc = 0; + waitingStop = false; + state = psIdle; + synthFilename = QString::null; + gender = false; + volume = 100; + time = 100; + pitch = 100; + codec = 0; + }; + + ~HadifixProcPrivate() { + delete hadifixProc; + }; + + void load(KConfig *config, const QString &configGroup) { + config->setGroup(configGroup); + hadifix = config->readEntry ("hadifixExec", QString::null); + mbrola = config->readEntry ("mbrolaExec", QString::null); + voice = config->readEntry ("voice", QString::null); + gender = config->readBoolEntry("gender", false); + volume = config->readNumEntry ("volume", 100); + time = config->readNumEntry ("time", 100); + pitch = config->readNumEntry ("pitch", 100); + codec = PlugInProc::codecNameToCodec(config->readEntry ("codec", "Local")); + }; + + QString hadifix; + QString mbrola; + QString voice; + bool gender; + int volume; + int time; + int pitch; + + bool waitingStop; + KShellProcess* hadifixProc; + volatile pluginState state; + QTextCodec* codec; + QString synthFilename; +}; + +/** Constructor */ +HadifixProc::HadifixProc( QObject* parent, const char* name, const QStringList &) : + PlugInProc( parent, name ){ + // kdDebug() << "HadifixProc::HadifixProc: Running" << endl; + d = 0; +} + +/** Destructor */ +HadifixProc::~HadifixProc(){ + // kdDebug() << "HadifixProc::~HadifixProc: Running" << endl; + + if (d != 0) { + delete d; + d = 0; + } +} + +/** Initializate the speech */ +bool HadifixProc::init(KConfig *config, const QString &configGroup){ + // kdDebug() << "HadifixProc::init: Initializing plug in: Hadifix" << endl; + + if (d == 0) + d = new HadifixProcPrivate(); + d->load(config, configGroup); + 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 +* 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. +*/ +void HadifixProc::sayText(const QString& /*text*/) +{ + kdDebug() << "HadifixProc::sayText: Warning, sayText not implemented." << endl; + return; +} + +/** +* 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. +*/ +void HadifixProc::synthText(const QString &text, const QString &suggestedFilename) +{ + if (d == 0) return; // Caller should have called init. + synth(text, d->hadifix, d->gender, d->mbrola, d->voice, d->volume, + d->time, d->pitch, d->codec, suggestedFilename); +} + +/** +* Synthesize text using a specified configuration. +* @param text The text to synthesize. +* @param hadifix Command to run hadifix (txt2pho). +* @param isMale True to use male voice. +* @param mbrola Command to run mbrola. +* @param voice Voice file for mbrola to use. +* @param volume Volume percent. 100 = normal +* @param time Speed percent. 100 = normal +* @param pitch Frequency. 100 = normal +* @param waveFilename Name of file to synthesize to. +*/ +void HadifixProc::synth(QString text, + QString hadifix, bool isMale, + QString mbrola, QString voice, + int volume, int time, int pitch, + QTextCodec *codec, + const QString waveFilename) +{ + // kdDebug() << "HadifixProc::synth: Saying text: '" << text << "' using Hadifix plug in" << endl; + if (d == 0) + { + d = new HadifixProcPrivate(); + } + if (hadifix.isNull() || hadifix.isEmpty()) + return; + if (mbrola.isNull() || mbrola.isEmpty()) + return; + if (voice.isNull() || voice.isEmpty()) + return; + + // If process exists, delete it so we can create a new one. + // kdDebug() << "HadifixProc::synth: creating process" << endl; + if (d->hadifixProc) delete d->hadifixProc; + + // Create process. + d->hadifixProc = new KShellProcess; + + // Set up txt2pho and mbrola commands. + // kdDebug() << "HadifixProc::synth: setting up commands" << endl; + QString hadifixCommand = d->hadifixProc->quote(hadifix); + if (isMale) + hadifixCommand += " -m"; + else + hadifixCommand += " -f"; + + QString mbrolaCommand = d->hadifixProc->quote(mbrola); + mbrolaCommand += " -e"; //Ignore fatal errors on unkown diphone + mbrolaCommand += QString(" -v %1").arg(volume/100.0); // volume ratio + mbrolaCommand += QString(" -f %1").arg(pitch/100.0); // freqency ratio + mbrolaCommand += QString(" -t %1").arg(1/(time/100.0)); // time ratio + mbrolaCommand += " " + d->hadifixProc->quote(voice); + mbrolaCommand += " - " + d->hadifixProc->quote(waveFilename); + + // kdDebug() << "HadifixProc::synth: Hadifix command: " << hadifixCommand << endl; + // kdDebug() << "HadifixProc::synth: Mbrola command: " << mbrolaCommand << endl; + + QString command = hadifixCommand + "|" + mbrolaCommand; + *(d->hadifixProc) << command; + + // Connect signals from process. + connect(d->hadifixProc, SIGNAL(processExited(KProcess *)), + this, SLOT(slotProcessExited(KProcess *))); + connect(d->hadifixProc, SIGNAL(wroteStdin(KProcess *)), + this, SLOT(slotWroteStdin(KProcess *))); + + // Store off name of wave file to be generated. + d->synthFilename = waveFilename; + // Set state, busy synthing. + d->state = psSynthing; + if (!d->hadifixProc->start(KProcess::NotifyOnExit, KProcess::Stdin)) + { + kdDebug() << "HadifixProc::synth: start process failed." << endl; + d->state = psIdle; + } else { + QCString encodedText; + if (codec) { + encodedText = codec->fromUnicode(text); + // kdDebug() << "HadifixProc::synth: encoding using " << codec->name() << endl; + } else + encodedText = text.latin1(); // Should not happen, but just in case. + // Send the text to be synthesized to process. + d->hadifixProc->writeStdin(encodedText, encodedText.length()); + } +} + +/** +* 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. +*/ +QString HadifixProc::getFilename() { return d->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 HadifixProc::stopText(){ + // kdDebug() << "Running: HadifixProc::stopText()" << endl; + if (d->hadifixProc) + { + if (d->hadifixProc->isRunning()) + { + // kdDebug() << "HadifixProc::stopText: killing Hadifix shell." << endl; + d->waitingStop = true; + d->hadifixProc->kill(); + } else d->state = psIdle; + } else d->state = psIdle; + // d->state = psIdle; + // kdDebug() << "HadifixProc::stopText: Hadifix stopped." << endl; +} + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @see pluginState +*/ +pluginState HadifixProc::getState() { return d->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 HadifixProc::ackFinished() +{ + if (d->state == psFinished) + { + d->state = psIdle; + d->synthFilename = 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 HadifixProc::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. +* +* 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 . +*/ +bool HadifixProc::supportsSynth() { return true; } + + +void HadifixProc::slotProcessExited(KProcess*) +{ + // kdDebug() << "HadifixProc:hadifixProcExited: Hadifix process has exited." << endl; + pluginState prevState = d->state; + if (d->waitingStop) + { + d->waitingStop = false; + d->state = psIdle; + emit stopped(); + } else { + d->state = psFinished; + if (prevState == psSynthing) + emit synthFinished(); + } +} + +void HadifixProc::slotWroteStdin(KProcess*) +{ + // kdDebug() << "HadifixProc::slotWroteStdin: closing Stdin" << endl; + d->hadifixProc->closeStdin(); +} + + +/***************************************************************************/ + +/** +* Static function to determine whether the voice file is male or female. +* @param mbrola the mbrola executable +* @param voice the voice file +* @param output the output of mbrola will be written into this QString* +* @return HadifixSpeech::MaleGender if the voice is male, +* HadifixSpeech::FemaleGender if the voice is female, +* HadifixSpeech::NoGender if the gender cannot be determined, +* HadifixSpeech::NoVoice if there is an error in the voice file +*/ +HadifixProc::VoiceGender HadifixProc::determineGender(QString mbrola, QString voice, QString *output) +{ + QString command = mbrola + " -i " + voice + " - -"; + + // create a new process + HadifixProc speech; + KShellProcess proc; + proc << command; + connect(&proc, SIGNAL(receivedStdout(KProcess *, char *, int)), + &speech, SLOT(receivedStdout(KProcess *, char *, int))); + connect(&proc, SIGNAL(receivedStderr(KProcess *, char *, int)), + &speech, SLOT(receivedStderr(KProcess *, char *, int))); + + speech.stdOut = QString::null; + speech.stdErr = QString::null; + proc.start (KProcess::Block, KProcess::AllOutput); + + VoiceGender result; + if (!speech.stdErr.isNull() && !speech.stdErr.isEmpty()) { + if (output != 0) + *output = speech.stdErr; + result = NoVoice; + } + else { + if (output != 0) + *output = speech.stdOut; + if (speech.stdOut.contains("female", false)) + result = FemaleGender; + else if (speech.stdOut.contains("male", false)) + result = MaleGender; + else + result = NoGender; + } + + return result; +} + +void HadifixProc::receivedStdout (KProcess *, char *buffer, int buflen) { + stdOut += QString::fromLatin1(buffer, buflen); +} + +void HadifixProc::receivedStderr (KProcess *, char *buffer, int buflen) { + stdErr += QString::fromLatin1(buffer, buflen); +} + +/** + * 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 HadifixProc::getSsmlXsltFilename() +{ + return KGlobal::dirs()->resourceDirs("data").last() + "kttsd/hadifix/xslt/SSMLtoTxt2pho.xsl"; +} diff --git a/kttsd/plugins/hadifix/hadifixproc.h b/kttsd/plugins/hadifix/hadifixproc.h new file mode 100644 index 0000000..225c7ed --- /dev/null +++ b/kttsd/plugins/hadifix/hadifixproc.h @@ -0,0 +1,203 @@ +/*************************************************************************** + hadifixproc.h - description + ------------------- + begin : Mon Okt 14 2002 + copyright : (C) 2002 by Gunnar Schmi Dt + email : gunnar@schmi-dt.de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 _HADIFIXPROC_H_ +#define _HADIFIXPROC_H_ + +#include <qstringlist.h> + +#include <pluginproc.h> + +class KProcess; + +class HadifixProcPrivate; +class HadifixProc : public PlugInProc{ + Q_OBJECT + + public: + enum VoiceGender { + MaleGender = 2, + FemaleGender = 1, + NoGender = 0, + NoVoice = -1 + }; + + /** Constructor */ + HadifixProc( QObject* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~HadifixProc(); + + /** Initializate the speech */ + 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(); + + /** + * Synthesize text using a specified configuration. + * @param text The text to synthesize. + * @param hadifix Command to run hadifix (txt2pho). + * @param isMale True to use male voice. + * @param mbrola Command to run mbrola. + * @param voice Voice file for mbrola to use. + * @param volume Volume percent. 100 = normal + * @param time Speed percent. 100 = normal + * @param pitch Frequency. 100 = normal + * @param waveFilename Name of file to synthesize to. + */ + void synth(QString text, + QString hadifix, bool isMale, + QString mbrola, QString voice, + int volume, int time, int pitch, + QTextCodec* codec, + const QString waveFilename); + + /** + * Static function to determine whether the voice file is male or female. + * @param mbrola the mbrola executable + * @param voice the voice file + * @param output the output of mbrola will be written into this QString* + * @return HadifixSpeech::MaleGender if the voice is male, + * HadifixSpeech::FemaleGender if the voice is female, + * HadifixSpeech::NoGender if the gender cannot be determined, + * HadifixSpeech::NoVoice if there is an error in the voice file + */ + static VoiceGender determineGender(QString mbrola, QString voice, QString *output = 0); + + /** + * 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(); + + private slots: + void slotProcessExited(KProcess*); + void slotWroteStdin(KProcess*); + + void receivedStdout (KProcess *, char *buffer, int buflen); + void receivedStderr (KProcess *, char *buffer, int buflen); + + private: + HadifixProcPrivate *d; + + QString stdOut; + QString stdErr; +}; + +#endif diff --git a/kttsd/plugins/hadifix/initialconfig.h b/kttsd/plugins/hadifix/initialconfig.h new file mode 100644 index 0000000..4fac632 --- /dev/null +++ b/kttsd/plugins/hadifix/initialconfig.h @@ -0,0 +1,164 @@ + +/** + * Tries to find hadifix and mbrola by looking onto the hard disk. This is + * neccessary because both hadifix and mbrola do not have standard + * installation directories. + */ +void findInitialConfig() { + QString hadifixDataPath = findHadifixDataPath(); + + defaultHadifixExec = findExecutable("txt2pho", hadifixDataPath+"/../"); + + QStringList list; list += "mbrola"; list += "mbrola-linux-i386"; + defaultMbrolaExec = findExecutable(list, hadifixDataPath+"/../../mbrola/"); + + defaultVoices = findVoices (defaultMbrolaExec, hadifixDataPath); +} + +/** Tries to find the hadifix data path by looking into a number of files. */ +QString findHadifixDataPath () { + QStringList files; + files += "/etc/txt2pho"; + files += QDir::homeDirPath()+"/.txt2phorc"; + + QStringList::iterator it; + for (it = files.begin(); it != files.end(); ++it) { + + QFile file(*it); + if ( file.open(IO_ReadOnly) ) { + QTextStream stream(&file); + + while (!stream.atEnd()) { + QString s = stream.readLine().stripWhiteSpace(); + // look for a line "DATAPATH=..." + + if (s.startsWith("DATAPATH")) { + s = s.mid(8, s.length()-8).stripWhiteSpace(); + if (s.startsWith("=")) { + s = s.mid(1, s.length()-1).stripWhiteSpace(); + if (s.startsWith("/")) + return s; + else { + QFileInfo info (QFileInfo(*it).dirPath() + "/" + s); + return info.absFilePath(); + } + } + } + } + file.close(); + } + } + return "/usr/local/txt2pho/"; +} + +/** Tries to find the an executable by looking onto the hard disk. */ +QString findExecutable (const QStringList &names, const QString &possiblePath) { + // a) Try to find it directly + QStringList::ConstIterator it; + QStringList::ConstIterator itEnd = names.constEnd(); + for (it = names.constBegin(); it != itEnd; ++it) { + QString executable = KStandardDirs::findExe (*it); + if (!executable.isNull() && !executable.isEmpty()) + return executable; + } + + // b) Try to find it in the path specified by the second parameter + for (it = names.constBegin(); it != itEnd; ++it) { + QFileInfo info (possiblePath+*it); + if (info.exists() && info.isExecutable() && info.isFile()) { + return info.absFilePath(); + } + } + + // Both tries failed, so the user has to locate the executable. + return QString::null; +} + +/** Tries to find the voice files by looking onto the hard disk. */ +QStringList findVoices(QString mbrolaExec, const QString &hadifixDataPath) { + + // First of all: + // dereference links to the mbrola executable (if mbrolaExec is a link). + for (int i = 0; i < 10; ++i) { + // If we have a chain of more than ten links something is surely wrong. + QFileInfo info (mbrolaExec); + if (info.exists() && info.isSymLink()) + mbrolaExec = info.readLink(); + } + + // Second: + // create a list of directories that possibly contain voice files + QStringList list; + + // 2a) search near the mbrola executable + QFileInfo info (mbrolaExec); + if (info.exists() && info.isFile() && info.isExecutable()) { + QString mbrolaPath = info.dirPath (true); + list += mbrolaPath; + } + + // 2b) search near the hadifix data path + info.setFile(hadifixDataPath + "../../mbrola"); + QString mbrolaPath = info.dirPath (true) + "/mbrola"; + if (!list.contains(mbrolaPath)) + list += mbrolaPath; + + // 2c) broaden the search by adding subdirs (with a depth of 2) + QStringList subDirs = findSubdirs (list); + QStringList subSubDirs = findSubdirs (subDirs); + list += subDirs; + list += subSubDirs; + + // Third: + // look into each of these directories and search for voice files. + QStringList result; + QStringList::iterator it; + for (it = list.begin(); it != list.end(); ++it) { + QDir baseDir (*it, QString::null, + QDir::Name|QDir::IgnoreCase, QDir::Files); + QStringList files = baseDir.entryList(); + + QStringList::iterator iter; + for (iter = files.begin(); iter != files.end(); ++iter) { + // Voice files start with "MBROLA", but are afterwards binary files + QString filename = *it + "/" + *iter; + QFile file (filename); + if (file.open(IO_ReadOnly)) { + QTextStream stream(&file); + if (!stream.atEnd()) { + QString s = stream.readLine(); + if (s.startsWith("MBROLA")) + if (HadifixProc::determineGender(mbrolaExec, filename) + != HadifixProc::NoVoice + ) + result += filename; + file.close(); + } + } + } + } + return result; +} + +/** Returns a list of subdirs (with absolute paths) */ +QStringList findSubdirs (const QStringList &baseDirs) { + QStringList result; + + QStringList::ConstIterator it; + QStringList::ConstIterator itEnd = baseDirs.constEnd(); + for (it = baseDirs.constBegin(); it != itEnd; ++it) { + // a) get a list of directory names + QDir baseDir (*it, QString::null, + QDir::Name|QDir::IgnoreCase, QDir::Dirs); + QStringList list = baseDir.entryList(); + + // b) produce absolute paths + QStringList::ConstIterator iter; + QStringList::ConstIterator iterEnd = list.constEnd(); + for (iter = list.constBegin(); iter != iterEnd; ++iter) { + if ((*iter != ".") && (*iter != "..")) + result += *it + "/" + *iter; + } + } + return result; +} diff --git a/kttsd/plugins/hadifix/kttsd_hadifixplugin.desktop b/kttsd/plugins/hadifix/kttsd_hadifixplugin.desktop new file mode 100644 index 0000000..2db3e1c --- /dev/null +++ b/kttsd/plugins/hadifix/kttsd_hadifixplugin.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Name=Hadifix +Name[ne]=ह्याडिफिक्स +Comment=German hadifix text-to-speech system +Comment[bg]=Синтезатор на глас за немски език Hadifix +Comment[ca]=Sistema de text a veu alemany hadifix +Comment[cs]=Německý systém hlasové syntézy hadifix +Comment[da]=Tysk hadifix tekst-til-tale system +Comment[de]=Deutsches hadifix-Sprachausgabesystem +Comment[el]=Σύστημα κειμένου-σε-ομιλία γερμανικού hadifix +Comment[es]=Sintetizador de texto a voz en alemán hadifix +Comment[et]=Saksa teksti kõneks muutmise süsteem Hadifix +Comment[eu]=Alemanierazko hadifix testutik hizketarako sistema +Comment[fa]=سیستم متن به گفتار hadifix آلمانی +Comment[fi]=Saksalainen hadifix teksti puheeksi -systeemi +Comment[fr]=Système allemand de synthèse vocale hadifix +Comment[ga]=Córas téacs-go-caint Gearmáinise hadifix +Comment[gl]=Sistema alemao de texto-para-fala hadifix +Comment[hu]=Hadifix szövegfelolvasó (Mbrola-alapú) +Comment[is]=Þýska hadifix texti-í-tal kerfið +Comment[it]=Sistema di pronuncia tedesca Hadifix +Comment[ja]=ドイツ語 hadifix テキスト読み上げシステム +Comment[ka]=გერმანული hadifix ტექსტის გახმოვანების სისტემა +Comment[km]=ប្រព័ន្ធអត្ថបទដែលត្រូវនិយាយ hadifix អាល្លឺម៉ង់ +Comment[mk]=Германски hadifix систем за текст-во-говор +Comment[ms]=Sistem teks-ke-tutur hadifiks Jerman +Comment[mt]=Sistema test-għal-vuċi hadifix Ġermaniż +Comment[nb]=Tysk hadifix system for tekst-til-tale +Comment[nds]=Düütsch Blicksnuut Hadifix +Comment[ne]=जर्मनी ह्याडिफिक्स पाठ वाचक प्रणाली +Comment[nl]=Duits hadifix tekst-tot-spraak-systeem +Comment[pa]=ਜਰਮਨ hadifix ਪਾਠ ਤੋਂ ਬੋਲੀ ਸਿਸਟਮ +Comment[pl]=Niemiecki system syntezy mowy hadifix +Comment[pt]=Sistema alemão de texto-para-voz hadifix +Comment[pt_BR]=Sistema de conversão de texto para áudio almeão hadifix +Comment[ru]=Немецкая система синтеза речи Hadifix +Comment[sk]=Nemecký systém text-na-reč hadifix +Comment[sl]=Nemški sistem besedila v govor hadifix +Comment[sr]=Hadifix, немачки систем за текст-у-говор +Comment[sr@Latn]=Hadifix, nemački sistem za tekst-u-govor +Comment[sv]=Tyska Hadifix text-till-tal system +Comment[ta]=ஜெர்மன் ஹாடிஃபிக்ஸ் உரையில் இருந்து பேச்சு அமைப்பு +Comment[tg]=Системаи олмонии таҳлили овози hadifix +Comment[tr]=Alman hadifix metinden konuşmaya sistemi +Comment[uk]=Німецька система синтезу мовлення hadifix +Comment[vi]=Hệ thống tổng hợp tiếng nói Đức hadifix +Comment[zh_TW]=德語 hadifix 文字轉語音系統 +Type=Service +ServiceTypes=KTTSD/SynthPlugin +X-KDE-Library=libkttsd_hadifixplugin +X-KDE-Languages=de,hu diff --git a/kttsd/plugins/hadifix/voicefileui.ui b/kttsd/plugins/hadifix/voicefileui.ui new file mode 100644 index 0000000..6c038f7 --- /dev/null +++ b/kttsd/plugins/hadifix/voicefileui.ui @@ -0,0 +1,119 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>VoiceFileWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>VoiceFileWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>452</width> + <height>117</height> + </rect> + </property> + <property name="caption"> + <string>Selecting Voice File</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="QLabel" row="0" column="0"> + <property name="name"> + <cstring>voiceFileLabel</cstring> + </property> + <property name="text"> + <string>Path of the voice file:</string> + </property> + </widget> + <widget class="KURLRequester" row="0" column="1"> + <property name="name"> + <cstring>voiceFileURL</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + </widget> + <widget class="QButtonGroup" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>genderOption</cstring> + </property> + <property name="title"> + <string>Gender</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="QRadioButton" row="0" column="0"> + <property name="name"> + <cstring>femaleOption</cstring> + </property> + <property name="text"> + <string>Female</string> + </property> + </widget> + <widget class="QRadioButton" row="0" column="1"> + <property name="name"> + <cstring>maleOption</cstring> + </property> + <property name="text"> + <string>Male</string> + </property> + </widget> + <widget class="KPushButton" row="0" column="2"> + <property name="name"> + <cstring>genderButton</cstring> + </property> + <property name="text"> + <string>Try to Determine From Voice File</string> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<connections> + <connection> + <sender>genderButton</sender> + <signal>clicked()</signal> + <receiver>VoiceFileWidget</receiver> + <slot>genderButton_clicked()</slot> + </connection> +</connections> +<includes> + <include location="global" impldecl="in implementation">kurlrequesterdlg.h</include> + <include location="global" impldecl="in implementation">kmessagebox.h</include> + <include location="local" impldecl="in implementation">hadifixproc.h</include> + <include location="local" impldecl="in implementation">voicefileui.ui.h</include> +</includes> +<variables> + <variable access="public">QString mbrola;</variable> +</variables> +<slots> + <slot>genderButton_clicked()</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/plugins/hadifix/voicefileui.ui.h b/kttsd/plugins/hadifix/voicefileui.ui.h new file mode 100644 index 0000000..9d37375 --- /dev/null +++ b/kttsd/plugins/hadifix/voicefileui.ui.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void VoiceFileWidget::genderButton_clicked() +{ + HadifixProc::VoiceGender gender; + QString details; + gender = HadifixProc::determineGender(mbrola, voiceFileURL->url(), &details); + + if (gender == HadifixProc::MaleGender) { + maleOption->setChecked (true); + femaleOption->setChecked (false); + } + else if (gender == HadifixProc::FemaleGender) { + maleOption->setChecked (false); + femaleOption->setChecked (true); + } + else if (gender == HadifixProc::NoGender) { + KMessageBox::sorry (this, + i18n("The gender of the voice file %1 could not be detected.").arg(voiceFileURL->url()), + i18n("Trying to Determine the Gender - Hadifix Plug In")); + } + else { + KMessageBox::detailedSorry (this, + i18n("The file %1 does not seem to be a voice file.").arg(voiceFileURL->url()), + details, i18n("Trying to Determine the Gender - Hadifix Plug In")); + } +} |