diff options
Diffstat (limited to 'kttsd')
289 files changed, 51807 insertions, 0 deletions
diff --git a/kttsd/AUTHORS b/kttsd/AUTHORS new file mode 100644 index 0000000..33193d5 --- /dev/null +++ b/kttsd/AUTHORS @@ -0,0 +1,5 @@ +José Pablo Ezequiel Fernández <pupeno@pupeno.com>
+Gary Cramblitt <garycramblitt@comcast.net>
+Gunnar Schmi Dt <gunnar@schmi-dt.de>
+Olaf Schmidt <ojschmidt@kde.org>
+Paul Giannaros <ceruleanblaze@gmail.com>
diff --git a/kttsd/ChangeLog b/kttsd/ChangeLog new file mode 100644 index 0000000..549f975 --- /dev/null +++ b/kttsd/ChangeLog @@ -0,0 +1,609 @@ +2006-11-02 Gary Cramblitt (PhantomsDad) + * Add support for new Vietnamese voices to festivalintvoices file. Get them at http://sourceforge.net/projects/vietnamesevoice. + +2006-02-09 Gary Cramblitt (PhantomsDad) + * Use KDE_CHECK_{HEADER,LIB} instead of AC_CHECK_{HEADER,LIB} to locate alsalib. + +2006-02-07 Stephan Johach + * Fix translation of "Speak Text" in kate/ktextedit. + +2006-02-06 Gary Cramblitt (PhantomsDad) + * Bump version to 0.3.5.2. + * Fix BUG:121427 Translations sometimes not working due to incorrect catalog + insertion/removal. + +2006-01-20 ======== Release v0.3.5.1 with KDE 3.5.1 + +2006-01-13 Gary Cramblitt (PhantomsDad) + * Fix BUG:120083 buffer length problem in Epos and Command plugin. + * Fix BUG:115795 Don't try to start the Epos server for every utterance. + * Epos server command changed from epos to eposd. Epos client command changed from + say to say-epos. These changes appeared around Epos v2.5.35. Enhance to try for these + automatically. + +2006-01-10 Gary Cramblitt (PhantomsDad) + * Bug fix. Pausing when popping up status in systray. + * Plug small memory leak. + * Allow user to specify custom ALSA device name. + +2006-01-09 Gary Cramblitt (PhantomsDad) + * Fix BUG:119753 Freezing when removing a speaking text job speaking on AlsaPlayer. + AlsaPlayer now uses non-blocking pcm open and polling. + * The ALSA device list pickable by user is now restricted to "default" and available + "plughw" devices. + * The following parameters may be placed in the [ALSAPlayer] section of + ~/.kde/share/config/kttsdrc: + PcmName=default (Any valid alsa pcm device, but your on your own. Examples: + dmix, hw:1,0) + DebugLevel=1 (0 = errors only; 1 = messages; 2 = verbose debug) + PeriodSize=128 (Number of frames in the ALSA buffer between interrupts. + If you have trouble, try adjusting up or down.) + Periods=8 (Same) + * If the ALSA pcm does not support pausing, simulates by not writing to the + pcm anymore, which will cause a slight delay, and underruns, + but that's the best we can do, I suppose. + * Fix bug. When starting KTTSMgr, it loaded the aRts plugin to determine if aRts is + available. But this started the aRts server and if user's machine does not have + proper audio sharing enabled, caused "device busy" errors. Now it starts the aRts + server only when playback begins the first time. + * Bump version from 0.3.5 to 0.3.5.1. + +2006-01-06 Gary Cramblitt (PhantomsDad) + * Fix BUG:118016 Not loading plugins when desktop language is not ISO-8859-1. + +2005-12-03 Jürgen Zdero + * Add German chat filter by Jürgen Zdero. + +2005-11-13 Gary Cramblitt (PhantomsDad) + * BUG:116031 Fix appendText method. Add version method to dcop interface. + This change did not make it into the KDE 3.5 release. + +2005-11-29 ======== Release v0.3.5 with KDE 3.5 + +2005-10-16 J. Riddell + * Make install error. Install the Hadifix icons. + +2005-07-19 Gary Cramblitt (PhantomsDad) + * Name kept .wav files in format kttsd-jjjjjjjj-ssssssss.wav where jjjjjjjj is the job + number zero-filled to the left and ssssssss is the sentence sequence number zero-filled + to the left. + +2005-07-01 Gary Cramblitt (PhantomsDad) + * Since ALSA plugin seems to work pretty well, enable building --without-arts and build + ALSA plugin by default (if headers found). + +2005-06-25 Gary Cramblitt (PhantomsDad) + * The XML Transformer filter now applies itself when the root element OR doctype and appId match. + +2005-06-12 Gary Cramblitt (PhantomsDad) + * New aKode audio plugin. Build it with ./configure --enable-kttsd-akode. + There are a couple of problems. See README players/akodeplayer/README. + * Activate rich speak (XHTML to SSML) in Konqueror plugin. To speak richly, + you must configure the xhtml2ssml_simple XML Transformer plugin. + +2005-06-03 Gary Cramblitt (PhantomsDad) + * New ALSA audio plugin. Build it with ./configure --enable-kttsd-alsa for now. + * New Qt Rich Text to Plain Text filter by David Powell. + +2005-04-30 Gary Cramblitt (PhantomsDad) + * Change Current Sentence widget in Jobs tab to KTextEdit to avoid resizing when text does + not fit in box. + * HTML to SSML now working pretty well. Note that you must + 1. Have Festival 1.95 installed. + 2. Have rab_diphone voice installed. + 3. Have xsltproc installed. + 4. If you have Festival Talker configured, reconfigure it (Edit) so that it will detect + if you have rab_diphone voice installed. You must hit the Apply button. + 5. Enable XML Transformer filter and point it to xhtml2ssml_simple.xsl stylesheet. + 6. Right now, only speaks HTML from Konqi if you paste HTML from clipboard. + Speak button in Konqi not yet enabled until I get some confidence it is working well. + +2005-04-23 Gary Cramblitt (PhantomsDad) + * Break sentences longer than 600 characters into shorter sentences by changing comma to + period. This prevents Festival from droning on lower and lower in tone until it is + unintelligible or crashes. + +2005-04-22 Gary Cramblitt (PhantomsDad) + * If resumeText is called on a speaking job, do not restart it. + +2005-04-21 Gary Cramblitt (PhantomsDad) + * New Hungarian Hadifix (mbrola and txt2pho) voice available at + http://tkltrans.sourceforge.net/ + Since Hungarian requires ISO 8859-2 character encoding, added encoding option to + Hadifix plugin. Also, Hadifix plugin auto-selects a voice matching the user's + chosen language. + * Abbreviate USA and UK country names to save space. + * Display male/female icons in Festival Interactive configuration dialog. + * Use new Select Talker dialog in Talker Chooser filter. + +2005-04-17 Gary Cramblitt (PhantomsDad) + * Actions to be taken on notifications may now be specified at three levels + Specific event of specific application + All other events of specific application + All other events + * When the last event of an application is deleted, delete the app from + Notifications list. + +2005-04-12 Gary Cramblitt (PhantomsDad) + * Enhanced notification options. Ability to customize each application event. + * Enhanced Talker chooser for notifications and Job Manager. + * Fix bug in Talker matching algorithm. + +2005-03-18 Gary Cramblitt (PhantomsDad) + * Added --systray cmdline option to KTTSMgr, which causes it to start minimized in system + tray. + * Added --autoexit cmdline option to KTTSMgr, which causes it to exit when no longer + speaking any text jobs. + * Added AutoStartManager and AutoExitManager options to config. These cause kttsd + to automatically start KTTSMgr when a text job starts, and automatically exit + KTTSMgr when all text jobs have finished speaking. + +2005-03-16 Gary Cramblitt (PhantomsDad) + * Bug fix. Interruption messages are spoken using the Talker of the interrupting message. + So if default Talker is en, and you do sayMessage "Guten Tag" "de", the "Text Interrupted. + Message" is spoken using German Talker. Interruption messages should speak using the + default Talker. + +2005-03-13 Gary Cramblitt (PhantomsDad) + * Bug. SBD Filter turned off by default. Not good! + * When running kttsd and no Talkers or Filters have been configured, automatically + attempt to configure them. Try to configure a Talker in the user's desktop language + and if that doesn't work, in English. Automatically configure the Standard + Sentence Boundary Detector. + +2005-03-12 Gary Cramblitt (PhantomsDad) + * Change toolbar buttons on Jobs tab to push buttons so they can be used from keyboard. + Clean up screen layout and add WhatsThis help. + * Added sayText convenience method to kspeech.h, which combines setText and startText + into a single call. Be sure to update kdelibs/interfaces/kspeech before attempting to + compile kttsd: + cd kdelibs/interfaces/kspeech + cvs up + make install + * When starting or restoring KTTSMgr, defaults to Jobs tab if visible. + * When clicking on Jobs tab, warn user if there are config changes not yet saved. + +2005-03-09 Gary Cramblitt (PhantomsDad) + * Add Hold and Resume items to systray menu. + +2005-03-06 Gary Cramblitt (PhantomsDad) + * Consolidated all the translation catalogues into single catalog called "kttsd". + +2005-03-05 Gary Cramblitt (PhantomsDad) + * Add support for Kiswahili, Zulu, and Ibibio Festival languages. Get them at + http://www.llsti.org/index.htm. Note that Ibibio does not have an assigned two-letter + (iso 639) code, so I gave it Zulu (zu), which is probably as bad as saying that English + is French, but it cannot be helped. The Ibibio and Zulu voices will only speak valid + Ibibio or Zulu text, so the Test button is non-functional for them until translators + provide translations. + +2005-03-02 Gary Cramblitt (PhantomsDad) + * BUG:100600 Fix for messages and warnings not spoken when text job is paused. + * Bug. Talker Chooser claims it can autoconfigure itself, but it cannot really. + * Allow to remove any Filter. + +2005-02-17 Gary Cramblitt (PhantomsDad) + * Remove festival/ and festivalcs/ plugins from source tree. AFAIK, these plugins don't work + and nobody is using them. And these are confusing packagers who think we have a build + dependency on Festival and Speech Tools libraries. Use festivalint/. + +2005-02-15 Gary Cramblitt (PhantomsDad) + * Translations now working. + * Include a copy of kdelibs/interfaces/kspeech in compat/ dir. This permits distribution + and compilation of KTTS for KDE less than 3.4. + * Support for Italian voices. Download from + http://www.csrf.pd.cnr.it/TTS/It-FESTIVAL-download.htm. + or http://mirko.lilik.it/Italian-FESTIVAL.zip + +2005-02-05 Gary Cramblitt (PhantomsDad) + * Version bumped to 0.3.0. + * Russian Festival voice added to voices file. Download voice from + http://nshmyrev.narod.ru/festival/festival.html. Must have Festival 1.95 beta or later. + Untar to festival/lib/voices/russian/ directory. Encoding must be one of the + single-byte cryllics, such as KOI8-R. + * Ability to load/save Talker Chooser and XML Transformer configurations. + * Some bug fixes related to Filters. + +2005-01-24 Gary Cramblitt (PhantomsDad) + * Add Talker Chooser Filter. + * Add option on Audio tab to preserve generated audio files by copying to user-specified + directory. + +2005-01-22 Gary Cramblitt (PhantomsDad) + * Allow filtering of notifications. (only setText and appendText are currently filtered.) + * Fix bug. Synchronous filters not being run. + +2005-01-21 Gary Cramblitt (PhantomsDad) + * Sentence Boundary Detector plugin now active. This means that SSML is parsed into + sentences and therefore can be rewound/advanced by sentence. It also means that + KTTS no longer crashes (taking KDE and X11 with it) on large HTML/SSML files. + * Filters can be applied based on the DCOP Application ID of app that queued the text + job. + * Filters can be applied based on language of Talker. + * StringReplacer filter can now load and save word lists to/from external file. + * Several other bug fixes. + +2005-01-13 Gary Cramblitt (PhantomsDad) + * FilterMgr class now implemented in kttsd, which means that filters now work. Well + almost. See TODO file for some nasty problems. (Hint: avoid large HTML/SSML pages!) + * Moved the TalkerCode matching and Talker plugins to TalkerMgr class so both + Speaker and SpeechData classes can access them. + +2005-01-09 Gary Cramblitt (PhantomsDad) + * Epos plugin defaults to ISO 8859-2 encoding. Pass encoded text to Epos client + via Stdin rather than command line to avoid encoding problem. + * Added encoding parameter to kspeech::setFile method. This allows for speaking a + file that is encoded different from global desktop default. + +2005-01-07 Gary Cramblitt (PhantomsDad) + * Start to Filters capability. StringReplacer Filter implemented in KTTSMgr GUI, + but not yet implemented in kttsd. + +2004-12-31 Gary Cramblitt (PhantomsDad) + * In KTTSD, if user has not yet configured any Talkers, prompt user and if they click + Yes, run KTTSMgr. User has option to turn off prompt. + * Activate kspeech::showDialog() method. + * The khmtlktts plugin automatically senses if KTTSD is installed and if not, + hides menu item. + +2004-12-30 Gary Cramblitt (PhantomsDad) + * Set LANG and LC_CTYPE environment variables before starting subprocess in Epos and + and Command plugins. Fix encoding bug in Epos plugin. Move common encoding routines + to PluginProc. + +2004-12-27 Gary Cramblitt (PhantomsDad) + * Add encoding option to Festival Interactive Plugin. When choosing a voice, default the + encoding option based on voices file. + * Add support for Polish Festival. + +2004-12-21 Gary Cramblitt (PhantomsDad) + * Require GStreamer 0.8.5 or above in configure.in.in. SuSE 9.2 users have + GStreamer 0.8.0, which causes KTTSMgr to immediately crash on startup. + +2004-12-20 Gary Cramblitt (PhantomsDad) + * kdenonbeta/kttsd moved to kdeaccessibility/kttsd. + * kdenonbeta/kttsd/libktts moved to kdelibs/interfaces/kspeech. libktts dropped. + * kdenonbeta/app-plugins/khtml-plugin moved to kdebase/konqueror/kttsplugin. + * kdenonbeta/app-plugins/kate-plugin moved to kdebase/kate/plugins/ktts. + * kdenonbeta/app-plugins/katepart-plugin moved to kdebase/kate/plugin/katepartktts. + +2004-12-19 Gary Cramblitt (PhantomsDad) + * Change Festival voices from ini to xml format. Permit translators to translate voice names. + * Add "--enable-kttsd-gstreamer" option to configure, defaulting to no. + Warn packagers not to distribute gstreamer in binary packages of kdeaccessibility as it + creates unwanted dependencies. + +2004-12-18 Gary Cramblitt (PhantomsDad) + * Remove kcmkttsmgr. Use kcmkttsd instead. + +2004-12-14 Gary Cramblitt (PhantomsDad) + * Under KDE 3.2, FestivalInt plugin automatically finding /usr/share/festival, rather + than /usr/bin/festival. + +2004-12-12 Release 0.2.0. First public release. + +2004-12-09 Gary Cramblitt (PhantomsDad) + * Require GStreamer >= 0.8.7. + * Bug. Always using default GStreamer sink in KTTSD. Honor user's choice now. + +2004-12-07 Gary Cramblitt (PhantomsDad) + * Add Festival Czech voice. + * Apply some Juk patches to GStreamerPlayer. + +2004-12-06 Gary Cramblitt (PhantomsDad) + * Initial, but primitive support for SSML with Hadifix. + +2004-12-03 Gary Cramblitt (PhantomsDad) + * Audio players are now plugins. This permits distribution of binary packages + without GStreamer being a mandatory dependency. + +2004-12-02 Gary Cramblitt (PhantomsDad) + * Bypass stretching (overall Speed setting) when utterance contains SSML. sox + mangles SSML pitch settings. + +2004-12-01 Gary Cramblitt (PhantomsDad) + * Some code reorganization. New TalkerCode object in libkttsd. + * Better Talker selection dialog when changing Talker in Jobs tab. + +2004-11-29 Gary Cramblitt (PhantomsDad) + * When clicking Test button, now honors users output method (arts or gstreamer) and + overall Audio Speed setting. + * Add option for GStreamer output sink. + +2004-11-27 Gary Cramblitt (PhantomsDad) + * Extremely rudimentary support for SSML/Sable using FestivalInt plugin. If it works for + you, it will be minor miracle. + * In FestivalInt configuration dialog, do not enable OK button if EXE Path is invalid. + * When Editing existing FestivalInt configuration, do not inadvertently change the selected + voice when rescanning. + * In FestivalInt configuration dialog, disable voice combo box until scan has been clicked. + +2004-11-26 Gary Cramblitt (PhantomsDad) + * Slight throughput improvement by eliminating pause at start of each Festival utterance. + * Bug fix. Can find synthesizers when HOME env variable is in the path. + +2004-11-25 Gary Cramblitt (PhantomsDad) + * Add Finnish Male voice (http://www.ling.helsinki.fi/suopuhe/download/) + * Improve throughput slightly by emitting Text Started signal after playback has begun. + +2004-11-24 Gary Cramblitt (PhantomsDad) + * Add overall Speed control to KTTSMgr Audio tab. This permits to speed up speech + even if the synth does not support it. Must have sox installed for this to work. + * Add Speed and Pitch controls to Epos plugin. Also set language. + * Better Speed adjustment for HTS voices. + +2004-11-23 Gary Cramblitt (PhantomsDad) + * Add support for OGI English and Spanish voices. + * Update Handbook. + * Better Spanish translations for voice names. + +2004-11-22 Gary Cramblitt (PhantomsDad) + * Add support for IMS German Festival. + * Update Handbook. + +2004-11-21 Gary Cramblitt (PhantomsDad) + * Add volume control to Festival Interactive. + * Accelerators for Festival Interactive configuration dialog. + * HTS voices only support Volume (not Speed or Pitch) + * Talker Codes reflect volume setting. + +2004-11-20 Gary Cramblitt (PhantomsDad) + * Add pitch control to Festival Interactive. + * GStreamer plugin. + +2004-11-17 Gary Cramblitt (PhantomsDad) + * Bug fix. Hosed Talker in KTTSMgr after removing a Talker and clicking Cancel. + * In KTTSMgr no longer record LastTalkerID in config file; compute at load time instead. + * Change Talker tab in KTTSMgr to "standard" layout. + * Bug fix. Talker matching. Tended to pick last talker no matter what. + +2004-11-13 Gary Cramblitt (PhantomsDad) + * Fix. Nothing happens when clicking Configure button for Festival Interactive + plugin if your desktop language is not English. Display translated name for + Synthesizer in Talkers tab. + * Display translated words for "male", "female", "neutral", "fast", "slow", "medium", + "loud", "soft" in KTTSMgr Talkers list and from Jobs "Change Talker" button. + * getTalkers() not returning country code as part of Talker Code. + * The Command plugin configuration dialog should enable OK button if %t or %f + appear in command, or "Send data via stdin" is checked. Also uncheck this option + by default in order for plugin to sense proper configuration. + * Add Jorge Luis Arzola to credits as tester. + +2004-11-11 Gary Cramblitt (PhantomsDad) + * Support for Festival 2.0. and Festival MultiSyn voices in FestivalInt plugin. + * Query Festival for available voices, rather than scanning for directories. + * Support for multiple versions of Festival executable. Now asks for EXE path rather + than voices path. + * Allow preload of Festival voices that take a long time to load. + If set, Festival is started when KTTSD starts and the voice is loaded. + * When stopText() is called and FestivalInt plugin is synthing (not saying) using + a pre-loaded voice, instead of killing Festival, which would cost hugely in + re-startup time, Festival is allowed to finish synthing and result is discarded. + This improves performance when rewinding/fastforwarding. + * Corrected FestivalInt voices file as to voice descriptions and languages. + Added MultiSyn voices. + * Added accelerators and WhatsThis help to FestivalInt, Command, Epos, Flite, and Hadifix + configuration dialogs. + * Added modal, cancelable, progress dialog while Testing in FestivalInt, Command, Epos, + Flite, FreeTTS, and Hadifix configuration dialogs. + This prevents a crash when user clicks OK or Cancel before test has completed. + * Command plugin always displays configuration dialog when added, i.e., never + autoconfigs. + * Speed adjustment disabled when using MultiSyn Festival voices. + * Allow KTTSMgr screen to be resized to minimum size. Allow splitter to resize jobs + ListView to minimum vertical size. + * No longer attempt to build Festival plugin (static linking to Festival/Speech Tools + libaries). User must explicitly request via ./configure --enable-kttsd-festival. + Code is woefully behind, I cannot get it to work, and probably wouldn't work + anymore even if I could get it to link and get past crash on first call to library. + FestivalInt seems to work just fine..grc. + +2004-11-10 Paul Giannaros (Cerulean) + * getTalkerCodes() returning corrupted talker codes. + +2004-11-04 Gary Cramblitt (PhantomsDad) + * Add English languages to FreeTTS desktop file so they show up in Add Talker dialog. + * When starting kttsmgr, if no Talkers are configured, default to Talker page. + * In kttsmgr, uncheck and disable Enable TTS checkbox until at least one Talker + has been configured. Once at least one Talker is added, enable the checkbox. + If all Talkers are removed, uncheck and disable the box and stop KTTSD. + * Give kttsmgr window an icon. + * Add option to embed KTTSMgr in the system tray. + +2004-11-03 Gary Cramblitt (PhantomsDad) + * Add ability to configure more than one plugin for a language, but with different + voices. Call them "talkers". + * Improve language selection and management in the KTTSD's configuration. Languages + added shouldn't be shown in the list of languages to add. Default languages should + show only the current languages. Languages should be shown as names and not codes. + Languages should be erased from the default language combo box. If a plugin does + not support a language, it should not be choosable. + * Add support for moving a text job to a different talker (language/plugin). + * Standarize the .desktop files + +2004-10-20 Gary Cramblitt (PhantomsDad) + * kcm_kttsmgr and kcm_kttsd are now identical libraries/KCModules. + Applications should use kcm_kttsd. When all apps have stopped + using kcm_kttsmgr, remove it. + +2004-10-19 Gary Cramblitt (PhantomsDad) + * The following installed files were renamed + on or about 19 Oct 2004: + In $KDEDIR/share/services/: + festival.desktop -> kttsd_festivalplugin.desktop + festivalint.desktop -> kttsd_festivalintplugin.desktop + command.desktop -> kttsd_commandplugin.desktop + hadifix.desktop -> kttsd_hadifixplugin.desktop + flite.desktop -> kttsd_fliteplugin.desktop + epos-kttsdplugin.desktop -> kttsd_eposplugin.desktop + freetts.desktop -> kttsd_freettsplugin.desktop + In $KDEDIR/lib/kde3/: + libfestivalplugin -> libkttsd_festivalplugin + libfestivalintplugin -> libkttsd_festivalintplugin + libcommandplugin -> libkttsd_commandplugin + libhadifixplugin -> libkttsd_hadifixplugin + libfliteplugin -> libkttsd_fliteplugin + libeposkttsdplugin -> libkttsd_eposplugin + libfreettsplugin -> libkttsd_freettsplugin + + Rename libkttsjobmgr to libkttsjobmgrpart per kdelibs/NAMING convention. + + There is a new shell script, clean_obsolete.sh that will remove these + obsolete files from your system. + +2004-10-14 Gary Cramblitt (PhantomsDad) + * Fix crash when clicking Cancel to prompt for Other language code, or user + enters an invalid language code. + +2004-10-13 Gary Cramblitt (PhantomsDad) + * libktts is now a versioned shared library. You may need to remove the + unversioned library, ie. + rm $KDEDIR/lib/libktts.* + +2004-10-13 Gary Cramblitt (PhantomsDad) + * Revert previous change. Sorry. + +2004-10-13 Gary Cramblitt (PhantomsDad) + * Remove versioning from libkttsd. + NOTE: After rebuilding and installing, you will want to delete + the following file: + $KDEDIR/lib/libkttsd.so.1.0.0 + +2004-10-12 Gary Cramblitt (PhantomsDad) + * Added Handbook. + * Help button and system tray content menu display Handbook. + +2004-09-07 Gary Cramblitt (PhantomsDad) + * Added 6 new HTS voices for Festival and FestivalInt plugins. + Courtesy of "N.Cat" Triskelios. + Download the voices from http://hts.ics.nitech.ac.jp/ + +2004-09-04 Gary Cramblitt (PhantomsDad) + * Rename Hadifax to Hadifix. + NOTE: After rebuilding and installing, you will want to delete the + following files: + $KDEDIR/lib/kde3/libhadifaxplugin.la + $KDEDIR/lib/kde3/libhadifaxplugin.so + $KDEDIR/share/services/hadifax.desktop + +2004-09-02 Paul Giannaros + * FreeTTS plugin. + +2004-08-28 Gary Cramblitt (PhantomsDad) + * GStreamer player added. Doesn't work right now because gstreamer 0.6 + has issues with wav files. Try again when KDE bindings (gst) have been + updated to gstreamer 0.8. + * If jobNum parameter in dcop calls is 0, default to last job queued by the + application, or if no such job, the current job (instead of last job). + * After call to stopText, if there more speakable jobs in the queue, they begin + speaking. + * Emit textPaused and textStopped signals as documented. + * Fix bug in festivalint plugin. Hangs sometimes with short utterances caused + by writing to Stdin before Stdin buffer is empty. + * Better sentence parsing. + * Minor fixes to kttsjobmgrpart. + +2004-08-09 Gary Cramblitt (PhantomsDad) + * Remove requirement to copy pluginconf.h to $KDEDIR/include when running designer + against plugin configuration widgets. + * Epos plugin not listing codecs. + * Some plugins listing codecs multiple times. + * Select and ensure default language visible when first displaying kttsmgr. + * Auto select and prepare Properties tab when adding a new language in kttsmgr. + +2004-08-08 Gary Cramblitt (PhantomsDad) + * Add plugin for Epos TTS Synthesis System (czech and slovak). + +2004-08-07 Gary Cramblitt (PhantomsDad) + * Existing release marked with tag ktts-0-1. + * New version of KTTS is 0.2. + * Many changes to KTTSD and the KTTSD plugins. + * QThreads have been mostly eliminated, which were causing anomalies. + * See libkttsd/pluginproc.h for the new plugin API. The new API is backwards + compatible to the old API. + * Plugins now perform synthesis separate from audibilizing, but they may do + both if needed. If a plugin supports separate synthesis, it returns True + in supportsSynth() method. + * If a plugin supports separate synthesis, KTTSD sends the synthesized audio file + to aRts for playback. The playback code has been designed with future + alternate playback plugins in mind, example gstreamer. (The code was adapted + from Juk with minor changes.) + * Plugins may work asynchronously, provide status via state() method and emit + signals when an operation has completed. If a plugin supports asynchronous + operation, it returns True in supportsAsync() method. + * If a plugin does not support asynchronous operation, it is wrapped in a QThread, + which is the same as before. See threadedplugin.h/cpp. + * Because playback is separate from synthesis and plugins work asynchronously, + pauseText or stopText _immediately_ pause or stop playback in mid-sentence. + * New plugin, flite, added for direct support of Festival Lite. + * The festivalint, command, and hadifax plugins were rewritten to support + these new capabilities. + * Need someone to rework the freetts plugin. I don't have JVM. + * Each plugin (except festival and freetts) now has a "Test" button on the + configuration dialog. + * The option to play a sound on text interruption now works. + +2004-07-10 Gary Cramblitt (PhantomsDad) + * Warn user if festival not installed. + * Install voices data file for festival interactive plugin even if + festival plugin not installed. + +2004-06-05 Gary Cramblitt (PhantomsDad) + * Draft KDE Text-to-Speech API Draft 3. + * Removed concept of paragraphs. + * Added concept of text job parts. + * Added sayScreenReaderOutput method. + * Replaced nextSenText, prevSenText, nextParText, prevParText with moveRelTextSentence. + * Added jumpToTextPart. + * Removed paragraph settings from configuration dialog. + +2004-05-31 Gary Cramblitt (PhantomsDad) + * Move speaking of notifications to KTTSD and persist notify settings. + * Clean up kcmkttsmgr.desktop and install to correct place. + +2004-05-23 Gary Cramblitt (PhantomsDad) + * Add option to speak KNotify notifications. + +2004-05-22 Gary Cramblitt (PhantomsDad) + * Change menu item to "Speak Text" on the Tools menu in app-plugins. Also start KTTSD if it is + not running. + +2004-05-21 Gary Cramblitt (PhantomsDad) + * Bug fix. Upon startup of kttsmgr, Remove button and Properties tab not enabled despite + default language selected. + +2004-05-19 Gary Cramblitt (PhantomsDad) + * Allow kttsmgr screen to be resized as narrow as possible. + * Correct some mispellings. + * When calling reinit() pauseText only if isSpeakingText(). + +2004-05-18 Gary Cramblitt (PhantomsDad) + * Reinit() without losing existing queued text jobs, messages, or warnings. + * When kttsd restarts because of reinit(), job manager part refreshes list. + +2004-05-17 Gary Cramblitt (PhantomsDad) + * Fix hang when calling reinit() while speaking. + +2004-05-15 Gary Cramblitt (PhantomsDad) + * Lots of changes: + * Defined the KDE Text-to-speech API. See /libktts/kspeech.h. + * Moved all the GUI stuff out of KTTSD into kttsmgr. KTTSD is a pure non-GUI deamon now. + * Added text job manager as a KPart (kttsjobmgr) for GUI interface. + * Replaced existing configuration manager (kcmkttsd) with kcmkttsmgr, which includes kttsjobmgr. + * Fixed several typos and minor bugs. + * Fixed Kate and KHTML Plugins. + * Updated README, TODO and this file. + * + * IMPORTANT note to developers using KTTSD: The startText, stopText, resumeText DCOP + * methods now take a jobNum argument, which you can code as 0 to operate against the last text + * job your app queued. See /plugins/kate-plugin/katekttsd.cpp for example, or /libktts/kspeech.h. + +2002-11-21 Pupeno <pupeno@pupeno.com> + * Full port to the new architecture finished. Some fine tunning still needed. + +2002-09-25 Pupeno <pupeno@pupeno.com> + * Festival Cliente/Server plug in added. Festival plug in was added initially in the + project. FreeTTS never was meant to work yet. + +2002-07-28 Pupeno <pupeno@pupeno.com> + * ChangeLog created. diff --git a/kttsd/Makefile.am b/kttsd/Makefile.am new file mode 100644 index 0000000..5a54368 --- /dev/null +++ b/kttsd/Makefile.am @@ -0,0 +1,19 @@ +# The build order must be libktts, then libkttsd, then the rest. +SUBDIRS = \ + compat \ + libkttsd \ + players \ + plugins \ + filters \ + kttsd \ + kttsjobmgr \ + kcmkttsmgr \ + kttsmgr \ + app-plugins \ + icons + +messages: rc.cpp + $(EXTRACTRC) */*.rc */*/*.rc >> rc.cpp + $(EXTRACTRC) */*.ui */*/*.ui >> rc.cpp + $(EXTRACTRC) --tag=name --context=FestivalVoiceName plugins/festivalint/voices >> rc.cpp + $(XGETTEXT) rc.cpp */*.cpp */*.h */*/*.cpp */*/*.h -o $(podir)/kttsd.pot diff --git a/kttsd/README b/kttsd/README new file mode 100644 index 0000000..7906f4f --- /dev/null +++ b/kttsd/README @@ -0,0 +1,166 @@ +KTTS -- KDE Text-to-Speech -- is a subsystem within the KDE desktop +for conversion of text to audible speech. KTTS is currently under +development and aims to become the standard subsystem for all KDE +applications to provide speech output. Currently, KTTS consists of +the following components: + +KTTSD + The KDE Text-to-Speech Deamon, a non-gui application that runs in +the background, providing TTS support to KDE applications. Applications +initiate TTS by making DCOP calls to kttsd. + +KTTSMGR + An application for configuring the KTTS System and for managing in-progress +speech jobs. kttsmgr provides an icon in the system tray for performing +these functions as needed. + +kttsjobmgr + A KPart for managing in-progress speech jobs. + +kcmkttsd + A KControl module for configuring the KTTS System. kcmkttsd runs in the +KDE Control Center or start it with the command "kcmshell kcmkttsd". + +ktexteditor_kttsd + A plugin for the KDE Advanced Text Editor that permits you to speak an entire +text file or any portion of a file. + +khmtl-plugin + A plugin for Konqueror that permits you to speak all or any portion of an +HTML web page. (Note: this component is in kdebase/konqueror/kttsplugin) + +Original Author: Jos Pablo Ezequiel "Pupeno" Fernndez <pupeno@kde.org> +Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net> +Contributors: + Olaf Schmidt <ojschmidt@kde.org> + Gunnar Schmi Dt <gunnar@schmi-dt.de> + Paul Giannaros <ceruleanblaze@gmail.com> + Jorge Luis Arzola <arzolacub@hotmail.com> + David Powell <achiestdragon@gmail.com> + +User features: + * Speak contents of a text file. + * Speak KDE notification events (KNotify). + * Speak all or part of the text of a web page in Konqueror. + * Speak all or part of the text in Kate text editor, including + instances where Kate is embedded in another KDE application. + * Long text is parsed into sentences. User may backup by sentence + or paragraph, replay, pause, and stop playing. + * Filter plugins for converting chat emoticons to speakable words, + fix mispronunciations, and convert xhtml to SSML. + * Audio output via aRts, ALSA, GStreamer (version 0.8.7 or later), + or aKode. + +Programmer features: + * Priority system for screen reader outputs, warnings and messages, + while still playing regular texts. + * Plugin-based architecture for support of a wide variety of speech + synthesis engines and drivers. + * Permit generation of speech from the command line (or via shell scripts) + using the KDE DCOP utilities. + * Provide a lightweight and easily usable interface for applications + to generate speech output. + * Applications need not be concerned about contention over the speech device. + * FUTURE: Provide support for speech markup languages, such as VoiceXML, Sable, + Java Speech Markup Language (JSML), and Speech Markup Meta-language (SMML). + * FUTURE: Provide limited support for embedded speech markers. + * Asynchronous to prevent system blocking. + +Requirements +------------ +KDE 3.2 or greater and a speech synthesis engine, such as Festival. Festival +can be obtained from http://www.cstr.ed.ac.uk/projects/festival/. Festival +is distributed with most Linux distros. Check your distro CDs. Also works +with Hadifix, Epos, Festival Lite (flite), FreeTTS, or any command that can +speak text. + +Optional Components +------------------- + * The sox utility is needed for adjusting overall speech rate. + * The xsltproc utility is needed for SSML support. + * GStreamer 0.8.7 or greater. + +See the KTTS Handbook + + (http://accessibility.kde.org/developer/kttsd/handbook/index.html) + +for complete instructions on installing, compiling, configuring, and using +KTTS. + +Audio Plugins +------------- +KTTS can be built with the following audio plugins. The "Default Configure +"Option" column shows the default option for the configure command. +The "Alternate" column gives the opposite configure option: + + System Plugin Default Configure Option Alternate + ------ ------------ ------------------------- ------------------------ + aRts artsplayer --with-arts --without-arts + ALSA alsaplayer --with-alsa=check --with-alsa=no + GStreamer gstplayer --with-gstreamer=no --with-gstreamer=check + aKode akodeplayer --with-akode=no --with-akode=check + +You *must* have at least one of these plugins for KTTS to work. + +The aKode and GStreamer players are not built by default because they have issues +with instant stopping and starting. + +Packager Notes +-------------- +The following speech synthesizer plugins are all built by default. +Some of them are runtime only dependent upon non-free software. +(Non-free according to Debian Policy). The "Configure Option to +not build" column shows the configure command to not build the plugin: + + Synth License Configure Option to not build + ------------- -------- ----------------------------- + Festival free --disable-kttsd-festivalint + Festival Lite free --disable-kttsd-flite + Epos free --disable-kttsd-epos + Command free --disable-kttsd-command + Hadifix non-free --disable-kttsd-hadifix + FreeTTS non-free --disable-kttsd-freetts + +IMPORTANT NOTE TO BINARY PACKAGERS: +Please do not distribute binary packages of kdeaccessibility with the +gstreamer plugin, as this creates unwanted dependencies. Instead, +distribute the gstreamer plugin in a separate package, e.g. +kde-accessibility-extras. The separate package should require +gstreamer >= 0.8.7. Thank you. + +Support +------- +The KTTS website is + + http://accessibility.kde.org/developer/kttsd + +In addition to the KDE Bugzilla database (bugs.kde.org), discussions for KTTSD +currently take place in the kde-accessibility mailing list. Subscribe at +https://mail.kde.org/mailman/listinfo/kde-accessibility. Developers +also hang out in IRC (irc.kde.org channel #kde-accessibility). + +Directories +----------- +libktts Library for applications desiring to speak text. This must be built first. +libkttsd Library for KTTSD. Defines the interface for plugins for KTTSD. Must be built second. +players Audio plugins for KTTSD. + artsplayer aRts audio plugin for KTTSD. + gstplayer GStreamer audio plugin for KTTSD. + alsaplayer ALSA audio plugin for KTTSD. + akodeplayer aKode audio plugin for KTTSD. +plugins Plugins for KTTSD. You must have at least one plugin to use KTTSD. + festivalint Plugin for Festival using Festival Interactive mode (festival --interactive). + flite Plugin for Festival Lite. + hadifix Plugin for Hadifix. + freetts Plugin for FreeTTS. + epos Plugin for Epos. + command Generic command plugin. +kttsd KTTSD itself. Non-interactive deamon. +icons Icon for kttsmgr and KTTSD. +kttsjobmgr A KPart used by kcmkttsmgr for managing text jobs in KTTSD. +kcmkttsmgr A KControl module for managing KTTSD. Run using "kcmshell kcmkttsd". +kttsmgr Application for managing KTTSD. Wrapper for kcmkttsmgr plus a system tray icon. +app-plugins Plugins for other applications to speak text. + kate Embedded Kate plugin. +compat Copy of kdelibs/interfaces/kspeech. Used when compiling for KDE less than 3.4. +debian Debian packaging files. (possibly way out of date) diff --git a/kttsd/README.kttsd b/kttsd/README.kttsd new file mode 100644 index 0000000..2fe8d51 --- /dev/null +++ b/kttsd/README.kttsd @@ -0,0 +1,10 @@ +Where is KTTSD? + +KTTSD was moved on 20 Dec 2004. +Most of it has moved to kdeaccessibility/kttsd. +The following pieces were also moved. + +kttsd/libktts -> kdelibs/interfaces/kspeech +kttsd/app-plugins/khtml-plugin -> kdebase/konqueror/kttsplugin +kttsd/app-plugins/kate-plugin -> kdebase/kate/plugins/ktts +kttsd/app-plugins/katepart-plugin -> kdebase/kate/plugin/katepartktts diff --git a/kttsd/TODO b/kttsd/TODO new file mode 100644 index 0000000..27ef1a3 --- /dev/null +++ b/kttsd/TODO @@ -0,0 +1,95 @@ +_ Filters: + _ XHTMLtoSSML stylesheet needs work to provide better mappings for most web pages. + _ Add KNewStuff capability for folks to upload/download filter configs. + _ There should be a default set of filters configured when user first runs KTTSMgr. + If user presses Default button, it should ask user whether to replace all existing + filters with the standard set. The standard set should probably include + the Qt to Plain Text and Fix Festival Unspeakable Characters filters, but I'm not + sure about the latter when desktop is not ISO 8859-1. + _ The SBD Filter configuration dialog can be shrunk smaller than the widgets on it. + This apparently due to a bug in the QLabel widget when it contains RichText. If + forced to plain text, the problem won't happen (I am told). However, the WARNING + in the dialog needs to be bolded. What to do? +_ Handbook: + _ Mention that "Root element is" and "DOCTYPE" are case sensitive. "html" is not + the same as "HTML". It should stay this way, BTW. Proper xhtml requires lowercase + tags. +_ Audio Plugins: + _ Audio Plugins are loaded for each sentence. Instead, reuse the plugins from a pool + of loaded plugins. + _ BUG:116542. Apply patch and resolve compile issue. + _ At least one user experiences a freeze-up using ALSA plugin. Speak a web page, + pause the page, cause a knotify to speak, kttsd freezes. This appears to be an + ALSA device contention problem? In this situation, there are two alsaplayer objects; + one in pause state, and one attempting to speak notify. + _ akodelib does not stop instantly when pause() or stop() is called. See bug:107135. + _ Why must Player constructor take a 3rd "args" argument, while PluginProc constructor + does not. They are essentially the same. ?? + _ Add option to automatically turn down music player volume while speaking (via dcop). +_ Festival Interactive plugin: + _ Need someone who can read czech to try integrating this into KTTS: + http://www.freebsoft.org/festival-czech + _ New Italian voice for Festival. http://www.pd.istc.cnr.it/FESTIVAL/home/default.htm +_ EPOS plugin: +_ Command plugin: + _ Command plugin encodes text on command line using %t in user's chosen encoding, but + if this differs from user's desktop default encoding, doesn't work. KProcess always + encodes command line stuff using user's default encoding. There is no point, therefore, + in attempting to encode it differently, just mangles the text even more. Short of modifying + KProcess, I see no way around this. But perhaps a warning to user advising to use %f instead? + _ Command plugin crashes when using command 'flite -t "%t"' and flite can't get write access to /dev/dsp*. +_ Hadifix plugin: + _ BUG:116151 Add options for using the new prepoc capability in Hadifix. + _ When stopText() is called, and Hadifix process is killed, the temporary wav file is not deleted, + despite QFile::remove call. Possibly still locked by exiting mbrola process? This only seems + to happen in the configuration dialog when canceling Tests. +_ KTTSMgr: + _ Give Talkers a Talker Name entered by user. Show this name in Jobs tab, Change Talker screen, + and in systray menu. + _ Change the Current Sentence panel to show the current sentence of the selected job? If selected + job is not the currently speaking job, won't show currently-speaking text anymore. However, would + be helpful when using advance/rewind buttons. Perhaps to be effective, if the currently-selected + job IS the currently speaking job, and that job finishes, would automatically select the next + speaking job? But that could become confusing for users with selection jumping around. + _ Add ability to quickly pick default Talker in systray menu. + _ KCMultiDialog has a bug that won't allow sizing the screen narrower than 640. + _ Overall Audio Speed should be a realtime setting, i.e., not necessary to click Apply. + _ Disable Audio Speed control if sox not installed (configure.in.in but runtime check would be better). + _ Add drag N drop for the Talkers so it is easier to change the ordering. + _ It is possible to configure multiple instances of Talkers with the exact same configuration. + _ In kcmkttsmgr, use setHelp() to set help anchor based on current tab. + _ Add playback controls to kttsmgr. Volume, pause. Maybe rewind? +_ KTTSD: + _ I am told that when kttsd crashes, it can take knotify down with it. See about + "disconnecting" knotify from kttsd (using post to Qt event queue). + _ Strange msg being produced by KIO::move, but it works fine. ?? + kio (KIOJob): stat file:///home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav + kio (KIOJob): error 11 /home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav + kio (KIOJob): This seems to be a suitable case for trying to rename before stat+[list+]copy+del + _ While kttsd currently configures a Talker automatically, would be nice if it could + autoconfigure talkers for each requested language, if one has not already been configured. + _ Add support for speech markers. + _ Implement error signal handling in KTTSD. + _ Add support for JSML and other Speech Markup languages. + _ Add method for apps to play a sound file, including in the middle of a job. + Could be done with SSML or with special Talker Code = "sound". +_ Other/General: + _ As pointed out by NOKUBI Takatsugu on Planet Debian, there exists an + open-source synthesis + recognition engine for Japanese. + http://aniki.daionet.gr.jp/~knok/blog/blosxom.cgi/2005/11/24 + http://hil.t.u-tokyo.ac.jp/~galatea/index.html + _ Save buttons should confirm if user chooses an existing file. KFileDialog doesn't seem to + offer this option, but shouldn't it? + _ .desktop files must have a Type keyword, but what to use for kcmkttsd_testmessage.desktop? + _ Add option to kill plugin process when no longer needed so that memory can be recovered. + Or maybe some sort of "nice" setting so that process will be swapped out when no longer needed. + _ Convert to KConfig XT. + _ Add a plugin for TuxTalk, http://tuxtalk.sourceforge.net/. Since it only supports + English (at the moment) and is alpha, don't think it is worth it now. + _ Add a plugin for dhvani, an Indian and Hindi TTS engine, http://dhvani.sourceforge.net/ + Currently written in c, but it looks like future versions will require Java VM. + Main problem at the moment is that dhvani sends audio directly to /dev/dsp with no option + to send to a wav file. + _ IBM ViaVoice plugin? IBM TTS is a proprietary runtime product with opensource API. + http://ibmtts-sdk.sourceforge.net/ + _ Add KTTS plugin to aKregator. diff --git a/kttsd/TODO.french b/kttsd/TODO.french new file mode 100644 index 0000000..32372c3 --- /dev/null +++ b/kttsd/TODO.french @@ -0,0 +1,31 @@ +The <a href="http://accessibility.kde.org/developer/kttsd/index.php">KDE Text-to-Speech System</a> currently supports the following languages via non-commercial TTS systems and voices on the Linux platform: + +American English +British +Spanish +German +Finnish +Czech +Polish +Russian +Italian +Kiswahili +Zulu +Ibibio + +Notably absent from this list is French (French Canadian voices can be purchased from <a href="http://www.cepstral.com">Cepstral LLC</a>). This is a damn shame. + +If you are French speaking, your mission, should you choose to accept it, is to develop a free and easy-to-install French language package for KTTS on the Linux platform. There are several possible ways this could be done: + +1. MBROLA and txt2pho. This solution uses MBROLA to synthesize voice from phonemes. What's needed is a means to convert text into phonemes. There are French voices available for MBROLA at <a href="http://tcts.fpms.ac.be/synthesis/mbrola/mbrcopybin.html">http://tcts.fpms.ac.be/synthesis/mbrola/mbrcopybin.html</a>. There are txt2pho utilities and other TTS systems available at <a href="http://tcts.fpms.ac.be/synthesis/mbrola/mbrtts.html">http://tcts.fpms.ac.be/synthesis/mbrola/mbrtts.html</a>. I tried these without success. In some cases, the txt2pho programs fussed about "little endian", so this might require some perl programming. If you get a solution working this way, it would employ the Hadifix plugin in KTTS. + +2. MBROLA and Festival. Like #1, this solution uses MBROLA to synthesize voice from phonemes, but uses Festival to do the text to phoneme conversion. More info at <a href="http://www.cstr.ed.ac.uk/projects/festival/mbrola.html">http://www.cstr.ed.ac.uk/projects/festival/mbrola.html</a>. A solution using this technique was developed for Festival 1.4.3 called FranFest. Google for "FranFest". Also, here's an article that explains how to install it: <a href="http://www.pollock-nageoire.net/festival/festival-english004.html">http://www.pollock-nageoire.net/festival/festival-english004.html</a>. The problem with this is 1) it requires patching and rebuilding Festival, and 2) it needs to be adapted to Festival 2.0 (1.95 beta). I would recommend approaching the Festival programmers about incorporating this patch into Festival permanently. + +3. Native Festival. Develop a "native" voice for Festival so that Festival can do the full TTS conversion. This is much harder than #1 or #2, but would be much easier for KTTS users to install. From time to time, I have seen people mention on the festival-talk mailing list that they are working on this, so it may already be done or near done. More info at <a href="http://festvox.org/maillists.html">http://festvox.org/maillists.html</a>. Unfortunately, the festival-talk mailing list archive is not available. I can send you an mbox of what I have if you are interested. + +Whatever solution you come up with, you'll need to document how French KTTS users install and configure it. If your solution uses Festival, it must list the voice code for the French voice in response to the following Scheme statement: + + (print (mapcar (lambda (pair) (car pair)) voice-locations)) + +Contact me in irc.kde.org channel #kde-accessibility for more information. + diff --git a/kttsd/app-plugins/Makefile.am b/kttsd/app-plugins/Makefile.am new file mode 100644 index 0000000..22a2493 --- /dev/null +++ b/kttsd/app-plugins/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = kate diff --git a/kttsd/app-plugins/kate/Makefile.am b/kttsd/app-plugins/kate/Makefile.am new file mode 100644 index 0000000..3508e52 --- /dev/null +++ b/kttsd/app-plugins/kate/Makefile.am @@ -0,0 +1,19 @@ +# Allow build from within kdebase or kdeaccessibility. +INCLUDES = \ + -I$(top_srcdir)/kate/interfaces \ + -I$(kde_includes)/kate \ + $(all_includes) + +METASOURCES = AUTO + +# Install this plugin in the KDE modules directory +kde_module_LTLIBRARIES = ktexteditor_kttsd.la + +ktexteditor_kttsd_la_SOURCES = katekttsd.cpp +ktexteditor_kttsd_la_LIBADD = -lktexteditor +ktexteditor_kttsd_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) + +kttsddatadir = $(kde_datadir)/ktexteditor_kttsd +kttsddata_DATA = ktexteditor_kttsdui.rc + +kde_services_DATA = ktexteditor_kttsd.desktop diff --git a/kttsd/app-plugins/kate/katekttsd.cpp b/kttsd/app-plugins/kate/katekttsd.cpp new file mode 100644 index 0000000..a2e64e6 --- /dev/null +++ b/kttsd/app-plugins/kate/katekttsd.cpp @@ -0,0 +1,120 @@ +/*************************************************************************** + A KTextEditor (Kate Part) plugin for speaking text. + + Copyright: + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + + Original Author: Olaf Schmidt <ojschmidt@kde.org> + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qmessagebox.h> +#include <dcopclient.h> +#include <qtimer.h> + +// KDE includes. +#include <ktexteditor/editinterface.h> +#include <ktexteditor/selectioninterface.h> + +#include <kaction.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kapplication.h> +#include <kgenericfactory.h> + +// KateKttsdPlugin includes. +#include "katekttsd.h" +#include "katekttsd.moc" + +K_EXPORT_COMPONENT_FACTORY( ktexteditor_kttsd, KGenericFactory<KateKttsdPlugin>( "ktexteditor_kttsd" ) ) + +KateKttsdPlugin::KateKttsdPlugin( QObject *parent, const char* name, const QStringList& ) + : KTextEditor::Plugin ( (KTextEditor::Document*) parent, name ) +{ +} + +KateKttsdPlugin::~KateKttsdPlugin() +{ +} + + +void KateKttsdPlugin::addView(KTextEditor::View *view) +{ + KateKttsdPluginView *nview = new KateKttsdPluginView (view, "KTTSD Plugin"); + m_views.append (nview); +} + +void KateKttsdPlugin::removeView(KTextEditor::View *view) +{ + for (uint z=0; z < m_views.count(); ++z) + if (m_views.at(z)->parentClient() == view) + { + KateKttsdPluginView *nview = m_views.at(z); + m_views.remove (nview); + delete nview; + } + KGlobal::locale()->removeCatalogue("kttsd"); +} + + +KateKttsdPluginView::KateKttsdPluginView( KTextEditor::View *view, const char *name ) + : QObject( view, name ), + KXMLGUIClient( view ) +{ + view->insertChildClient( this ); + setInstance( KGenericFactory<KateKttsdPlugin>::instance() ); + KGlobal::locale()->insertCatalogue("kttsd"); + (void) new KAction( i18n("Speak Text"), "kttsd", 0, this, SLOT(slotReadOut()), actionCollection(), "tools_kttsd" ); + setXMLFile( "ktexteditor_kttsdui.rc" ); +} + +void KateKttsdPluginView::slotReadOut() +{ + KTextEditor::View *v = (KTextEditor::View*)parent(); + KTextEditor::SelectionInterface *si = KTextEditor::selectionInterface( v->document() ); + QString text; + + if ( si->hasSelection() ) + text = si->selection(); + else { + KTextEditor::EditInterface *ei = KTextEditor::editInterface( v->document() ); + text = ei->text(); + } + + DCOPClient *client = kapp->dcopClient(); + // If KTTSD not running, start it. + if (!client->isApplicationRegistered("kttsd")) + { + QString error; + if (kapp->startServiceByDesktopName("kttsd", QStringList(), &error)) + QMessageBox::warning(0, i18n( "Starting KTTSD Failed"), error ); + } + QByteArray data; + QByteArray data2; + QCString replyType; + QByteArray replyData; + QDataStream arg(data, IO_WriteOnly); + arg << text << ""; + if ( !client->call("kttsd", "KSpeech", "setText(QString,QString)", + data, replyType, replyData, true) ) + QMessageBox::warning( 0, i18n( "DCOP Call Failed" ), + i18n( "The DCOP call setText failed." )); + QDataStream arg2(data2, IO_WriteOnly); + + arg2 << 0; + if ( !client->call("kttsd", "KSpeech", "startText(uint)", + data2, replyType, replyData, true) ) + QMessageBox::warning( 0, i18n( "DCOP Call Failed" ), + i18n( "The DCOP call startText failed." )); +} + diff --git a/kttsd/app-plugins/kate/katekttsd.h b/kttsd/app-plugins/kate/katekttsd.h new file mode 100644 index 0000000..cbab720 --- /dev/null +++ b/kttsd/app-plugins/kate/katekttsd.h @@ -0,0 +1,57 @@ +/*************************************************************************** + A KTextEditor (Kate Part) plugin for speaking text. + + Copyright: + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + + Original Author: Olaf Schmidt <ojschmidt@kde.org> + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef _KATEKTTSD_H_ +#define _KATEKTTSD_H_ + +#include <ktexteditor/plugin.h> +#include <ktexteditor/view.h> +#include <kxmlguiclient.h> +#include <qobject.h> + +class KateKttsdPlugin : public KTextEditor::Plugin, public KTextEditor::PluginViewInterface +{ + Q_OBJECT + + public: + KateKttsdPlugin( QObject *parent = 0, + const char* name = 0, + const QStringList &args = QStringList() ); + virtual ~KateKttsdPlugin(); + + void addView (KTextEditor::View *view); + void removeView (KTextEditor::View *view); + + private: + QPtrList<class KateKttsdPluginView> m_views; +}; + +class KateKttsdPluginView : public QObject, public KXMLGUIClient +{ + Q_OBJECT + + public: + KateKttsdPluginView( KTextEditor::View *view, const char *name=0 ); + ~KateKttsdPluginView() {}; + + public slots: + void slotReadOut(); +}; + +#endif // _KATEKTTSD_H_ diff --git a/kttsd/app-plugins/kate/ktexteditor_kttsd.desktop b/kttsd/app-plugins/kate/ktexteditor_kttsd.desktop new file mode 100644 index 0000000..aac7e0c --- /dev/null +++ b/kttsd/app-plugins/kate/ktexteditor_kttsd.desktop @@ -0,0 +1,104 @@ +[Desktop Entry] +Name=KTextEditor KTTSD Plugin +Name[bg]=Приставка KTextEditor KTTSD +Name[br]=Lugent KTTSD KTextEditor +Name[bs]=KTextEditor KTTSD dodatak +Name[ca]=Connector pel KTTSD del KTextEditor +Name[cs]=KTextEditor KTTSD modul +Name[de]=KTextEditor-Modul für KTTSD +Name[el]=Πρόσθετο KTextEditor KTTSD +Name[es]=Complemento KTextEditor KTTSD +Name[et]=KTextEditor KTTSD plugin +Name[eu]=KTextEditor-en KTTSD plugina +Name[fa]=وصلۀ KTextEditor KTTSD +Name[fi]=KTextEditor KTTSD -liitännäinen +Name[fr]=Module KTTSD pour KTextEditor +Name[ga]=Breiseán KTTSD KTextEditor +Name[gl]=Plugin de KTTSD para KTextEditor +Name[he]=תוסף KTextEditor עבור KTTSD +Name[hu]=KTextEditor KTTSD-modul +Name[is]=KTextEditor KTTSD íforrit +Name[it]=Plugin di KTTSD per KTextEditor +Name[ja]=KTextEditor KTTSD プラグイン +Name[ka]=KTextEditor KTTSD მოდული +Name[km]= កម្មវិធីជំនួយ KTextEditor KTTSD +Name[lt]=KTextEditor KTTSD priedas +Name[mk]=KTTSD-приклучок за KTextEditor +Name[ms]=Plugin KTextEditor KTTSD +Name[nb]=KTextEditor programtillegg for KTTSD +Name[nds]=KTTSD-Moduul KTextEditor +Name[ne]=केडीई पाठ सम्पादक KTTSD प्लगइन +Name[nl]=KTextEditor KTTSD-plugin +Name[pa]=KTextEditor KTTSD ਪਲੱਗਿੰਨ +Name[pl]=Wtyczka KTTSD dla edytora tekstu +Name[pt]='Plugin' KTextEditor do KTTSD +Name[pt_BR]=Plug-in do KTTSD para o KTextEditor +Name[ru]=Модуль KTextEditor для KTTSD +Name[sk]=KTextEditor KTTSD modul +Name[sl]=Vstavek KTTSD za KTextEditor +Name[sr]=KTTSD као прикључак за KTextEditor +Name[sr@Latn]=KTTSD kao priključak za KTextEditor +Name[sv]=KTTSD-insticksprogram för Ktexteditor +Name[ta]=KTextEditor KTTSD சொருகுப்பொருள் +Name[tg]=Модли KTextEditor барои KTTSD +Name[th]=ปลั๊กอิน KTTSD สำหรับ KTextEditor +Name[tr]=Ktexteditor KTTSD Eklentisi +Name[uk]=Втулок KTextEditor KTTSD +Name[vi]=Trình bổ sung KTTSD cho Trình soạn thảo K +Name[zh_CN]=KTextEditor KTTSD 插件 +Name[zh_TW]=K-文字編輯器的 kttsd 外掛程式 +Comment=Adds a menu entry for speaking the text +Comment[bg]=Добавяне на операция в менюто за изговаряне на текст +Comment[bs]=Dodaje stavku u meniju za čitanje teksta +Comment[ca]=Afegeix una entrada de menú per pronunciar el text +Comment[cs]=Vložení nabídky pro předčítání textu +Comment[da]=Tilføjer et menupunkt for at læse teksten op +Comment[de]=Fügt einen Menüeintrag zum Vorlesen des Textes hinzu +Comment[el]=Προσθέτει μία καταχώρηση στο μενού για τη μετατροπή σε φωνή του κειμένου +Comment[es]=Añadir una entrada al menú para leer el texto +Comment[et]=Lisab menüükirje teksti kõnelemiseks +Comment[eu]=Testuak ahoskatzeko menuko sarrera bat gehitzen du +Comment[fa]=مدخل گزینگان را برای گفتن متن اضافه میکند +Comment[fi]=Lisää valikkovalinnan tekstin puhumiseksi +Comment[fr]=Ajoute une entrée dans le menu pour prononcer le texte +Comment[ga]=Cuir iontráil sa roghchlár chun an téacs a rá +Comment[gl]=Engade unha entrada no menu para falar o texto +Comment[he]=מוסיף כניסה בתפריט להקראת הטקסט +Comment[hu]=Menüpont létrehozása szövegfelolvasáshoz +Comment[is]=Bætir við valmyndarfærslu fyrir tal af textanum +Comment[it]=Aggiunge una voce del menu per pronunciare il testo +Comment[ja]=テキスト読み上げをメニューエントリに追加する +Comment[ka]=ტექსტის წაკითხვას ამატებს მენიუს ჩანაწერს +Comment[km]=បន្ថែមធាតុម៉ឺនុយសម្រាប់ការនិយាយអត្ថបទ +Comment[lt]=Prideda meniu įrašą teksto įkalbėjimui +Comment[mk]=Додава елемент во менито за зборување на текстот +Comment[ms]=Tambah entri menu untuk menjadikan teks bercakap +Comment[nb]=Legger til en menyoppføring for å si teksten høyt +Comment[nds]=Föögt en Menüindrag för dat Textvörlesen to +Comment[ne]=पाठ बोल्नका लागि मेनु प्रविष्टि थप्छ +Comment[nl]=Voegt een menuoptie toe voor het uitspreken van de tekst +Comment[pa]=ਪਾਠ ਬੋਲਣ ਲਈ ਇੱਕ ਮੇਨੂ ਇੰਦਰਾਜ਼ ਜੋੜਦਾ ਹੈ +Comment[pl]=Dodaje wpis w menu do mówienia tekstu +Comment[pt]=Adiciona uma entrada de menu para ler o texto +Comment[pt_BR]=Adiciona uma entrada no menu para falar o texto +Comment[ru]=Добавляет меню синтеза речи +Comment[sk]=Pridá položku menu pre hovorenie textu +Comment[sl]=Dodaj menijski vnos za govorjenje besedila +Comment[sr]=Додаје ставку менија за изговарање текста +Comment[sr@Latn]=Dodaje stavku menija za izgovaranje teksta +Comment[sv]=Lägger till ett menyalternativ för att läsa upp texten +Comment[ta]=உரையை பேசுவதற்கு ஒரு பட்டியல் உள்ளிட்டை சேர்க்கிறது +Comment[tg]=Илова кардани менюи таҳлили овоз +Comment[th]=เพิ่มรายการเมนูสำหรับอ่านข้อความเป็นเสียง +Comment[tr]=Metni okumak için bir menü girdisi oluşturur +Comment[uk]=Додає елемент меню для декламування тексту +Comment[vi]=Thêm danh mục thực đơn cho việc đọc văn bản +Comment[zh_CN]=添加可读出文本的菜单项 +Comment[zh_TW]=新增朗讀文字的目錄 +Type=Service +ServiceTypes=KTextEditor/Plugin +X-KDE-Library=ktexteditor_kttsd +X-Kate-Version=2.2 +MimeType=text/plain +Icon=kttsd +author=Olaf Jan Schmidt, ojschmidt@kde.org diff --git a/kttsd/app-plugins/kate/ktexteditor_kttsdui.rc b/kttsd/app-plugins/kate/ktexteditor_kttsdui.rc new file mode 100644 index 0000000..9dea629 --- /dev/null +++ b/kttsd/app-plugins/kate/ktexteditor_kttsdui.rc @@ -0,0 +1,11 @@ +<!DOCTYPE kpartgui> +<kpartplugin name="ktexteditor_kttsd" library="ktexteditor_kttsd" version="2"> + <MenuBar> + <Menu name="tools"><Text>&Tools</Text> + <Action name="tools_kttsd" /> + </Menu> + </MenuBar> + <ToolBar name="mainToolBar"><text>Main Toolbar</text> + <Action name="tools_kttsd" /> + </ToolBar> +</kpartplugin> diff --git a/kttsd/clean_obsolete.sh b/kttsd/clean_obsolete.sh new file mode 100755 index 0000000..17e8c26 --- /dev/null +++ b/kttsd/clean_obsolete.sh @@ -0,0 +1,105 @@ +#! /bin/sh + +# This script cleans obsolete KTTS files from your system. +# You should run this if you have been downloading KTTS and installing +# prior to the indicated dates. + +# You would normally run this after running configure and before +# make install, i.e., +# cd kdenonbeta +# echo kttsd>inst-apps +# make -f Makefile.cvs +# ./configure +# cd kttsd +# ./clean_obsolete.sh +# make install + +PREFIX=$(kde-config --prefix) +LIBTOOL="../libtool" + +if [ -z "$PREFIX" ]; then + echo "KDE prefix not found. Do you have kde-config installed?" + exit +fi + +if [ ! -x $LIBTOOL ]; then + echo "libtool was not found. Did you run configure?" + exit +fi + +set -x + +# libktts removed. See kdeaccessibility/kttsd/kcmkttsmgr/Makefile.am +# for example how to build without it. +# on or about 20 Dec 2004. +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libktts +$LIBTOOL --mode=uninstall $PREFIX/lib/libktts + +# ServiceType kttsd.desktop renamed to kttsd_synthplugin.desktop, +# which distinquishes it from kttsd.desktop in the services dir +# and more accurately reflects its purpose +# on or about 8 Dec 2004. +rm -f $PREFIX/share/servicetypes/kttsd.desktop + +# kcm_kttsmgr removed. Use kcm_kttsd instead. +# Change made on or about 18 Dec 2004. +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/kcm_kttsmgr +rm -f $PREFIX/share/applnk/Settings/Accessibility/kcmkttsmgr.desktop +rm -f $PREFIX/share/applications/kde/kcmkttsmgr.desktop + +# Renamed libkttsjobmgr to libkttsjobmgrpart per kdelibs/NAMING convention +# on or about 19 Oct 2004: + +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libkttsjobmgr + +# The following installed files were renamed +# on or about 19 Oct 2004: +# In $KDEDIR/share/services/: +# festival.desktop -> kttsd_festivalplugin.desktop +# festivalint.desktop -> kttsd_festivalintplugin.desktop +# command.desktop -> kttsd_commandplugin.desktop +# hadifix.desktop -> kttsd_hadifixplugin.desktop +# flite.desktop -> kttsd_fliteplugin.desktop +# epos-kttsdplugin.desktop -> kttsd_eposplugin.desktop +# freetts.desktop -> kttsd_freettsplugin.desktop +# In $KDEDIR/lib/kde3/: +# libfestivalplugin -> libkttsd_festivalplugin +# libfestivalintplugin -> libkttsd_festivalintplugin +# libcommandplugin -> libkttsd_commandplugin +# libhadifixplugin -> libkttsd_hadifixplugin +# libfliteplugin -> libkttsd_fliteplugin +# libeposkttsdplugin -> libkttsd_eposplugin +# libfreettsplugin -> libkttsd_freettsplugin + +rm -f $PREFIX/share/services/festival.desktop +rm -f $PREFIX/share/services/festivalint.desktop +rm -f $PREFIX/share/services/command.desktop +rm -f $PREFIX/share/services/hadifix.desktop +rm -f $PREFIX/share/services/flite.desktop +rm -f $PREFIX/share/services/epos-kttsdplugin.desktop +rm -f $PREFIX/share/services/freetts.desktop + +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libfestivalplugin +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libfestivalintplugin +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libcommandplugin +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libhadifixplugin +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libfliteplugin +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libeposkttsdplugin +$LIBTOOL --mode=uninstall $PREFIX/lib/kde3/libfreettsplugin + +# The following library was changed from unversioned to versioned +# on or about 13 Oct 2004, + +$LIBTOOL --mode=uninstall $PREFIX/lib/libktts + +# The hadifax plugin was renamed to hadifix +# on or about 4 Sep 2004. + +rm -f $PREFIX/share/services/hadifax.desktop +$LIBTOOL --mode=uninstall /lib/kde3/libhadifaxplugin + +# Clean up the library cache. + +$LIBTOOL --mode=finish -n $PREFIX/lib +$LIBTOOL --mode=finish -n $PREFIX/lib/kde3/ + diff --git a/kttsd/compat/Makefile.am b/kttsd/compat/Makefile.am new file mode 100644 index 0000000..f1e3edd --- /dev/null +++ b/kttsd/compat/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = \ + $(KTTS_ICONS_DIR) \ + $(KTTS_INTERFACES_DIR) diff --git a/kttsd/compat/README_COMPAT b/kttsd/compat/README_COMPAT new file mode 100644 index 0000000..40729d0 --- /dev/null +++ b/kttsd/compat/README_COMPAT @@ -0,0 +1,6 @@ +This is a directory to keep compatibility in KTTSD. +This directory permits distribution and compilation of the following kttsd components: + If KDE < 3.4, copy of kdelibs/interfaces/kspeech. + If KDE < 3.5, copy of kdelibs/pics (kttsd icons only) + +Last Sync: Sat Mar 26 18:00:00 EST 2004 by Gary Cramblitt (PhantomsDad) <garycramblitt@comcast.net> diff --git a/kttsd/compat/icons/Makefile.am b/kttsd/compat/icons/Makefile.am new file mode 100644 index 0000000..e5515a8 --- /dev/null +++ b/kttsd/compat/icons/Makefile.am @@ -0,0 +1 @@ +KDE_ICON = AUTO diff --git a/kttsd/compat/icons/cr128-app-kttsd.png b/kttsd/compat/icons/cr128-app-kttsd.png Binary files differnew file mode 100644 index 0000000..0efd8c9 --- /dev/null +++ b/kttsd/compat/icons/cr128-app-kttsd.png diff --git a/kttsd/compat/icons/cr16-app-kttsd.png b/kttsd/compat/icons/cr16-app-kttsd.png Binary files differnew file mode 100644 index 0000000..3be5160 --- /dev/null +++ b/kttsd/compat/icons/cr16-app-kttsd.png diff --git a/kttsd/compat/icons/cr22-app-kttsd.png b/kttsd/compat/icons/cr22-app-kttsd.png Binary files differnew file mode 100644 index 0000000..8372208 --- /dev/null +++ b/kttsd/compat/icons/cr22-app-kttsd.png diff --git a/kttsd/compat/icons/cr32-app-kttsd.png b/kttsd/compat/icons/cr32-app-kttsd.png Binary files differnew file mode 100644 index 0000000..dde2333 --- /dev/null +++ b/kttsd/compat/icons/cr32-app-kttsd.png diff --git a/kttsd/compat/icons/cr48-app-kttsd.png b/kttsd/compat/icons/cr48-app-kttsd.png Binary files differnew file mode 100644 index 0000000..9b14757 --- /dev/null +++ b/kttsd/compat/icons/cr48-app-kttsd.png diff --git a/kttsd/compat/icons/cr64-app-kttsd.png b/kttsd/compat/icons/cr64-app-kttsd.png Binary files differnew file mode 100644 index 0000000..9cbc325 --- /dev/null +++ b/kttsd/compat/icons/cr64-app-kttsd.png diff --git a/kttsd/compat/icons/crsc-app-kttsd.svgz b/kttsd/compat/icons/crsc-app-kttsd.svgz Binary files differnew file mode 100644 index 0000000..199484f --- /dev/null +++ b/kttsd/compat/icons/crsc-app-kttsd.svgz diff --git a/kttsd/compat/interfaces/Makefile.am b/kttsd/compat/interfaces/Makefile.am new file mode 100644 index 0000000..54cb7de --- /dev/null +++ b/kttsd/compat/interfaces/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = $(AUTODIRS) diff --git a/kttsd/compat/interfaces/kspeech/Makefile.am b/kttsd/compat/interfaces/kspeech/Makefile.am new file mode 100644 index 0000000..d7b5aaa --- /dev/null +++ b/kttsd/compat/interfaces/kspeech/Makefile.am @@ -0,0 +1,8 @@ +# This is necessary for "make bcheck" to work. +INCLUDES = $(all_includes) + +# Header files to be installed. +include_HEADERS = kspeech.h kspeechsink.h + +# Define DCOP/Text-to-Speech Service Type. +kde_servicetypes_DATA = dcoptexttospeech.desktop diff --git a/kttsd/compat/interfaces/kspeech/dcoptexttospeech.desktop b/kttsd/compat/interfaces/kspeech/dcoptexttospeech.desktop new file mode 100644 index 0000000..637cb7e --- /dev/null +++ b/kttsd/compat/interfaces/kspeech/dcoptexttospeech.desktop @@ -0,0 +1,52 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=DCOP/Text-to-Speech +Comment=Text-to-Speech Service with a DCOP interface +Comment[bg]=Модул за синтез на глас с интерфейс DCOP +Comment[bs]=Servis za čitanje teksta za DCOP interfejsom +Comment[ca]=Servei de text a veu amb una interfície DCOP +Comment[cs]=Služba hlasové syntézy s DCOP rozhraním +Comment[cy]=Gwasanaeth Testun-i-Lafar efo rhyngwyneb DCOP +Comment[da]=Tekst-til-tale tjeneste med en DCOP grænseflade +Comment[de]=Sprachausgabedienst mit DCOP-Schnittstelle +Comment[el]=Υπηρεσία κειμένου-σε-ομιλία με διασύνδεση DCOP +Comment[es]=Servicio de texto a voz con interfaz DCOP +Comment[et]=Teksti kõneks muutmise teenus DCOP-liidesega +Comment[eu]=DCOP interfazedun testutik hizketarako zerbitzua +Comment[fa]=خدمت متن به گفتار با یک واسط DCOP +Comment[fi]=Teksti puheeksi -palvelu DCOP -liittymällä +Comment[fr]=Service de synthèse vocale avec une interface DCOP +Comment[ga]=Seirbhís Téacs-go-Caint le comhéadan DCOP +Comment[gl]= Servizo de Texto-para-Fala con interface DCOP +Comment[he]=שירות טקסט לדיבור עם ממשק DCOP +Comment[hu]=Szövegfelolvasó szolgáltatás DCOP-felülettel +Comment[is]=Texti-í-tal þjónusta með DCOP viðmóti +Comment[it]=Servizio di pronuncia con interfaccia DCOP +Comment[ja]=DCOP インターフェースによるテキスト読み上げサービス +Comment[ka]=ტექსტი-სიტყვა სერვისი DCOP ინტერფეისით +Comment[km]=សេវាអត្ថបទដែលត្រូវនិយាយជាមួយនឹងចំណុចប្រទាក់ DCOP +Comment[lt]=Teksto vertimo kalba tarnyba su DCOP sąsaja +Comment[mk]=Сервис за текст-во-говор со DCOP-интерфејс +Comment[ms]=Servis Teks-ke-Tutur dengan antara muka DCOP +Comment[nb]=Tjeneste for tekst-til-tale med DCOP-grensesnitt +Comment[nds]=Vörleesdeenst mit DCOP-Koppelsteed +Comment[ne]=DCOP इन्टरफेससँग पाठ वाचक सेवा +Comment[nl]=Tekst-tot-spraak-dienst met een DCOP-interface +Comment[pl]=Usługa odczytywania tekstu z interfejsem DCOP +Comment[pt]=Servidor Texto-para-Voz com interface DCOP +Comment[pt_BR]=Serviço de Fala de Textos com uma interface DCOP +Comment[ru]=Сервис синтеза речи через интерфейс DCOP +Comment[rw]=Serivise Umwandiko-ku-Kuvuga ifite imigaragarire DCOP +Comment[sk]=Text-na-reč služba pre DCOP +Comment[sl]=Storitev besedila v govor z vmesnikom DCOP +Comment[sr]=Услуга изговарања текста са DCOP интерфејсом +Comment[sr@Latn]=Usluga izgovaranja teksta sa DCOP interfejsom +Comment[sv]=Text-till-tal tjänst med DCOP-gränssnitt +Comment[ta]=ஒரு DCOP இடைமுகத்தில் உரையில் இருந்து பேச்சு சேவை +Comment[tg]=Бартарафсозии таҳлили овоз ба воситаи интерфейси DCOP +Comment[th]=บริการแปลงข้อความเป็นคำพูดผ่านทาง DCOP +Comment[tr]=DCOP arayüzü ile Metinden Konuşmaya Servisi +Comment[uk]=Служба синтезу мовлення з тексту із інтерфейсом DCOP +Comment[vi]=Dịch vụ chuyển Văn bản thành Tiếng nói cho giao diện DCOP +Comment[zh_CN]=带 DCOP 接口的文本到语音服务 +Comment[zh_TW]=有 DCOP 介面的文字轉語音服務 diff --git a/kttsd/compat/interfaces/kspeech/kspeech.h b/kttsd/compat/interfaces/kspeech/kspeech.h new file mode 100644 index 0000000..e4a831c --- /dev/null +++ b/kttsd/compat/interfaces/kspeech/kspeech.h @@ -0,0 +1,1285 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + kspeech.h + KTTSD DCOP Interface + -------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + ******************************************************************************/ + +/*************************************************************************** + * * + * 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 _KSPEECH_H_ +#define _KSPEECH_H_ + +#include <dcopobject.h> +#include <qstringlist.h> + +/** + * @interface KSpeech + * + * kspeech - the KDE Text-to-Speech API. + * + * @version 1.0 Draft 10 + * + * @since KDE 3.4 + * + * This class defines the DCOP interface for applications desiring to speak text. + * Applications may speak text by sending DCOP messages to application "kttsd" object "KSpeech". + * + * %KTTSD -- the KDE Text-to-Speech Deamon -- is the program that supplies the services + * in the KDE Text-to-Speech API. + * + * @warning The KSpeech interface is still being developed and is likely to change in the future. + * + * @section Features + * + * - Priority system for Screen Readers, warnings and messages, while still playing + * regular texts. + * - Long text is parsed into sentences. User may backup by sentence or part, + * replay, pause, and stop playing. + * - Handles multiple speaking applications. Text messages are treated like print jobs. + * Jobs may be created, started, stopped, paused, resumed, and deleted. + * - Speak contents of clipboard. + * - Speak KDE notifications. + * - Plugin-based text job filtering permits substitution for misspoken words, + * abbreviations, etc., transformation of XML or XHTML to SSML, and automatic + * choice of appropriate synthesis engine. + * + * @section Requirements + * + * You may build any KDE application to use KSpeech, since the interface is in kdelibs, but + * the kdeaccessibility package must be installed for KTTS to function. + * + * You will need a speech synthesis engine, such as Festival. See the KTTS Handbook + * for the latest information on installing and configuring speech engines and voices + * with KTTS. + * + * @section goals Design Goals + * + * The KDE Text-to-Speech API is designed with the following goals: + * + * - Support the features enumerated above. + * - Plugin-based architecture for support of a wide variety of speech synthesis + * engines and drivers. + * - Permit generation of speech from the command line (or via shell scripts) + * using the KDE DCOP utilities. + * - Provide a lightweight and easily usable interface for applications to + * generate speech output. + * - Applications need not be concerned about contention over the speech device. + * - Provide limited support for speech markup languages, such as Sable, + * Java %Speech Markup Language (JSML), and %Speech Markup Meta-language (SMML). + * - Provide limited support for embedded speech markers. + * - Asynchronous to prevent system blocking. + * - Plugin-based audio architecture. Currently supports aRts but will support + * additional audio engines in the future, such as gstreamer. + * - Compatible with original %KTTSD API as developed by José Pablo Ezequiel + * "Pupeno" Fernández (avoid breaking existing applications). + * + * Architecturally, applications interface with %KTTSD, which performs queueing, + * speech job managment, plugin management and sentence parsing. %KTTSD interfaces with a + * %KTTSD speech plugin(s), which then interfaces with the speech engine(s) or driver(s). + * + @verbatim + application + ^ + | via DCOP (the KDE Text-to-Speech API) + v + kttsd + ^ + | KTTSD plugin API + v + kttsd plugin + ^ + | + v + speech engine + @endverbatim + * + * The %KTTSD Plugin API is documented in PluginConf in the kdeaccessibility module. + * + * There is a separate GUI application, called kttsmgr, for providing %KTTSD + * configuration and job management. + * + * kttsd maintains 4 types of speech output: + * - Screen Reader Output + * - Warnings + * - Messages + * - Text Jobs + * + * Method sayScreenReaderOutput speaks Screen Reader output. + * It pre-empts any other speech in progress, + * including other Screen Reader outputs, i.e., it is not a queue. + * This method is reserved for use by Screen Readers. + * + * Methods sayWarning and sayMessage place messages into the Warnings and + * Messages queues respectively. Warnings take priority over messages, which take priority + * over text jobs. Warnings and messages are spoken when the currently-speaking + * sentence of a text job is finished. + * + * setText places text into the text job queue. startText begins speaking jobs. + * When one job finishes, the next job begins. Method appendText adds + * additional parts to a text job. Within a text job, the application (and user + * via the kttsmgr GUI), may back up or advance by sentence or part, or rewind + * to the beginning. + * See jumpToTextPart and moveRelTextSentence. + * Text jobs may be paused, stopped, and resumed or deleted from the queue. + * See pauseText, stopText, resumeText, and removeText. + * + * @section cmdline DCOP Command-line Interface + * + * To create a text job to be spoken + * + @verbatim + dcop kttsd KSpeech setText <text> <talker> + @endverbatim + * + * where \<text\> is the text to be spoken, and \<talker\> is usually a language code + * such as "en", "cy", etc. + * + * Example. + * + @verbatim + dcop kttsd KSpeech setText "This is a test." "en" + @endverbatim + * + * To start speaking the text. + * + @verbatim + dcop kttsd KSpeech startText 0 + @endverbatim + * + * You can combine the setText and startText commands into a single command. + * + @verbatim + dcop kttsd KSpeech sayText <text> <talker> + @endverbatim + * + * @since KDE 3.5 + * + * To stop speaking and rewind to the beginning of the text. + * + @verbatim + dcop kttsd KSpeech stopText 0 + @endverbatim + * + * Depending upon the speech plugin used, speaking may not immediately stop. + * + * To stop and remove a text job. + * + @verbatim + dcop kttsd KSpeech removeText 0 + @endverbatim + * + * Note: For more information about talker codes, see talkers below. + * + * @section programming Calling KTTSD from a Program + * + * There are two methods of making DCOP calls from your application to %KTTSD. + * + * - Manually code them using dcopClient object. See kdebase/konqueror/kttsplugin/khtmlkttsd.cpp + * for an example. This method is recommended if you want to make a few simple calls to KTTSD. + * - Use kspeech_stub as described below. This method generates the marshalling code for you + * and is recommended for a more complex speech-enabled applications. kcmkttsmgr in the + * kdeaccessibility module is an example that uses this method. + * + * To make DCOP calls from your program using kspeech_stub, follow these steps: + * + * 1. Include kspeech_stub.h in your code. Derive an object from the KSpeech_stub interface. + * For example, suppose you are developing a KPart and want to call %KTTSD. + * Your class declaration might look like this: + * + @verbatim + #include <kspeech_stub.h> + class MyPart: public KParts::ReadOnlyPart, public KSpeech_stub { + @endverbatim + * + * 2. In your class constructor, initialize DCOPStub, giving it the sender + * "kttsd", object "KSpeech". + * + @verbatim + MyPart::MyPart(QWidget *parent, const char *name) : + KParts::ReadOnlyPart(parent, name), + DCOPStub("kttsd", "KSpeech") { + @endverbatim + * + * 3. See if KTTSD is running, and if not, start it. + * + @verbatim + DCOPClient *client = dcopClient(); + client->attach(); + if (!client->isApplicationRegistered("kttsd")) { + QString error; + if (KApplication::startServiceByDesktopName("kttsd", QStringList(), &error)) + cout << "Starting KTTSD failed with message " << error << endl; + } + @endverbatim + * + * If you want to detect if KTTSD is installed without starting it, use this code. + * + @verbatim + KTrader::OfferList offers = KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'"); + if (offers.count() > 0) + { + // KTTSD is installed. + } + @endverbatim + * + * Typically, you would do this to hide a menu item or button if KTTSD is not installed. + * + * 4. Make calls to KTTSD in your code. + * + @verbatim + uint jobNum = setText("Hello World", "en"); + startText(jobNum); + @endverbatim + * + * 4. Add kspeech_DIR and kspeech.stub to your Makefile.am. Example: + * + @verbatim + kspeech_DIR = $(kde_includes) + libmypart_la_SOURCES = kspeech.stub + @endverbatim + * + * @section signals Signals Emitted by KTTSD + * + * %KTTSD emits a number of DCOP signals, which provide information about sentences spoken, + * text jobs started, stopped, paused, resumed, finished, or deleted and markers seen. + * In general, these signals are broadcast to any application that connects to them. + * Applications should check the appId argument to determine whether the signal belongs to + * them or not. + * + * To receive %KTTSD DCOP signals, follow these steps: + * + * 1. Include kspeechsink.h in your code. Derive an object from the KSpeechSink interface + * and declare a method for each signal you'd like to receive. For example, + * if you were coding a KPart and wanted to receive the KTTSD signal sentenceStarted: + * + @verbatim + #include <kspeechsink.h> + class MyPart: + public KParts::ReadOnlyPart, + virtual public KSpeechSink + { + protected: + ASYNC sentenceStarted(const QCString& appId, const uint jobNum, const uint seq); + @endverbatim + * + * You can combine sending and receiving in one object. + * + @verbatim + #include <kspeechsink.h> + class MyPart: + public KParts::ReadOnlyPart, + public KSpeech_stub, + virtual public KSpeechSink + { + protected: + ASYNC sentenceStarted(const QCString& appId, const uint jobNum, const uint seq); + @endverbatim + * + * See below for the signals you can declare. + * + * 2. In your class constructor, initialize DCOPObject with the name of your DCOP + * receiving object. + * + @verbatim + MyPart::MyPart(QWidget *parent, const char *name) : + KParts::ReadOnlyPart(parent, name), + DCOPObject("mypart_kspeechsink") { + @endverbatim + * + * Use any name you like. + * + * 3. Where appropriate (usually in your constructor), make sure your DCOPClient + * is registered and connect the %KTTSD DCOP signals to your declared receiving + * methods. + * + @verbatim + // Register DCOP client. + DCOPClient *client = kapp->dcopClient(); + if (!client->isRegistered()) + { + client->attach(); + client->registerAs(kapp->name()); + } + // Connect KTTSD DCOP signals to our slots. + connectDCOPSignal("kttsd", "KSpeech", + "sentenceStarted(QCString,uint,uint)", + "sentenceStarted(QCString,uint,uint)", + false); + @endverbatim + * + * Notice that the argument signatures differ slightly from the actual declarations. For + * example + * + @verbatim + ASYNC sentenceStarted(const QCString& appId, const uint jobNum, const uint seq); + @endverbatim + * + * becomes + * + @verbatim + "sentenceStarted(QCString,uint,uint)", + @endverbatim + * + * in the connectDCOPSignal call. + * + * 4. Write the definition for the received signal. Be sure to check whether the signal + * is intended for your application. + * + @verbatim + ASYNC MyPart::sentenceStarted(const QCString& appId, const uint jobNum, const uint seq) + { + // Check appId to determine if this is our signal. + if (appId != dcopClient()->appId()) return; + // Do something here. + } + @endverbatim + * + * 5. Add kspeechsink_DIR and kspeechsink.skel to your Makefile.am. Example for an app + * both sending and receiving. + * + @verbatim + kspeech_DIR = $(kde_includes) + kspeechsink_DIR = $(kde_includes) + libmypart_la_SOURCES = kspeech.stub kspeechsink.skel + @endverbatim + * + * @section talkers Talkers, Talker Codes, and Plugins + * + * Many of the methods permit you to specify a desired "talker". This + * may be a simple language code, such as "en" for English, "es" for Spanish, etc. + * Code as NULL to use the default configured talker. + * + * Within KTTSMGR, the user has the ability to configure more than one talker for each language, + * with different voices, genders, volumes, and talking speeds. + * + * Talker codes serve two functions: + * - They identify configured plugins, and + * - They provide a way for applications to specify the desired speaking attributes + * that influence the choice of plugin to speak text. + * + * A Talker Code consists of a series of XML tags and attributes. + * An example of a full Talker Code with all attributes specified is + * + * <voice lang="en" name="kal" gender="male"/> + * <prosody volume="soft" rate="fast"/> + * <kttsd synthesizer="Festival" /> + * + * (The @e voice and @e prosody tags are adapted from the W3C Speech Synthesis + * Markup Language (SSML) and Java Speech Markup Language (JSML). + * The @e kttsd tag is an extension to the SMML and JSML languages to support + * named synthesizers and text encodings.) + * %KTTS doesn't really care about the @e voice, @e prosody, and @e kttsd tags. In fact, + * they may be omitted and just the attributes specified. The example above then + * becomes + * + * lang="en" name="kal" gender="male" volume="soft" rate="fast" + * synthesizer="Festival" + * + * The attributes may be specified in any order. + * + * For clarity, the rest of the discussion + * will omit the @e voice, @e prosody, and @e kttsd tags. + * + * The attributes that make up a talker code are: + * + * - @e lang. Language code and optional country code. + * Examples: en, es, en_US, en_GB. Codes + * are case in-sensitive and hyphen (-) or underscore (_) may be + * used to separate the country code from the language code. + * - @e synthesizer. The name of the synthesizer (plugin) used to produce the speech. + * - @e gender. May be either "male", "female", or "neutral". + * - @e name. The name of the voice code. + * The choice of voice codes is synthesizer-specific. + * - @e volume. May be "loud", "medium", or "quiet". A synonym for "quiet" is + * "soft". + * - @e rate. May be "fast", "medium", or "slow". + * + * Each plugin, once it has been configured by a user in kttsmgr, returns a + * fully-specified talker code to identify itself. If the plugin supports it, + * the user may configure another instance of the plugin with a different set + * of attributes. This is the difference between a "plugin" and a "talker". + * A talker is a configured instance of a plugin. Each plugin (if it supports it) + * may be configured as multiple talkers. + * + * When the user configures %KTTSD, she configures one or more talkers and then + * places them in preferred order, top to bottom in kttsmgr. In effect, + * she specifies her preferences for each of the talkers. + * + * When applications specify a talker code, they need not (and typically do not) + * give a full specification. An example of a talker code with only some of the + * attributes specified might be + * + * lang="en" gender="female" + * + * If the talker code is not in XML attribute format, it assumed to be a @e lang + * attribute. So the talker code + * + * en + * + * is interpreted as + * + * lang="en" + * + * When a program requests a talker code in calls to setText, appendText, + * sayMessage, sayWarning, and sayScreenReaderOutput, + * %KTTSD tries to match the requested talker code to the closest matching + * configured talker. + * + * The @e lang attribute has highest priority (attempting to speak English with + * a Spanish synthesizer would likely be unintelligible). So the language + * attribute is said to have "priority". + * If an application does not specify a language attribute, a default one will be assumed. + * The rest of the attributes are said to be "preferred". If %KTTSD cannot find + * a talker with the exact preferred attributes requested, the closest matching + * talker will likely still be understandable. + * + * An application may specify that one or more of the attributes it gives in a talker + * code have priority by preceeding each priority attribute with an asterisk. + * For example, the following talker code + * + * lang="en" gender="*female" volume="soft" + * + * means that the application wants to use a talker that supports American English language + * and Female gender. If there is more than one such talker, one that supports + * Soft volume would be preferred. Notice that a talker configured as English, Male, + * and Soft volume would not be picked as long as an English Female talker is + * available. + * + * The algorithm used by %KTTSD to find a matching talker is as follows: + * + * - If language code is not specified by the application, assume default configured + * by user. The primary language code automatically has priority. + * - (Note: This is not yet implemented.) + * If there are no talkers configured in the language, %KTTSD will attempt + * to automatically configure one (see automatic configuraton discussion below) + * - The talker that matches on the most priority attributes wins. + * - If a tie, the one that matches on the most preferred attributes wins. + * - If there is still a tie, the one nearest the top of the kttsmgr display + * (first configured) will be chosen. + * + * Language codes actually consist of two parts, a language code and an optional + * country code. For example, en_GB is English (United Kingdom). The language code is + * treated as a priority attribute, but the country code (if specified) is treated + * as preferred. So for example, if an application requests the following + * talker code + * + * lang="en_GB" gender="male" volume="medium" + * + * then a talker configured as lang="en" gender="male" volume="medium" would be + * picked over one configured as lang="en_GB" gender="female" volume="soft", + * since the former matches on two preferred attributes and the latter only on the + * preferred attribute GB. An application can override this and make the country + * code priority with an asterisk. For example, + * + * lang="*en_GB" gender="male" volume="medium" + * + * To specify that American English is priority, put an asterisk in front of + * en_US, like this. + * + * lang="*en_US" gender="male" volume="medium" + * + * Here the application is indicating that a talker that speaks American English + * has priorty over one that speaks a different form of English. + * + * (Note: Not yet implemented). + * If a language code is specified, and no plugin is currently configured + * with a matching language code, %KTTSD will attempt to automatically + * load and configure a plugin to support the requested language. If + * there is no such plugin, or there is a plugin but it cannot automatically + * configure itself, %KTTSD will pick one of the configured plugins using the + * algorithm given above. + * + * Notice that %KTTSD will always pick a talker, even if it is a terrible match. + * (The principle is that something heard is better than nothing at all. If + * it sounds terrible, user will change his configuration.) + * If an attribute is absolutely mandatory -- in other words the application + * must speak with the attribute or not at all -- the application can determine if + * there are any talkers configured with the attribute by calling getTalkers, + * and if there are none, display an error message to the user. + * + * Applications can implement their own talker-matching algorithm by + * calling getTalkers, then finding the desired talker from the returned + * list. When the full talker code is passed in, %KKTSD will find an exact + * match and use the specified talker. + * + * If an application requires a configuration that user has not created, + * it should display a message to user instructing them to run kttsmgr and + * configure the desired talker. (This must be done interactively because + * plugins often need user assistance locating voice files, etc.) + * + * The above scheme is designed to balance the needs + * of applications against user preferences. Applications are given the control + * they @e might need, without unnecessarily burdening the application author. + * If you are an application author, the above discussion might seem overly + * complicated. It isn't really all that complicated. Here are rules of thumb: + * + * - It is legitimate to give a NULL (0) talker code, in which case, the user's default + * talker will be used. + * - If you know the language code, give that in the talker code, otherwise + * leave it out. + * - If there is an attribute your application @e requires for proper functioning, + * specify that with an asterisk in front of it. For example, your app might + * speak in two different voices, Male and Female. (Since your + * app requires both genders, call getTalkers to determine if both genders + * are available, and if not, advise user to configure them. Better yet, + * give the user a choice of available distinquishing attributes + * (loud/soft, fast/slow, etc.) + * - If there are other attributes you would prefer, specify those without an + * asterisk, but leave them out if it doesn't really make any difference + * to proper functioning of your application. Let the user decide them + * when they configure %KTTS. + * + * One final note about talkers. %KTTSD does talker matching for each sentence + * spoken, just before the sentence is sent to a plugin for synthesis. Therefore, + * the user can change the effective talker in mid processing of a text job by + * changing his preferences, or even deleting or adding new talkers to the configuration. + * + * @section markup Speech Markup + * + * Note: %Speech Markup is not yet fully implemented in %KTTSD. + * + * Each of the five methods for queueing text to be spoken -- sayScreenReaderOutput, + * setText, appendText, sayMessage, and sayWarning -- may contain speech markup, + * provided that the plugin the user has configured supports that markup. The markup + * languages and plugins currently supported are: + * + * - %Speech Synthesis Markup language (SSML): Festival and Hadifix. + * + * This may change in the future as synthesizers improve. + * + * Before including markup in the text sent to kttsd, the application should + * query whether the currently-configured plugin + * supports the markup language by calling supportsMarkup. + * + * It it does not support the markup, it will be stripped out of the text. + * + * @section markers Support for Markers + * + * Note: Markers are not yet implemented in %KTTSD. + * + * When using a speech markup language, such as Sable, JSML, or SSML, the application may embed + * named markers into the text. If the user's chosen speech plugin supports markers, %KTTSD + * will emit DCOP signal markerSeen when the speech engine encounters the marker. + * Depending upon the speech engine and plugin, this may occur either when the speech engine + * encounters the marker during synthesis from text to speech, or when the speech is actually + * spoken on the audio device. The calling application can call the supportsMarkers + * method to determine if the currently configured plugin supports markers or not. + * + * @section sentenceparsing Sentence Parsing + * + * Not all speech engines provide robust capabilities for stopping synthesis that is in progress. + * To compensate for this, %KTTSD parses text jobs given to it by the setText and + * appendText methods into sentences and sends the sentences to the speech + * plugin one at a time. In this way, should the user wish to stop the speech + * output, they can do so, and the worst that will happen is that the last sentence + * will be completed. This is called Sentence Boundary Detection (SBD). + * + * Sentence Boundary Detection also permits the user to rewind by sentences. + * + * The default sentence delimiter used for plain text is as follows: + * + * - A period (.), question mark (?), exclamation mark (!), colon (:), or + * semi-colon (;) followed by whitespace (including newline), or + * - Two newlines in a row separated by optional whitespace, or + * - The end of the text. + * + * When given text containing speech markup, %KTTSD automatically determines the markup type + * and parses based on the sentence semantics of the markup language. + * + * An application may change the sentence delimiter by calling setSentenceDelimiter + * prior to calling setText. Changing the delimiter does not affect other + * applications. + * + * Text given to %KTTSD via the sayWarning, sayMessage, and sayScreenReaderOutput + * methods is @e not parsed into sentences. For this reason, applications + * should @e not send long messages with these methods. + * + * Sentence Boundary Detection is implemented as a plugin SBD filter. See + * filters for more information. + * + * @section filters Filters + * + * Users may specify filters in the kttsmgr GUI. Filters are plugins that modify the text + * to be spoken or change other characteristics of jobs. Currently, the following filter plugins + * are available: + * + * - String Replacer. Permits users to substitute for mispoken words, or vocalize chat + * emoticons. + * - XML Transformer. Given a particular XML or XHTML format, permits conversion of the + * XML to SSML (Speech Synthesis Markup Language) using XSLT (XML Style Language - Transforms) + * stylesheets. + * - Talker Chooser. Permits users to redirect jobs from one configured Talker to another + * based on the contents of the job or application that sent it. + * + * Additional plugins may be available in the future. + * + * In additional to these regular filters, KTTS also implements Sentence Boundary Detection (SBD) + * as a plugin filter. See sentenceparsing for more information. + * + * Regular filters are applied to Warnings, Messages, and Text jobs. SBD filters are + * only applied to regular Text jobs; they are not applied to Warnings and Messages. Screen + * Reader Outputs are never filtered. + * + * @section authors Authors + * + * @author José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + * @author Gary Cramblitt <garycramblitt@comcast.net> + * @author Olaf Schmidt <ojschmidt@kde.org> + * @author Gunnar Schmi Dt <gunnar@schmi-dt.de> + */ + +// NOTE: kspeech class is now obsolete. Please use KSpeech instead. + +class KSpeech : virtual public DCOPObject { + K_DCOP + + public: + /** + * @enum kttsdJobState + * Job states returned by method getTextJobState. + */ + enum kttsdJobState + { + jsQueued = 0, /**< Job has been queued but is not yet speakable. */ + jsSpeakable = 1, /**< Job is speakable, but is not speaking. */ + jsSpeaking = 2, /**< Job is currently speaking. */ + jsPaused = 3, /**< Job has been paused. */ + jsFinished = 4 /**< Job is finished and is deleteable. */ + }; + + /** + * @enum kttsdMarkupType + * %Speech markup language types. + */ + enum kttsdMarkupType + { + mtPlain = 0, /**< Plain text */ + mtJsml = 1, /**< Java %Speech Markup Language */ + mtSsml = 2, /**< %Speech Synthesis Markup Language */ + mtSable = 3, /**< Sable 2.0 */ + mtHtml = 4 /**< HTML @since 3.5 */ + }; + + k_dcop: + /** @name DCOP Methods */ + //@{ + + /** + * Determine whether the currently-configured speech plugin supports a speech markup language. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * @param markupType The kttsd code for the desired speech markup language. + * @return True if the plugin currently configured for the indicated + * talker supports the indicated speech markup language. + * @see kttsdMarkupType + */ + virtual bool supportsMarkup(const QString &talker, uint markupType = 0) const = 0; + + /** + * Determine whether the currently-configured speech plugin supports markers in speech markup. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * @return True if the plugin currently configured for the indicated + * talker supports markers. + */ + virtual bool supportsMarkers(const QString &talker) const = 0; + + /** + * Say a message as soon as possible, interrupting any other speech in progress. + * IMPORTANT: This method is reserved for use by Screen Readers and should not be used + * by any other applications. + * @param msg The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * + * If an existing Screen Reader output is in progress, it is stopped and discarded and + * replaced with this new message. + */ + virtual ASYNC sayScreenReaderOutput(const QString &msg, const QString &talker) = 0; + + /** + * Say a warning. The warning will be spoken when the current sentence + * stops speaking and takes precedence over Messages and regular text. Warnings should only + * be used for high-priority messages requiring immediate user attention, such as + * "WARNING. CPU is overheating." + * @param warning The warning to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + */ + virtual ASYNC sayWarning(const QString &warning, const QString &talker) = 0; + + /** + * Say a message. The message will be spoken when the current sentence stops speaking + * but after any warnings have been spoken. + * Messages should be used for one-shot messages that can't wait for + * normal text messages to stop speaking, such as "You have mail.". + * @param message The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no talker has been configured for the specified talker code, + * defaults to the closest matching talker. + */ + virtual ASYNC sayMessage(const QString &message, const QString &talker) = 0; + + /** + * Sets the GREP pattern that will be used as the sentence delimiter. + * @param delimiter A valid GREP pattern. + * + * The default sentence delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n)) + @endverbatim + * + * Note that backward slashes must be escaped. + * When %KTTSD parses the text, it replaces all tabs, spaces, and formfeeds + * with a single space, and then replaces the sentence delimiters using + * the following statement: + @verbatim + QString::replace(sentenceDelimiter, "\\1\t"); + @endverbatim + * + * which replaces all sentence delimiters with a tab, but + * preserving the first capture text (first parenthesis). In other + * words, the sentence punctuation is preserved. + * The tab is later used to separate the text into sentences. + * + * Changing the sentence delimiter does not affect other applications. + * + * @see sentenceparsing + */ + virtual ASYNC setSentenceDelimiter(const QString &delimiter) = 0; + + /** + * Queue a text job. Does not start speaking the text. + * @param text The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default plugin. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @return Job number. + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call setSentenceDelimiter to change the sentence delimiter prior to + * calling setText. + * Call getTextCount to retrieve the sentence count after calling setText. + * + * The text may contain speech mark language, such as Sable, JSML, or SSML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + * + * Call startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * + * @see getTextCount + * @see startText + */ + virtual uint setText(const QString &text, const QString &talker) = 0; + + /** + * Say a plain text job. This is a convenience method that + * combines setText and startText into a single call. + * @param text The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default plugin. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @return Job number. + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call setSentenceDelimiter to change the sentence delimiter prior to + * calling setText. + * Call getTextCount to retrieve the sentence count after calling setText. + * + * The text may contain speech mark language, such as Sable, JSML, or SSML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + * + * The job is marked speakable. + * If there are other speakable jobs preceeding this one in the queue, + * those jobs continue speaking and when finished, this job will begin speaking. + * If there are no other speakable jobs preceeding this one, it begins speaking. + * + * @see getTextCount + * + * @since KDE 3.5 + */ + virtual uint sayText(const QString &text, const QString &talker) = 0; + + /** + * Adds another part to a text job. Does not start speaking the text. + * @param text The message to be spoken. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return Part number for the added part. Parts are numbered starting at 1. + * + * The text is parsed into individual sentences. Call getTextCount to retrieve + * the sentence count. Call startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * + * @see setText. + * @see startText. + */ + virtual int appendText(const QString &text, uint jobNum=0) = 0; + + /** + * Queue a text job from the contents of a file. Does not start speaking the text. + * @param filename Full path to the file to be spoken. May be a URL. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @param encoding Name of the encoding to use when reading the file. If + * NULL or Empty, uses default stream encoding. + * @return Job number. 0 if an error occurs. + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call setSentenceDelimiter to change the sentence delimiter prior to calling setText. + * Call getTextCount to retrieve the sentence count after calling setText. + * + * The text may contain speech mark language, such as Sable, JSML, or SSML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + * + * Call startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * + * @see getTextCount + * @see startText + */ + virtual uint setFile(const QString &filename, const QString &talker, + const QString& encoding) = 0; + + /** + * Get the number of sentences in a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return The number of sentences in the job. -1 if no such job. + * + * The sentences of a job are given sequence numbers from 1 to the number returned by this + * method. The sequence numbers are emitted in the sentenceStarted and + * sentenceFinished signals. + */ + virtual int getTextCount(uint jobNum=0) = 0; + + /** + * Get the job number of the current text job. + * @return Job number of the current text job. 0 if no jobs. + * + * Note that the current job may not be speaking. See isSpeakingText. + * + * @see getTextJobState. + * @see isSpeakingText + */ + virtual uint getCurrentTextJob() = 0; + + /** + * Get the number of jobs in the text job queue. + * @return Number of text jobs in the queue. 0 if none. + */ + virtual uint getTextJobCount() = 0; + + /** + * Get a comma-separated list of text job numbers in the queue. + * @return Comma-separated list of text job numbers in the queue. + */ + virtual QString getTextJobNumbers() = 0; + + /** + * Get the state of a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return State of the job. -1 if invalid job number. + * + * @see kttsdJobState + */ + virtual int getTextJobState(uint jobNum=0) = 0; + + /** + * Get information about a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return A QDataStream containing information about the job. + * Blank if no such job. + * + * The stream contains the following elements: + * - int state - Job state. + * - QCString appId - DCOP senderId of the application that requested the speech job. + * - QString talker - Talker Code requested by application. + * - int seq - Current sentence being spoken. Sentences are numbered starting at 1. + * - int sentenceCount - Total number of sentences in the job. + * - int partNum - Current part of the job begin spoken. Parts are numbered starting at 1. + * - int partCount - Total number of parts in the job. + * + * Note that sequence numbers apply to the entire job. They do not start from 1 at the beginning of + * each part. + * + * The following sample code will decode the stream: + @code + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talker; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + @endcode + */ + virtual QByteArray getTextJobInfo(uint jobNum=0) = 0; + + /** + * Given a Talker Code, returns the Talker ID of the talker that would speak + * a text job with that Talker Code. + * @param talkerCode Talker Code. + * @return Talker ID of the talker that would speak the text job. + */ + virtual QString talkerCodeToTalkerId(const QString& talkerCode) = 0; + + /** + * Return a sentence of a job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @param seq Sequence number of the sentence. + * @return The specified sentence in the specified job. If no such + * job or sentence, returns "". + */ + virtual QString getTextJobSentence(uint jobNum=0, uint seq=0) = 0; + + /** + * Determine if kttsd is currently speaking any text jobs. + * @return True if currently speaking any text jobs. + */ + virtual bool isSpeakingText() const = 0; + + /** + * Remove a text job from the queue. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is deleted from the queue and the textRemoved signal is emitted. + * + * If there is another job in the text queue, and it is marked speakable, + * that job begins speaking. + */ + virtual ASYNC removeText(uint jobNum=0) = 0; + + /** + * Start a text job at the beginning. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * Rewinds the job to the beginning. + * + * The job is marked speakable. + * If there are other speakable jobs preceeding this one in the queue, + * those jobs continue speaking and when finished, this job will begin speaking. + * If there are no other speakable jobs preceeding this one, it begins speaking. + * + * The textStarted signal is emitted when the text job begins speaking. + * When all the sentences of the job have been spoken, the job is marked for deletion from + * the text queue and the textFinished signal is emitted. + */ + virtual ASYNC startText(uint jobNum=0) = 0; + + /** + * Stop a text job and rewind to the beginning. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is marked not speakable and will not be speakable until startText + * or resumeText is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * + * If the job is currently speaking, the textStopped signal is emitted, + * the job stops speaking, and if the next job in the queue is speakable, it + * begins speaking. + * + * Depending upon the speech engine and plugin used, speech may not stop immediately + * (it might finish the current sentence). + */ + virtual ASYNC stopText(uint jobNum=0) = 0; + + /** + * Pause a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is marked as paused and will not be speakable until resumeText or + * startText is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * + * If the job is currently speaking, the textPaused signal is emitted and the job + * stops speaking. Note that if the next job in the queue is speakable, it does + * not start speaking as long as this job is paused. + * + * Depending upon the speech engine and plugin used, speech may not stop immediately + * (it might finish the current sentence). + * + * @see resumeText + */ + virtual ASYNC pauseText(uint jobNum=0) = 0; + + /** + * Start or resume a text job where it was paused. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is marked speakable. + * + * If the job is currently speaking, or is waiting to be spoken (speakable + * state), the resumeText() call is ignored. + * + * If the job is currently queued, or is finished, it is the same as calling + * @see startText . + * + * If there are speaking jobs preceeding this one in the queue, + * those jobs continue speaking and when finished this job will begin + * speaking where it left off. + * + * The textResumed signal is emitted when the job resumes. + * + * @see pauseText + */ + virtual ASYNC resumeText(uint jobNum=0) = 0; + + /** + * Get a list of the talkers configured in KTTS. + * @return A QStringList of fully-specified talker codes, one + * for each talker user has configured. + * + * @see talkers + */ + virtual QStringList getTalkers() = 0; + + /** + * Change the talker for a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @param talker New code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + */ + virtual ASYNC changeTextTalker(const QString &talker, uint jobNum=0 ) = 0; + + /** + * Get the user's default talker. + * @return A fully-specified talker code. + * + * @see talkers + * @see getTalkers + */ + virtual QString userDefaultTalker() = 0; + + /** + * Move a text job down in the queue so that it is spoken later. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * If the job is currently speaking, it is paused. + * If the next job in the queue is speakable, it begins speaking. + */ + virtual ASYNC moveTextLater(uint jobNum=0) = 0; + + /** + * Jump to the first sentence of a specified part of a text job. + * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return Part number of the part actually jumped to. + * + * If partNum is greater than the number of parts in the job, jumps to last part. + * If partNum is 0, does nothing and returns the current part number. + * If no such job, does nothing and returns 0. + * Does not affect the current speaking/not-speaking state of the job. + */ + virtual int jumpToTextPart(int partNum, uint jobNum=0) = 0; + + /** + * Advance or rewind N sentences in a text job. + * @param n Number of sentences to advance (positive) or rewind (negative) in the job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return Sequence number of the sentence actually moved to. Sequence numbers + * are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + */ + virtual uint moveRelTextSentence(int n, uint jobNum=0) = 0; + + /** + * Add the clipboard contents to the text queue and begin speaking it. + */ + virtual ASYNC speakClipboard() = 0; + + /** + * Displays the %KTTS Manager dialog. In this dialog, the user may backup or skip forward in + * any text job by sentence or part, rewind jobs, pause or resume jobs, or + * delete jobs. + */ + virtual void showDialog() = 0; + + /** + * Stop the service. + */ + virtual void kttsdExit() = 0; + + /** + * Re-start %KTTSD. + */ + virtual void reinit() = 0; + + /** + * Return the KTTSD deamon version number. + * @since KDE 3.5 + */ + virtual QString version() = 0; + //@} + + k_dcop_signals: + void ignoreThis(); + + /** @name DCOP Signals */ + //@{ + + /** + * This signal is emitted when KTTSD starts or restarts after a call to reinit. + */ + void kttsdStarted(); + /** + * This signal is emitted just before KTTSD exits. + */ + void kttsdExiting(); + /** + * This signal is emitted when the speech engine/plugin encounters a marker in the text. + * @param appId DCOP application ID of the application that queued the text. + * @param markerName The name of the marker seen. + * + * @see markers + */ + void markerSeen(const QCString& appId, const QString& markerName); + /** + * This signal is emitted whenever a sentence begins speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * + * @see getTextCount + */ + void sentenceStarted(const QCString& appId, uint jobNum, uint seq); + /** + * This signal is emitted when a sentence has finished speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * + * @see getTextCount + */ + void sentenceFinished(const QCString& appId, uint jobNum, uint seq); + + /** + * This signal is emitted whenever a new text job is added to the queue. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textSet(const QCString& appId, uint jobNum); + + /** + * This signal is emitted whenever a new part is appended to a text job. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + * @param partNum Part number of the new part. Parts are numbered starting + * at 1. + */ + void textAppended(const QCString& appId, uint jobNum, int partNum); + + /** + * This signal is emitted whenever speaking of a text job begins. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textStarted(const QCString& appId, uint jobNum); + /** + * This signal is emitted whenever a text job is finished. The job has + * been marked for deletion from the queue and will be deleted when another + * job reaches the Finished state. (Only one job in the text queue may be + * in state Finished at one time.) If startText or resumeText is + * called before the job is deleted, it will remain in the queue for speaking. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textFinished(const QCString& appId, uint jobNum); + /** + * This signal is emitted whenever a speaking text job stops speaking. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + * + * The signal is only emitted if stopText() is called and the job is currently + * speaking. + */ + void textStopped(const QCString& appId, uint jobNum); + /** + * This signal is emitted whenever a speaking text job is paused. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textPaused(const QCString& appId, uint jobNum); + /** + * This signal is emitted when a text job, that was previously paused, resumes speaking. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textResumed(const QCString& appId, uint jobNum); + /** + * This signal is emitted whenever a text job is deleted from the queue. + * The job is no longer in the queue when this signal is emitted. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textRemoved(const QCString& appId, uint jobNum); + //@} +}; + +#endif // _KSPEECH_H_ diff --git a/kttsd/compat/interfaces/kspeech/kspeechsink.h b/kttsd/compat/interfaces/kspeech/kspeechsink.h new file mode 100644 index 0000000..e90a005 --- /dev/null +++ b/kttsd/compat/interfaces/kspeech/kspeechsink.h @@ -0,0 +1,164 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + kspeechsink.h + KTTSD DCOP Signal Sink Interface + -------------------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +/** + * @interface KSpeechSink + * + * KTTSD DCOP Signal Sink + * + * @since KDE 3.4 + * + * This defines the interface to sink signals emitted by KTTSD, the KDE Text-to-speech Deamon. + * The DCOP IDL Compiler generates a skeleton file from this interface definition that will + * marshal the arguments for you. + * + * @section Usage + * + * See the Signals section of kspeech.h for instructions. + * + * @warning The KSpeechSink interface is still being developed and is likely to change in the future. +*/ + +#ifndef _KSPEECHSINK_H_ +#define _KSPEECHSINK_H_ + +#include <dcopobject.h> + +class KSpeechSink : virtual public DCOPObject { + K_DCOP + + public: + /** + * @enum kttsdJobState + * Job states returned by method getTextJobState. + */ + enum kttsdJobState + { + jsQueued = 0, /**< Job has been queued but is not yet speakable. */ + jsSpeakable = 1, /**< Job is speakable, but is not speaking. */ + jsSpeaking = 2, /**< Job is currently speaking. */ + jsPaused = 3, /**< Job has been paused. */ + jsFinished = 4 /**< Job is finished and is deleteable. */ + }; + + /** + * @enum kttsdMarkupType + * %Speech markup language types. + */ + enum kttsdMarkupType + { + mtPlain = 0, /**< Plain text */ + mtJsml = 1, /**< Java %Speech Markup Language */ + mtSmml = 2, /**< %Speech Markup Meta-language */ + mtSable = 3 /**< Sable 2.0 */ + }; + + k_dcop: + /** + * This signal is emitted when KTTSD starts or restarts after a call to reinit. + */ + virtual ASYNC kttsdStarted() { }; + /** + * This signal is emitted just before KTTSD exits. + */ + virtual ASYNC kttsdExiting() { }; + + /** + * This signal is emitted when the speech engine/plugin encounters a marker in the text. + * @param appId DCOP application ID of the application that queued the text. + * @param markerName The name of the marker seen. + * @see markers + */ + virtual ASYNC markerSeen(const QCString& appId, const QString& markerName) { Q_UNUSED(appId); Q_UNUSED(markerName); }; + /** + * This signal is emitted whenever a sentence begins speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * @see getTextCount + */ + virtual ASYNC sentenceStarted(const QCString& appId, uint jobNum, uint seq) { Q_UNUSED(appId); Q_UNUSED(jobNum); Q_UNUSED(seq); }; + /** + * This signal is emitted when a sentence has finished speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * @see getTextCount + */ + virtual ASYNC sentenceFinished(const QCString& appId, uint jobNum, uint seq) { Q_UNUSED(appId); Q_UNUSED(jobNum); Q_UNUSED(seq); }; + + /** + * This signal is emitted whenever a new text job is added to the queue. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textSet(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; + + /** + * This signal is emitted whenever a new part is appended to a text job. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + * @param partNum Part number of the new part. Parts are numbered starting + * at 1. + */ + virtual ASYNC textAppended(const QCString& appId, uint jobNum, int partNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); Q_UNUSED(partNum); }; + + /** + * This signal is emitted whenever speaking of a text job begins. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textStarted(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; + /** + * This signal is emitted whenever a text job is finished. The job has + * been marked for deletion from the queue and will be deleted when another + * job reaches the Finished state. (Only one job in the text queue may be + * in state Finished at one time.) If startText or resumeText is + * called before the job is deleted, it will remain in the queue for speaking. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textFinished(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; + /** + * This signal is emitted whenever a speaking text job stops speaking. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textStopped(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; + /** + * This signal is emitted whenever a speaking text job is paused. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textPaused(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; + /** + * This signal is emitted when a text job, that was previously paused, resumes speaking. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textResumed(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; + /** + * This signal is emitted whenever a text job is deleted from the queue. + * The job is no longer in the queue when this signal is emitted. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + virtual ASYNC textRemoved(const QCString& appId, uint jobNum) { Q_UNUSED(appId); Q_UNUSED(jobNum); }; +}; + +#endif // _KSPEECHSINK_H_ diff --git a/kttsd/configure.in.bot b/kttsd/configure.in.bot new file mode 100644 index 0000000..b24a085 --- /dev/null +++ b/kttsd/configure.in.bot @@ -0,0 +1,65 @@ +if test "x$have_gst" = xno && test "x$with_gstreamer" != xno; then + echo "********************************************************" + echo "*" + echo "* You do not seem to have GStreamer >= 0.8.7 installed." + echo "*" + echo "* KTTSD supports GStreamer output but will also" + echo "* work with aRts, ALSA, and aKode" + echo "*" + echo "* PACKAGERS: Please do not include the gstreamer plugin" + echo "* in binary distributions of kdeaccessibility as it" + echo "* creates unwanted dependencies. Instead, include in a" + echo "* separate package, e.g., kdeaccessibility-extras." + echo "* The separate package should require gstreamer >= 0.8.7" + echo "*" + echo "********************************************************" +fi +if test "x$have_gst" = xyes && test "x$with_gstreamer" = xno; then + echo "********************************************************" + echo "*" + echo "* You seem to have GStreamer >= 0.8.7 installed." + echo "* but you did not choose to build the gstreamer plugin." + echo "* To enable GStreamer plugin, configure with" + echo "*" + echo "* --with-gstreamer" + echo "*" + echo "* KTTSD supports GStreamer output but will also" + echo "* work with aRts, ALSA, and aKode" + echo "*" + echo "* PACKAGERS: Please do not include the gstreamer plugin" + echo "* in binary distributions of kdeaccessibility as it" + echo "* creates unwanted dependencies. Instead, include in a" + echo "* separate package, e.g., kdeaccessibility-extras." + echo "* The separate package should require gstreamer >= 0.8.7" + echo "*" + echo "********************************************************" +fi +if test "x$have_gst" = xyes && test "x$with_gstreamer" != xno; then + echo "********************************************************" + echo "* The build of the GStreamer audio plugin has been enabled." + echo "*" + echo "* PACKAGERS: Please do not include the gstreamer plugin" + echo "* in binary distributions of kdeaccessibility as it" + echo "* creates unwanted dependencies. Instead, include in a" + echo "* separate package, e.g., kdeaccessibility-extras." + echo "* The separate package should require gstreamer >= 0.8.7" + echo "*" + echo "********************************************************" +fi +if test "x$have_alsa" = xno && test "x$with_alsa" = xcheck; then + echo "********************************************************" + echo "* The build of the KTTS ALSA audio plugin has been" + echo "* disabled because the ALSA sound library headers" + echo "* (alsa/asoundlib.h) were not found." + echo "*" + echo "********************************************************" +fi +if test "x$have_akode" = xno && test "x$with_akode" = xcheck; then + echo "********************************************************" + echo "* The build of the KTTS aKode audio plugin has been" + echo "* disabled because the aKode library configuration" + echo "* script (akode-config) was not found. The aKode libary" + echo "* is part of the kdesupport module." + echo "*" + echo "********************************************************" +fi diff --git a/kttsd/configure.in.in b/kttsd/configure.in.in new file mode 100644 index 0000000..4adcfb1 --- /dev/null +++ b/kttsd/configure.in.in @@ -0,0 +1,304 @@ +#MIN_CONFIG(3.2) + +### AM_INIT_AUTOMAKE(kttsd,0.2.0) + +dnl ================================================================================ + +dnl Check for GStreamer >= 0.8.7 + +AC_ARG_WITH(gstreamer, + [AC_HELP_STRING(--with-gstreamer, + [enable support for GStreamer @<:@default=no@:>@])], + [], with_gstreamer=no) + +have_gst=no +if test "x$with_gstreamer" != xno; then + # pkg-config seems to have a bug where it masks needed -L entries when it + # shouldn't, so disable that. + + PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 + export PKG_CONFIG_ALLOW_SYSTEM_LIBS + + dnl start with 0.8 + GST_MAJORMINOR=0.8 + dnl Actually need 0.8.7, but plugins were version 0.8.5. argh! + GST_REQ=0.8.5 + + PKG_CHECK_MODULES(GST, \ + gstreamer-$GST_MAJORMINOR >= $GST_REQ \ + gstreamer-control-$GST_MAJORMINOR >= $GST_REQ \ + gstreamer-libs-$GST_MAJORMINOR >= $GST_REQ, + have_gst=yes, have_gst=no) + + if test "x$with_gstreamer" != xcheck && test "x$have_gst" != xyes; then + AC_MSG_ERROR([--with-gstreamer was given, but test for GStreamer >= 0.8.5 failed]) + fi +fi + +if test "x$have_gst" = "xno"; then + GST_CFLAGS="" + LDADD_GST="" + LDFLAGS_GST="" + AC_DEFINE(HAVE_GSTREAMER, 0, [have GStreamer]) +else + LDADD_GST=`$PKG_CONFIG --libs-only-l gstreamer-$GST_MAJORMINOR` + LDFLAGS_GST=`$PKG_CONFIG --libs-only-other gstreamer-$GST_MAJORMINOR` + + # Append -L entries, since they are masked by --libs-only-l and + # --libs-only-other + LIBDIRS_GST=`$PKG_CONFIG --libs-only-L gstreamer-$GST_MAJORMINOR` + LDADD_GST="$LDADD_GST $LIBDIRS_GST" + + AC_MSG_NOTICE([GStreamer version >= $GST_REQ found.]) + AC_DEFINE(HAVE_GSTREAMER, 1, [have GStreamer]) + + if test "x$with_gstreamer" != xcheck && test "x$have_gst" != xyes; then + AC_MSG_ERROR([--with-gstreamer was given, but test for GStreamer >= 0.8.7 failed]) + fi +fi + +AC_SUBST(GST_CFLAGS) +AC_SUBST(LDADD_GST) +AC_SUBST(LDFLAGS_GST) + +AM_CONDITIONAL(include_kttsd_gstplayer, [test "x$have_gst" = "xyes"]) + +dnl ================================================================================ + +dnl Do not compile artsplayer plugin if user specifies --without-arts + +AM_CONDITIONAL(include_kttsd_artsplayer, [test "x$build_arts" = "xyes"]) + +dnl ================================================================================ + +dnl Check for ALSA. +dnl TODO: Don't know if 0.5 works or not. + +AC_DEFUN([KDE_CHECK_ALSA], +[ + have_alsa=no + + KDE_CHECK_HEADERS([sys/asoundlib.h alsa/asoundlib.h], + [have_alsa=yes]) + + KDE_CHECK_LIB(asound, snd_seq_create_simple_port, + [:], [have_alsa=no]) + + AC_LANG_SAVE + AC_LANG_C + if test "x$have_alsa" = xyes; then + AC_TRY_COMPILE([ + #include "confdefs.h" + #ifdef HAVE_SYS_ASOUNDLIB_H + #include <sys/asoundlib.h> + #endif + #ifdef HAVE_ALSA_ASOUNDLIB_H + #include <alsa/asoundlib.h> + #endif + ],[ + #if (SND_LIB_MAJOR == 0) && (SND_LIB_MINOR == 5) + /* we have ALSA 0.5.x */ + #else + #error not ALSA 0.5.x + #endif + ], + have_alsa_0_5=yes) + + AC_TRY_COMPILE([ + #include "confdefs.h" + #ifdef HAVE_SYS_ASOUNDLIB_H + #include <sys/asoundlib.h> + #endif + #ifdef HAVE_ALSA_ASOUNDLIB_H + #include <alsa/asoundlib.h> + #endif + ],[ + #if (SND_LIB_MAJOR == 0) && (SND_LIB_MINOR == 9) + /* we have ALSA 0.9.x */ + #else + #error not ALSA 0.9.x + #endif + ], + have_alsa_0_9=yes) + + AC_TRY_COMPILE([ + #include "confdefs.h" + #ifdef HAVE_SYS_ASOUNDLIB_H + #include <sys/asoundlib.h> + #endif + #ifdef HAVE_ALSA_ASOUNDLIB_H + #include <alsa/asoundlib.h> + #endif + ],[ + #if (SND_LIB_MAJOR == 1) + /* we have ALSA 1.x */ + #else + #error not ALSA 1.x + #endif + ], + have_alsa_1=yes) + fi + AC_LANG_RESTORE + + if test "x$have_alsa_0_9" = xyes || test "x$have_alsa_1" = xyes; then + # for kmix/ and akode/ + LIBASOUND="-lasound" + AC_DEFINE(HAVE_LIBASOUND2, 1, [Define if you have libasound.so.2 (required for ALSA 0.9.x/1.x support)]) + + # for arts/ + ARTS_LIBASOUND="-lasound" + AC_DEFINE(HAVE_ARTS_LIBASOUND2, 1, [Define if you have libasound.so.2 (required for ALSA 0.9.x/1.x support)]) + fi + + if test "x$have_alsa_0_5" = xyes; then + # for arts/ + ARTS_LIBASOUND="-lasound" + AC_DEFINE(HAVE_ARTS_LIBASOUND, 1, [Define if you have libasound.so.1 (required for ALSA 0.5.x support)]) + fi + + AC_SUBST(LIBASOUND) + AC_SUBST(ARTS_LIBASOUND) +]) + +AC_ARG_WITH(alsa, + [AC_HELP_STRING(--with-alsa, + [enable support for ALSA @<:@default=check@:>@])], + [], with_alsa=check) + +have_alsa=no +if test "x$with_alsa" != xno; then + KDE_CHECK_ALSA + + if test "x$with_alsa" != xcheck && test "x$have_alsa" != xyes; then + AC_MSG_ERROR([--with-alsa was given, but test for ALSA failed]) + fi +fi + +AM_CONDITIONAL(include_kttsd_alsaplayer, [test "x$have_alsa" = "xyes"]) + +dnl ================================================================================ + +dnl Check for aKode library. Note: As of about 16 Jul 2005, it got moved +dnl from kdemultimedia to kdesupport. + +AC_DEFUN([KDE_CHECK_AKODE], +[ + AC_PATH_PROG(AKODE_CONFIG, akode-config, [no], [$PATH:$prefix/bin]) + + if test "x$AKODE_CONFIG" != xno; then + AC_DEFINE(HAVE_AKODE, 1, [define if you have aKodelib installed]) + akode_includes=`$AKODE_CONFIG --cflags` + akode_libs=`$AKODE_CONFIG --libs` + have_akode=yes + else + akode_includes="" + akode_libs="" + have_akode=no + fi + + AC_SUBST(akode_includes) + AC_SUBST(akode_libs) +]) + +AC_ARG_WITH(akode, + [AC_HELP_STRING([--with-akode], + [enable the aKode decoder @<:@default=no@:>@])], + [], with_akode=no) + +have_akode=no +if test "x$with_akode" != xno; then + KDE_CHECK_AKODE + + if test "x$with_akode" != xcheck && test "x$have_akode" != xyes; then + AC_MSG_ERROR([--with-akode was given, but test for aKode failed]) + fi +fi + +AM_CONDITIONAL(include_kttsd_akodeplayer, [test "x$have_akode" = "xyes"]) + +dnl ================================================================================ + +dnl Check whether to include sys/time.h and time.h, or just sys/time.h. +dnl Defines TIME_WITH_SYS_TIME and HAVE_SYS_TIME_H, which are used in +dnl alsaplayer.cpp. + +AC_HEADER_TIME +AC_CHECK_HEADERS(sys/time.h) + +dnl ================================================================================ + +dnl See if the latest kspeech.h is installed and if not, compile against +dnl kttsd/compat directory. + +if test "$KTTS_KSPEECH_DIR" = ""; then + KDE_CHECK_HEADER(kspeech.h, ktts_have_kspeech_h=yes, ktts_have_kspeech_h=no) + have_latest_kspeech=no + if test "x$ktts_have_kspeech_h" = xyes; then + AC_MSG_CHECKING([whether installed kspeech.h is latest version]) + ktts_save_cppflags=$CPPFLAGS + AC_LANG_SAVE + CPPFLAGS="$all_includes $CPPFLAGS" + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE( + [#include <kspeech.h>], + [ + if (4 == KSpeech::mtHtml); + ], + have_latest_kspeech=yes, + have_latest_kspeech=no) + AC_MSG_RESULT($have_latest_kspeech) + CPPFLAGS=$ktts_save_cppflags + AC_LANG_RESTORE + fi + if test "x$have_latest_kspeech" = xyes; then + KTTS_KSPEECH_DIR='$(kde_includes)' + KTTS_KSPEECH_INCLUDE="" + KTTS_INTERFACES_DIR="" + else + KTTS_KSPEECH_DIR='$(top_srcdir)/kttsd/compat/interfaces/kspeech' + KTTS_KSPEECH_INCLUDE='-I$(top_srcdir)/kttsd/compat/interfaces/kspeech' + KTTS_INTERFACES_DIR="interfaces" + AC_MSG_WARN([Latest kspeech.h not installed. Compiling using kttsd/compat directory.]) + fi + + AC_SUBST(KTTS_KSPEECH_DIR) + AC_SUBST(KTTS_KSPEECH_INCLUDE) + AC_SUBST(KTTS_INTERFACES_DIR) +fi + +dnl ================================================================================ + +# --- Check for KDE < 3.5 --- +# If so, install icons. + +AC_MSG_CHECKING([for KDE version]) + +AC_LANG_SAVE +AC_LANG_CPLUSPLUS +kdeversion_save_CXXFLAGS="$CXXFLAGS" +kdeversion_save_LIBS="$LIBS" +LIBS="$LIBS $X_EXTRA_LIBS" +CXXFLAGS="$CXXFLAGS $all_includes" + +AC_COMPILE_IFELSE([ +#include <kdeversion.h> +#if ! ( KDE_IS_VERSION( 3, 4, 90 ) ) +#error KDE 3.4 +#endif +], + KTTS_ICONS_DIR="" +, + KTTS_ICONS_DIR="icons" +) + +CXXFLAGS="$kdeversion_save_CXXFLAGS" +LIBS="$kdeversion_save_LIBS" +AC_LANG_RESTORE + +if test "$KTTS_ICONS_DIR" = ""; then + AC_MSG_RESULT([KDE 3.4.x or less]) +else + AC_MSG_RESULT([KDE 3.5 or later]) +fi + +AC_SUBST(KTTS_ICONS_DIR) diff --git a/kttsd/cvs2dist b/kttsd/cvs2dist new file mode 100755 index 0000000..da2c08d --- /dev/null +++ b/kttsd/cvs2dist @@ -0,0 +1,634 @@ +#! /bin/bash + +# This is cvs2dist +# Webpage: http://www.katzbrown.com/shiritsu/programming/cvs2dist/ +# Newest version is always available there! +# Please report bugs to <jason@katzbrown.com>. + +# Original author (of cvs2pack.sh) was Sebastian Stein <seb.stein@hpfsc.de> +# Heavy, heavy modifications by Jason Katz-Brown <jason@katzbrown.com> +# Some modifications for i18n inclusion by Dominique Devriese <devriese@kde.org> +# Added --no-i18n-lang and --replace-files by Michael Buesch <mbuesch@freenet.de> +# License: GPL (http://www.gnu.org/) +# Last modification: 2004/10/13 + +cmdline="$@" + +returndir=`pwd` +override="README ChangeLog INSTALL AUTHORS AUTHOR COPYING COPYING.LIB TODO" +remove="config.cache config.log config.status Makefile configure inst-apps CVS acinclude.m4 aclocal.m4 config.h config.h.bot config.h.in configure.files libtool stamp-h stamp-h.in stamp-h1 subdirs *.moc *.la .libs .deps .cvsignore autom4te.cache {arch} .arch-ids" +toplevelremove="configure.in.bot" +# whitespace seperated list of languages to never include. +always_skip_languages="xx" + +pofilenames="kttsd kttsd_commandplugin kttsd_eposplugin kttsd_festivalintplugin +kttsd_fliteplugin kttsd_freettsplugin kttsd_hadifixplugin kttsd_sbdplugin +kttsd_stringreplacerplugin kttsd_talkerchooserplugin kttsd_xmltransformerplugin +kttsjobmgr kttsmgr kcmkttsd khtmlkttsd ktexteditor_kttsd" + +exit_cleanup() +{ + echo -n "Cleaning up... " + if [ -d $temp_dir ]; then + test -z "$debug" && rm -Rf $temp_dir + fi + echo "done." +} + +trap "exit_cleanup; exit 1" SIGINT SIGTERM + +test -e ~/.cvs2distrc && extraoptions=`cat ~/.cvs2distrc` + +# getopt usage from the getopt bash example +# --log has optional argument +TEMP=`getopt \ +-o v:n:r:e:a:B:dhmbgol \ +--long log::,version:,name:,required-header:,required-header-error-message:,make-unpackaged,help,no-bz2,no-bzip2,no-gzip,only-directory,remove-hidden,admin-dir:,branch:,no-i18n,no-i18n-lang:,cvs-root:,debug,replace-files: \ +-n cvs2dist -- $extraoptions "$@"` + +if [ $? != 0 ]; then + echo "Aborted." >&2 + exit 1 +fi + +eval set -- "$TEMP" + +log="/dev/null" +calclog=0 +doi18n="yes" +noi18nlang="" +replace_files="" +cvsroot="" +debug="" + +while true; do + case "$1" in + -v|--version) version=$2; shift 2 ;; + -n|--name) name=$2; shift 2 ;; + -a|--admin-dir) admindir=$2; + if [ `echo $admindir | sed -e 's#^/##'` = $admindir ]; then + admindir="`pwd`/$admindir" + fi + shift 2 ;; + -B|--branch) branch="-r$2"; shift 2;; + --debug) debug="non-empty"; shift 1 ;; + --cvs-root) cvsroot="$2"; shift 2 ;; + -r|--required-header) requiredheader=$2; shift 2 ;; + --no-i18n) doi18n="no"; shift 1 ;; + --no-i18n-lang) noi18nlang="$2"; shift 2 ;; + -e|--required-header-error-message) requiredmsg=$2; shift 2 ;; + -m|--make-unpackaged) leavedir="non-empty"; shift 1 ;; + -h|--help) showhelp="non-empty"; shift 1 ;; + -b|--no-bz2|--no-bzip2) nobzip2="non-empty"; shift 1 ;; + -o|--only-directory) + nogzip="non-empty" + nobzip2="non-empty" + leavedir="non-empty" + shift 1 ;; + -g|--no-gzip) nogzip="non-empty"; shift 1 ;; + -d|--remove-hidden) removehidden="non-empty"; shift 1 ;; + -l) calclog=1; shift 1 ;; + --log) + case "$2" in + # no-argument case + "") calclog=1; shift 2 ;; + # something, should be a file + *) log=$2 + origlog=$log + if [ `echo $log | sed -e 's#^/##'` = $log ]; then + log="`pwd`/$log" + fi + + shift 2 ;; + esac ;; + --replace-files) replace_files="$2"; shift 2 ;; + --) shift + break ;; + *) echo "Aborted." + exit 1 ;; + esac +done + +count=0 +for arg do + test $count = 0 && module=$arg + test $count = 1 && directory=$arg + + if [ $count -ge 2 ]; then + modarg=$arg + if [ `echo $modarg | sed -e 's#^/##'` = $modarg ]; then + modarg="`pwd`/$modarg" + fi + addfiles="$addfiles $modarg" + fi + + let count count++ +done + +# test if a module and directory name was given +if [ ! -z $showhelp ] || [ -z $module ] || [ -z $directory ]; then + echo "Usage: cvs2dist module directory [options] [addfile1] [addfile2] ..." + echo "" + echo " -n --name <name>" + echo " -v --version <version>" + echo " --cvs-root <root> the value to use as cvs root" + echo " variable. It is not necessary if you use --no-i18n." + echo " if this is not given, it is taken from the CVSROOT " + echo " environment variable." + echo " --admin-dir <dir> admin/ location (default is module/admin)" + echo " (symbolic links are OK.)" + echo " -B --branch <branch> use branch for i18n checkouts." + echo " --no-i18n don't try to automatically checkout the translations from cvs." + echo " --no-i18n-lang <languages> exclude all languages in the given comma" + echo " seperated list. example:" + echo " --no-i18n-lang uk,de,en_GB" + echo " --log=<logfile> If logfile unspecified, default file is used." + echo " (The '=' is essential and may not be ommited" + echo " when specifying the logfile.)" + echo " -l Log to default logfile." + echo " -m --make-unpackaged Also makes an unpacked distribution" + echo " in the current directory." + echo " -g --no-gzip Do not create gzip package." + echo " -b --no-bz2 Do not create bzip2 package." + echo " --no-bzip2 Alias for the above." + echo " -o --only-directory Alias for -mbg (no packages, only a directory.)" + echo " -r --required-header <header> Errors if header is not installed." + echo " -e --required-header-error-message <message>" + echo " Error message for above." + echo " -d --remove-hidden Remove hidden files/directories from packages" + echo " --replace-files <file-pair-list>" + echo " <file-pair-list> is a comma separated list of file pairs" + echo " which should be replaced in the final distribution package." + echo " Each element of a pair is separated by an @" + echo -n " Example: --replace-files take_this_file@and_move_it_here," + echo "configure.in.bot.dist@configure.in.bot" + echo " The filenames are all relative to your package root." + echo " Please be careful! Try to avoid the usage of .. in the path. It" + echo " may break stuff! There can not be blank characters in the paths." + echo " -h --help Show this help" + echo "" + echo " Name defaults to the last part of the directory." + echo "By default, creates name[-version].tar.gz if gzip is installed" + echo "and/or name[-version].tar.bz2 if bzip2 is installed in the current" + echo "directory. Removes all temporary files it creates. Produces" + echo "tarballs that are standard distribution tarballs with a" + echo "configure script." + echo " Unless --no-i18n is given, it automatically tries to check out " + echo "strings and documentation translation from cvs." + echo " Arguments after the second are added to the toplevel directory" + echo "in the package. Short options can be combined, eg -dm to create" + echo "a directory and remove hidden files. ~/.cvs2distrc is added to" + echo "the beginning of command line arguments if it exists." + echo " '--' signifies the end of options." + echo "" + echo "Example: cvs2dist /sources/kdegames kolf/objects/picture \\" + echo " -n kolf-picture -v 0.9 -r \"kolf/game.h\" \\" + echo " --log ~/tmp/extra-file" + echo "" + echo " Creates packages of the kolf picture plugin, naming the" + echo " packages kolf-picture-0.9 and logging the process. For configure" + echo " to succeed, kolf/game.h must be installed or an error will occur." + echo " ~/tmp/extra-file is added to the packages." + echo "" + echo "Webpage: http://www.katzbrown.com/shiritsu/programming/cvs2dist/" + echo "Authors: Jason Katz-Brown <jason@katzbrown.com>," + echo " Sebastian Stein <seb.stein@hpfsc.de>," + echo " Dominique Devriese <devriese@kde.org>" + exit 1 +fi + +# expand module +module=`echo $module | sed -e 's#/$##'` +if [ `echo $module | sed -e 's#^/##'` = $module ]; then + module="`pwd`/$module" +fi + +# test if the given module is a directory +test -d $module +if [ $? -ne 0 ]; then + echo "$module is not a directory." + echo "Aborted." + exit 1 +fi + +# go to our module +cd $module + +directory=`echo $directory | sed -e 's#^/##'` + +if [ -z $name ]; then + name=$directory + # get rid of trailing slash + name=`echo $name | sed -e 's#/$##'` + # leading slash + name=`echo $name | sed -e 's#^/##'` + + if [ `echo $name | sed -e 's#/##'` != $name ]; then + name=`echo $name | sed -e 's#^.*/##'` + fi +fi + +test $calclog = 1 && log="$returndir/cvs2dist-$name.log" && origlog="cvs2dist-$name.log" + +# test if the given name is a sub-directory +if [ ! -d "$directory" ]; then + echo "$directory is not a sub-directory of $module." + echo "Aborted." + exit 1 +fi + +test $log != "/dev/null" && echo "Logging to $log." + +echo "cvs2dist log on "`date` > $log +echo "http://katzbrown.com/shiritsu/programming/cvs2dist/" >> $log +echo -n "Module: $module; Directory: $directory; Name: $name; " >> $log +if [ -z $version ]; then + echo "Version unspecified." >> $log +else + echo "Version $version." >> $log +fi +echo $cmdline >> $log +echo "--------" >> $log + +if [ -z $version ]; then + filename=$name +else + filename="$name-$version" +fi + +if [ -z $cvsroot ]; then + cvsroot="$CVSROOT"; +fi +if [ $doi18n = "yes" ]; then + # a little warning... + echo "Will try to fetch i18n files from KDE's CVS." + echo "If this doesn't work, use --no-i18n." + + if [ -z "$cvsroot" ]; then + echo "No cvs root specified, CVSROOT env var is empty, " >> $log + echo "and --no-i18n option is not given.." >> $log + echo "Using anonymous cvs.." >> $log + cvsroot=":pserver:anonymous@anoncvs.kde.org:/home/kde" + # append an entry to ~/.cvspass so the user will not be asked + # for a pwd. Thanks to coolo for the idea.. + if ! grep "anoncvs.kde.org" ~/.cvspass >/dev/null 2>&1; then + echo "/1 :pserver:anonymous@anoncvs.kde.org:2401/home/kde A" >> ~/.cvspass + fi + fi +fi + +# the temporary directory +temp_dir="$module/cvs2dist-tmp" +temp_dist="$temp_dir/$filename" + +echo "Temporary directory is $temp_dir." >> $log + +# make a temporary directory +test -d $temp_dir && rm -Rf $temp_dir + +mkdir -p $temp_dist + +# check if we were able to create temp_dir +test -d $temp_dist +if [ $? -ne 0 ]; then + echo "Could not create temporary directory $temp_dist." + echo "$temp_dist could not be created." >> $log + echo "Aborted." + exit 1 +fi + +test -z "$requiredmsg" && requiredmsg="Install development package needed first! $requiredheader, for one, is missing." + +### configure.in.in +if [ ! -z $requiredheader ]; then + naiyou="KDE_CHECK_HEADER( +$requiredheader, +[], +[AC_MSG_ERROR(\"$requiredmsg\")] +)" + echo $naiyou > $temp_dir/configure.in.in + echo "configure.in.in contents: " >> $log + echo "--------" >> $log + echo $naiyou >> $log + appaddfiles="$appaddfiles $temp_dir/configure.in.in" +fi + +# copy all files of the module to temp_dir/name +cp -RL $module/$directory $temp_dist/$name + +echo "cp -R $module/$directory $temp_dist/$name" >> $log + +modulename="$module" +# get rid of trailing slash +modulename=`echo $modulename | sed -e 's#/$##'` +# get the last part +modulename=`echo $modulename | sed -e 's#^.*/##'` + +remove="$remove $modulename.lsm" + +# we check out kde-i18n/subdirs in temp_dir/kde-i18n.. +if [ $doi18n = "yes" ]; then + pushd $temp_dir + echo "cvs co kde-i18n/subdirs" >> $log + cvs -z4 -q -d "$cvsroot" co $branch -P kde-i18n/subdirs > /dev/null 2>&1 + i18nlangs_tmp="$(cat kde-i18n/subdirs)" + skiplist="`echo $noi18nlang | sed -e 's/,/ /g'`" + skiplist="$skiplist $always_skip_languages" + for lang in $i18nlangs_tmp; do + must_skip="no" + for skip in $skiplist; do + if [ "$lang" = "$skip" ]; then + must_skip="yes" + fi + done + if [ "$must_skip" = "no" ]; then + i18nlangs="$i18nlangs $lang" + fi + done + echo "available languages: $i18nlangs" >> $log + popd +fi + +# if a handbook exists, we also copy it to the directory +if [ -d $module/doc/$name ]; then + mkdir -p $temp_dist/doc/$name + cp -Rf $module/doc/$name $temp_dist/doc + find $module/doc/ ! -xtype d -maxdepth 1 | xargs --replace={} cp {} $temp_dist/doc + + if [ $doi18n = "yes" ]; then + pushd $temp_dir + for lang in $i18nlangs; do + test -d $temp_dist/doc/$lang && rm -Rf $temp_dist/doc/$lang + docdirname="kde-i18n/$lang/docs/$modulename/$name" + echo "cvs co $docdirname" >> $log + cvs -z4 -q -d "$cvsroot" co $branch -P "$docdirname" > /dev/null 2>&1 + if [ ! -d "$docdirname" ]; then + echo "$lang's $name documentation does not exist." >> $log + continue + fi + echo -n "Copying $lang's $name documentation over... " + cp -R $docdirname $temp_dist/doc/$lang + # we don't want KDE_DOCS = AUTO, cause that makes the + # build system assume that the name of the app is the + # same as the name of the dir the Makefile.am is in. + # Instead, we explicitly pass the name.. + cat $temp_dist/doc/$lang/Makefile.am | sed -e "s/AUTO/$name/" > $temp_dist/doc/$lang/Makefile.am.nw + mv $temp_dist/doc/$lang/Makefile.am.nw $temp_dist/doc/$lang/Makefile.am + echo "done." + echo "$lang documentation included." >> $log + done + popd + fi +fi + +# clean up doc directory +if [ -d $temp_dist/doc/$name ]; then + pushd $temp_dist/doc/$name + echo -n "make clean in $temp_dist/doc/$name/... " + echo >> $log + echo "make clean in $temp_dist/doc/$name/" >> $log + make clean >> $log + echo "--------" >> $log + echo "done." + popd +fi + +if [ $doi18n = "yes" ]; then + test -d $temp_dist/po && rm -Rf $temp_dist/po + mkdir $temp_dist/po + + pushd $temp_dir/ + for lang in $i18nlangs; do + dest=$temp_dist/po/$lang + for poname in $pofilenames; do + pofilename="kde-i18n/$lang/messages/$modulename/$poname.po"; + echo "cvs co $pofilename" >> $log + cvs -z4 -q -d "$cvsroot" co $branch -P "$pofilename" > /dev/null 2>&1 + if [ ! -f "$pofilename" ]; then + echo "$lang's $poname.po does not exist." >> $log + continue + fi + + if [ ! -d $dest ]; then + mkdir $dest + fi + echo -n "Copying $lang's $poname.po over... " + echo "$lang's $poname.po file included." >> $log + cp $pofilename $dest + done + echo "done." + + echo "KDE_LANG = $lang +SUBDIRS = \$(AUTODIRS) +POFILES = AUTO" > $dest/Makefile.am + + subdirs="non_empty" + done + + if [ -z "$subdirs" ]; then + rm -Rf $temp_dist/po + else + echo "SUBDIRS = \$(AUTODIRS)" > $temp_dist/po/Makefile.am + fi +fi + +# copy the admin directory +if [ -z $admindir ]; then + cp -pRL $module/admin $temp_dist + echo "cp -pRL $module/admin $temp_dist" >> $log +else + cp -pRL $admindir $temp_dist + echo "cp -pRL $admindir $temp_dist" >> $log +fi + +# and all files from the base dir, except directories +echo "Copying over files from the module directory:" >> $log +find $module ! -xtype d -maxdepth 1 | xargs --verbose --replace={} cp {} $temp_dist 2>> $log +echo "--------" >> $log + +# we now enter the temp_dist and delete all unwanted files +# and add wanted files +cd $temp_dist + +# override top-level files +echo "Override files: " >> $log +for file in $override; do + test -e $temp_dist/$name/$file && mv $temp_dist/$name/$file . && echo "mv $temp_dist/$name/$file ." >> $log +done + +test ! -z "$addfiles" && echo "Addfiles: " >> $log +for file in $addfiles; do + test -e $file && cp -R $file $temp_dist && echo "cp -R $file $temp_dist" >> $log +done + +test ! -z "$appaddfiles" && echo "Application addfiles: " >> $log +for file in $appaddfiles; do + test -e $file && cp -R $file $temp_dist/$name/ && echo "cp -R $file $temp_dist/$name/" >> $log +done + +echo "--------" >> $log + +# replace all user requested files +if [ -n "$replace_files" ]; then + echo "Replace user requested files" >> $log + pair_list="`echo $replace_files | sed -e 's/,/ /g'`" + for pair in $pair_list; do + pair_tmp="`echo $pair | sed -e 's/@/ /g'`" + from="`echo $pair_tmp | awk '//{print $1}'`" + to="`echo $pair_tmp | awk '//{print $2}'`" + if [ -z "$from" ] || [ -z "$to" ]; then + echo "bogus pair \"$from@$to\"" >> $log + continue + fi + echo "Replacing \"$to\" by \"$from\"" >> $log + from_path="$temp_dist/$name/$from" + to_path="$temp_dist/$name/$to" + if [ ! -f "$from_path" ]; then + echo "$from_path does not exist!" >> $log + echo "" + echo "Warning: \"$from\" does not exist!" + echo -n "Please enter the path in --replace-files relative " + echo "to the package root of your project." + echo "Your package root is: \"$module/$directory\"" + echo "" + continue + fi + rm -f "$to_path" && \ + mv "$from_path" "$to_path" + if [ $? -ne 0 ]; then + echo -n "Moving \"$from_path\" failed! " >> $log + echo -n "This should not happen." >> $log + if [ -f "$to_path" ]; then + echo "" + else + echo " No \"$to\" will exist in the archive!" >> $log + fi + fi + done + echo "--------" >> $log +fi + +# version file, if it doesn't exist +test ! -z $version && test ! -e VERSION && echo "$name version $version" > VERSION + +cd $temp_dist/$name +echo "make clean in $temp_dist/$name/:" >> $log +echo "" >> $log +echo -n "make clean in $temp_dist/$name/... " +make clean >> $log +echo "--------" >> $log +echo "done." + +cd $temp_dist + +# remove files +echo "Remove files: " >> $log +for file in $remove; do + find . -name $file | xargs rm -Rf + echo "find . -name $file | xargs rm -Rf" >> $log +done + +# remove more files +echo "Remove toplevel files: " >> $log +for file in $toplevelremove; do + test -e $file && rm -Rf $file && echo "rm -Rf $file" >> $log +done +unset file + +# remove hidden files +test ! -z $removehidden && echo "Remove hidden files: " >> $log && find $temp_dist -name ".*" -and ! -name "." | xargs --verbose rm -Rf 2>> $log + +echo "--------" >> $log + +cd $temp_dist + +# create the configure script +# working directory is $temp_dist +echo "make -f Makefile.cvs in $temp_dist/:" >> $log +echo "" >> $log +echo -n "make -f Makefile.cvs in $temp_dist/... " +make -f Makefile.cvs >> $log 2>> $log +echo "rm Makefile.cvs" >> $log +rm -f Makefile.cvs +echo "rm autom4te.cache" >> $log +rm -Rf autom4te.cache +echo "--------" >> $log +echo "done." + +echo "Directory will be $filename." + +# play around with our tar file in temp_dir +cd $temp_dir + +if [ ! -z $leavedir ]; then + test -e $returndir/$filename && rm -Rf $returndir/$filename + cp -R $filename $returndir +fi + +# make a tar of temp_dist +if [ -z $nogzip ] || [ -z $nobzip2 ]; then + echo -n "Tarring... " + echo "tar cf $filename.tar $filename" >> $log + tar cf $filename.tar $filename >> $log + echo "done." +fi + +if [ -z $nogzip ] && [ ! -z `which gzip 2> /dev/null` ]; then + cp $filename.tar $filename.tar.tmp + echo -n "running gzip... " + echo "gzip $filename.tar" >> $log + gzip $filename.tar + echo "done." + mv $filename.tar.tmp $filename.tar + mv $filename.tar.gz $returndir + echo "mv $filename.tar.gz $returndir" >> $log +fi +if [ -z $nobzip2 ] && [ ! -z `which bzip2 2> /dev/null` ]; then + echo -n "running bzip2... " + echo "bzip2 $filename.tar" >> $log + bzip2 $filename.tar + echo "done." + mv $filename.tar.bz2 $returndir + echo "mv $filename.tar.bz2 $returndir" >> $log +fi + +test -e $filename.tar && rm -f $filename.tar + +# cleanup all tempoaries +exit_cleanup + +cd $returndir + +test -z $leavedir && test ! -z $nobzip2 && test ! -z $nogzip && echo "Finished - no created files." && exit 1 + +# cool, everything went ok! +if [ -z $leavedir ]; then + echo "Finished. Created packages:" +else + if [ -z $nogzip ] || [ -z $nobzip2 ]; then + echo "Finished. Created packages/directories:" + else + echo "Finished. Created directory:" + fi +fi + +echo "--------" >> $log +echo "Done: " >> $log +echo "Files are in `pwd`." >> $log + +if [ ! -z $leavedir ] && [ -d $filename ]; then + ls -ld $filename + ls -ld $filename >> $log +fi +if [ -z $nogzip ] && [ -e $filename.tar.gz ]; then + ls -l $filename.tar.gz + ls -l $filename.tar.gz >> $log +fi +if [ -z $nobzip2 ] && [ -e $filename.tar.bz2 ]; then + ls -l $filename.tar.bz2 + ls -l $filename.tar.bz2 >> $log +fi +if [ $log != "/dev/null" ]; then + echo "Created log:" + ls -l $origlog +fi diff --git a/kttsd/debian/TODO b/kttsd/debian/TODO new file mode 100644 index 0000000..12b05e3 --- /dev/null +++ b/kttsd/debian/TODO @@ -0,0 +1,36 @@ +* FWIW, I have the following packages installed (among many others, of course):
+ libqt3-headers 3.3.3-4.1 Qt3 header files
+ libqt3-mt-dev 3.3.3-4.1 Qt development files (Threaded)
+ libqt3c102-mt 3.3.3-4.1 Qt GUI Library (Threaded runtime version), V
+ libqthreads-12 1.6.4-4 QuickThreads library for Guile
+ qt3-designer 3.3.3-4.1 Qt3 Designer
+ qt3-dev-tools 3.3.3-4.1 Qt3 development tools
+ qt3-doc 3.3.3-4.1 Qt3 API documentation
+ kde 3.1.2 The K Desktop Environment
+ kdelibs4 3.2.3-2 KDE core libraries
+ kdelibs4-dev 3.2.3-2 KDE core libraries (development files)
+ kdelibs4-doc 3.2.3-2 KDE core library documentation
+ kde-devel 3.1.2 The K Desktop Environment (development files
+ kde-devel-extr 3.1.2-0.2 The K Desktop Environment (extra development
+ exuberant-ctag 5.5.4-1 build tag file indexes of source code defini
+ cdbs 0.4.26-1.1 common build system for Debian packages
+
+* Here's my build procedure (note that I'm using CDBS):
+ - cd kdeaccessibility/doc/kttsd && make distclean && cd kdeaccessibility/kttsd && make distclean
+ - cvs2dist kdeaccessibility kttsd --version 0.3.0
+ - Copy resulting kttsd-0.3.0.tar.gz to a work folder and rename to kttsd_0.3.0.orig.tar.gz
+ (Note underscore)
+ - Unpack tarball
+ - cd kttsd-0.3.0
+ - rm -fR autom4te.cache
+ - cp -R kttsd/debian debian
+ - debuild -uc -us
+
+* dpkg-shlibdeps is reporting:
+ dpkg-shlibdeps: warning: could not find path for libkttsd.so.1
+ Should I have libkttsd.so in the .install file?
+
+* Should rc files be installed to etc/ tree?
+
+* For unknown reason, several of the doc translations aren't being built (da, pt for example),
+ but en and de are being built. ??
diff --git a/kttsd/debian/changelog b/kttsd/debian/changelog new file mode 100644 index 0000000..81f503a --- /dev/null +++ b/kttsd/debian/changelog @@ -0,0 +1,5 @@ +kttsd (0.3.0-1) unstable; urgency=low + + * Second Release + + -- Gary Cramblitt (PhantomsDad) <garycramblitt@comcast.net> Wed, 9 Feb 2005 23:05:18 -0500 diff --git a/kttsd/debian/compat b/kttsd/debian/compat new file mode 100644 index 0000000..b8626c4 --- /dev/null +++ b/kttsd/debian/compat @@ -0,0 +1 @@ +4 diff --git a/kttsd/debian/control b/kttsd/debian/control new file mode 100644 index 0000000..61c10b7 --- /dev/null +++ b/kttsd/debian/control @@ -0,0 +1,40 @@ +Source: kttsd +Section: kde +Priority: extra +Maintainer: Gary Cramblitt (PhantomsDad) <garycramblitt@comcast.net> +Build-Depends: debhelper (>= 4.0.0), kdelibs4-dev (>= 4:3.2), qt3-doc (>=3.3.3) +Standards-Version: 3.6.1.1 + +Package: kttsd +Architecture: any +Depends: ${shlibs:Depends} +Suggests: festival, festival-doc, festvox-kallpc, flite, epos, + xsltproc, sox, kttsd-gstreamer +Description: KDE Text-to-Speech system + KTTS, the KDE Text-to-Speech (TTS) system, is a plug-in based dcop service + that allows any KDE (or non-KDE) application to speak using the DCOP + interface. + . + KTTS intends to be the implementation for the KDE Text-to-Speech API. + . + Features + -------- + * Priority system for warnings and messages, while still playing + regular text. + * Long text is parsed into sentences. User may backup by sentence or + paragraph, replay, pause, and stop playing. + * Speak contents of clipboard. + * Speak KDE notification events (KNotify). + . + You must also install a speech synthesis engine, such as festival, flite, + epos, freetts, or hadifix (MBROLA and txt2pho). khelpcenter help:/kttsd/ + for links and suggestions for obtaining and installing synthesizers. + +Package: kttsd-gstreamer +Architecture: any +Depends: kttsd, libgstreamer0.8-0 (>= 0.8.7) +Description: KTTSD GStreamer plugin + A plugin for the KDE Text-to-Speech system for producing + audio output via GStreamer. You will also need the + GStreamer wav file plugin and at least one GStreamer + output plugin (aRts, oss, or nas). diff --git a/kttsd/debian/copyright b/kttsd/debian/copyright new file mode 100644 index 0000000..c4848dc --- /dev/null +++ b/kttsd/debian/copyright @@ -0,0 +1,33 @@ +This package was debianized by Gary Cramblitt (PhantomsDad) +<garycramblitt@comcast.net> on +Wed, 9 FEb 2005 17:06:00 +0500. + +It was downloaded from the KDE CVS repository. + +Upstream Authors: + José Pablo Ezequiel Fernández <pupeno@pupeno.com> + Gary Cramblitt <garycramblitt@comcast.net> + Gunnar Schmi Dt <gunnar@schmi-dt.de> + Olaf Schmidt <ojschmidt@kde.org> + Paul Giannaros <ceruleanblaze@gmail.com> + +Copyright: +(c) 2002, José Pablo Ezequiel Fernández + + This package 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 dated June, 1991. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + +On Debian systems, the complete text of the GNU General +Public License can be found in /usr/share/common-licenses/GPL file. diff --git a/kttsd/debian/dirs b/kttsd/debian/dirs new file mode 100644 index 0000000..e772481 --- /dev/null +++ b/kttsd/debian/dirs @@ -0,0 +1 @@ +usr/bin diff --git a/kttsd/debian/docs b/kttsd/debian/docs new file mode 100644 index 0000000..724e084 --- /dev/null +++ b/kttsd/debian/docs @@ -0,0 +1,2 @@ +README +TODO diff --git a/kttsd/debian/kttsd-gstreamer.install b/kttsd/debian/kttsd-gstreamer.install new file mode 100644 index 0000000..dbe0185 --- /dev/null +++ b/kttsd/debian/kttsd-gstreamer.install @@ -0,0 +1,3 @@ +debian/tmp/usr/lib/kde3/libkttsd_gstplugin.so +debian/tmp/usr/lib/kde3/libkttsd_gstplugin.la +debian/tmp/usr/share/services/kttsd_gstplugin.desktop diff --git a/kttsd/debian/kttsd.install b/kttsd/debian/kttsd.install new file mode 100644 index 0000000..1ed977e --- /dev/null +++ b/kttsd/debian/kttsd.install @@ -0,0 +1,107 @@ +debian/tmp/usr/bin/kttsd +debian/tmp/usr/bin/kttsmgr +debian/tmp/usr/lib/libkttsd.so.1.0.0 +debian/tmp/usr/lib/libkttsd.so.1 +debian/tmp/usr/lib/libkttsd.la +debian/tmp/usr/lib/kde3/libkttsd_festivalintplugin.so +debian/tmp/usr/lib/kde3/libkttsd_festivalintplugin.la +debian/tmp/usr/lib/kde3/libkttsd_commandplugin.so +debian/tmp/usr/lib/kde3/libkttsd_commandplugin.la +debian/tmp/usr/lib/kde3/libkttsd_hadifixplugin.so +debian/tmp/usr/lib/kde3/libkttsd_hadifixplugin.la +debian/tmp/usr/lib/kde3/libkttsd_fliteplugin.so +debian/tmp/usr/lib/kde3/libkttsd_fliteplugin.la +debian/tmp/usr/lib/kde3/libkttsd_eposplugin.so +debian/tmp/usr/lib/kde3/libkttsd_eposplugin.la +debian/tmp/usr/lib/kde3/libkttsd_freettsplugin.so +debian/tmp/usr/lib/kde3/libkttsd_freettsplugin.la +debian/tmp/usr/lib/kde3/libkttsd_artsplugin.so +debian/tmp/usr/lib/kde3/libkttsd_artsplugin.la +debian/tmp/usr/lib/kde3/libkttsd_stringreplacerplugin.so +debian/tmp/usr/lib/kde3/libkttsd_stringreplacerplugin.la +debian/tmp/usr/lib/kde3/libkttsd_sbdplugin.so +debian/tmp/usr/lib/kde3/libkttsd_sbdplugin.la +debian/tmp/usr/lib/kde3/libkttsd_talkerchooserplugin.so +debian/tmp/usr/lib/kde3/libkttsd_talkerchooserplugin.la +debian/tmp/usr/lib/kde3/libkttsd_xmltransformerplugin.so +debian/tmp/usr/lib/kde3/libkttsd_xmltransformerplugin.la +debian/tmp/usr/lib/kde3/libkttsjobmgrpart.so +debian/tmp/usr/lib/kde3/libkttsjobmgrpart.la +debian/tmp/usr/lib/kde3/kcm_kttsd.so +debian/tmp/usr/lib/kde3/kcm_kttsd.la +debian/tmp/usr/lib/kde3/ktexteditor_kttsd.la +debian/tmp/usr/lib/kde3/ktexteditor_kttsd.so + +debian/tmp/usr/lib/kde3/libkhtmlkttsdplugin.la +debian/tmp/usr/lib/kde3/libkhtmlkttsdplugin.so + +debian/tmp/usr/share/servicetypes/kttsd_synthplugin.desktop +debian/tmp/usr/share/servicetypes/kttsd_audioplugin.desktop +debian/tmp/usr/share/servicetypes/kttsd_filterplugin.desktop +debian/tmp/usr/share/servicetypes/dcoptexttospeech.desktop + +debian/tmp/usr/share/apps/kttsd/xslt/SSMLtoPlainText.xsl +debian/tmp/usr/share/apps/kttsd/festivalint/voices +debian/tmp/usr/share/apps/kttsd/festivalint/sabletowave.scm +debian/tmp/usr/share/apps/kttsd/festivalint/xslt/SSMLtoSable.xsl +debian/tmp/usr/share/apps/kttsd/hadifix/xslt/SSMLtoTxt2pho.xsl +debian/tmp/usr/share/apps/kttsd/talkerchooser/female_notifications_rc +debian/tmp/usr/share/apps/kttsd/sbd/polish_festival_sbdrc +debian/tmp/usr/share/apps/kttsd/sbd/standard_sbdrc +debian/tmp/usr/share/apps/kttsd/stringreplacer/abbreviations.xml +debian/tmp/usr/share/apps/kttsd/stringreplacer/emoticons.xml +debian/tmp/usr/share/apps/kttsd/stringreplacer/polish_festival_fixes.xml +debian/tmp/usr/share/apps/kttsd/stringreplacer/chat.xml +debian/tmp/usr/share/apps/kttsd/stringreplacer/kmail.xml +debian/tmp/usr/share/apps/kttsd/stringreplacer/polish_festival_unspeakables.xml +debian/tmp/usr/share/apps/kttsd/xmltransformer/xhtml2ssml.xsl +debian/tmp/usr/share/apps/ktexteditor_kttsd/ktexteditor_kttsdui.rc + +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/common +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/general.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/index.cache.bz2 +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/index.docbook +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/addtalker.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/audio.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/festivalintconf.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/general.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/interruption.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/jobs.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/talkers.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/filters.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/stringreplacer.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/xmltransformer.png +debian/tmp/usr/share/doc/kde/HTML/*/kttsd/talkerchooser.png + +debian/tmp/usr/share/services/kttsd_festivalintplugin.desktop +debian/tmp/usr/share/services/kttsd_commandplugin.desktop +debian/tmp/usr/share/services/kttsd_hadifixplugin.desktop +debian/tmp/usr/share/services/kttsd_fliteplugin.desktop +debian/tmp/usr/share/services/kttsd_eposplugin.desktop +debian/tmp/usr/share/services/kttsd_freettsplugin.desktop +debian/tmp/usr/share/services/kttsd_stringreplacerplugin.desktop +debian/tmp/usr/share/services/kttsd_sbdplugin.desktop +debian/tmp/usr/share/services/kttsd_xmltransformerplugin.desktop +debian/tmp/usr/share/services/kttsd_talkerchooserplugin.desktop +debian/tmp/usr/share/services/kttsd.desktop +debian/tmp/usr/share/services/kttsjobmgr.desktop +debian/tmp/usr/share/services/kttsd_artsplugin.desktop +debian/tmp/usr/share/services/ktexteditor_kttsd.desktop + +debian/tmp/usr/share/apps/khtml/kpartplugins/khtmlkttsd.desktop +debian/tmp/usr/share/apps/khtml/kpartplugins/khtmlkttsd.rc + +debian/tmp/usr/share/icons/hicolor/16x16/actions/male.png +debian/tmp/usr/share/icons/hicolor/16x16/actions/female.png +debian/tmp/usr/share/icons/crystalsvg/16x16/apps/kttsd.png +debian/tmp/usr/share/icons/crystalsvg/22x22/apps/kttsd.png +debian/tmp/usr/share/icons/crystalsvg/32x32/apps/kttsd.png +debian/tmp/usr/share/icons/crystalsvg/48x48/apps/kttsd.png +debian/tmp/usr/share/icons/crystalsvg/64x64/apps/kttsd.png +debian/tmp/usr/share/icons/crystalsvg/128x128/apps/kttsd.png +debian/tmp/usr/share/icons/crystalsvg/scalable/apps/kttsd.svgz + +debian/tmp/usr/share/applications/kde/kcmkttsd.desktop +debian/tmp/usr/share/applications/kde/kttsmgr.desktop + +debian/tmp/usr/share/locale/*/LC_MESSAGES/ diff --git a/kttsd/debian/kttsd.manpages b/kttsd/debian/kttsd.manpages new file mode 100644 index 0000000..8051bd1 --- /dev/null +++ b/kttsd/debian/kttsd.manpages @@ -0,0 +1,2 @@ +debian/man/kttsmgr.1 +debian/man/kttsd.1 diff --git a/kttsd/debian/kttsmgr.menu b/kttsd/debian/kttsmgr.menu new file mode 100644 index 0000000..f39b56b --- /dev/null +++ b/kttsd/debian/kttsmgr.menu @@ -0,0 +1,6 @@ +?package(kttsd):\ + needs="x11"\ + section="Apps/Tools"\ + hints="KDE"\ + title="KTTSMgr"\ + command="/usr/bin/kttsmgr" diff --git a/kttsd/debian/man/kttsd.1 b/kttsd/debian/man/kttsd.1 new file mode 100644 index 0000000..379d7f0 --- /dev/null +++ b/kttsd/debian/man/kttsd.1 @@ -0,0 +1,159 @@ +.\" This file was generated by kdemangen.pl and hand edited +.TH KTTSD 1 "Oct 2004" "K Desktop Environment" "Text-to-speech synthesis deamon" +.SH NAME +kttsd +- Text-to-speech synthesis deamon +.SH SYNOPSIS +kttsd [Qt-options] [KDE-options] +.SH DESCRIPTION +kttsd is part of the KDE Text-to-Speech system. kttsd is the +non-GUI deamon that provides TTS services to applications +via the DCOP interface. kttsd is configured via kttsmgr. +.SH OPTIONS +.SS Generic options: +.TP +.B --help +Show help about options +.TP +.B --help-qt +Show Qt specific options +.TP +.B --help-kde +Show KDE specific options +.TP +.B --help-all +Show all options +.TP +.B --author +Show author information +.TP +.B -v, --version +Show version information +.TP +.B --license +Show license information +.TP +.B -- +End of options +.SS +.SS KDE options: +.TP +.B --caption <caption> +Use 'caption' as name in the titlebar +.TP +.B --icon <icon> +Use 'icon' as the application icon +.TP +.B --miniicon <icon> +Use 'icon' as the icon in the titlebar +.TP +.B --config <filename> +Use alternative configuration file +.TP +.B --dcopserver <server> +Use the DCOP Server specified by 'server' +.TP +.B --nocrashhandler +Disable crash handler, to get core dumps +.TP +.B --waitforwm +Waits for a WM_NET compatible windowmanager +.TP +.B --style <style> +sets the application GUI style +.TP +.B --geometry <geometry> +sets the client geometry of the main widget +.TP +.B --nofork +Don't run in the background. +.SS Qt options: +.TP +.B --display <displayname> +Use the X-server display 'displayname' +.TP +.B --session <sessionId> +Restore the application for the given 'sessionId' +.TP +.B --cmap +Causes the application to install a private color +map on an 8-bit display +.TP +.B --ncols <count> +Limits the number of colors allocated in the color +cube on an 8-bit display, if the application is +using the QApplication::ManyColor color +specification +.TP +.B --nograb +tells Qt to never grab the mouse or the keyboard +.TP +.B --dograb +running under a debugger can cause an implicit +-nograb, use -dograb to override +.TP +.B --sync +switches to synchronous mode for debugging +.TP +.B --fn, --font <fontname> +defines the application font +.TP +.B --bg, --background <color> +sets the default background color and an +application palette (light and dark shades are +calculated) +.TP +.B --fg, --foreground <color> +sets the default foreground color +.TP +.B --btn, --button <color> +sets the default button color +.TP +.B --name <name> +sets the application name +.TP +.B --title <title> +sets the application title (caption) +.TP +.B --visual TrueColor +forces the application to use a TrueColor visual on +an 8-bit display +.TP +.B --inputstyle <inputstyle> +sets XIM (X Input Method) input style. Possible +values are onthespot, overthespot, offthespot and +root +.TP +.B --im <XIM server> +set XIM server +.TP +.B --noxim +disable XIM +.TP +.B --reverse +mirrors the whole layout of widgets +.SS + +.SH SEE ALSO +Full user documentation is available through the KDE Help Center. You can also enter the URL +.BR help:/kttsd/ +directly into konqueror or you can run +.BR "`khelpcenter help:/kttsd/'" +from the command-line. +.br + +kttsmgr(1) +.br +.SH AUTHORS +.nf +Jos Pablo Ezequiel Fernndez <pupeno@pupeno.com> +.br +Gary Cramblitt <garycramblitt@comcast.net> +.br +Gunnar Schmi Dt <gunnar@schmi-dt.de> +.br +Olaf Schmidt <ojschmidt@kde.org> +.br +Paul Giannaros <ceruleanblaze@gmail.com> +.br + diff --git a/kttsd/debian/man/kttsmgr.1 b/kttsd/debian/man/kttsmgr.1 new file mode 100644 index 0000000..547ff14 --- /dev/null +++ b/kttsd/debian/man/kttsmgr.1 @@ -0,0 +1,157 @@ +.\" This file was generated by kdemangen.pl and hand edited. +.TH KTTSMGR 1 "Oct 2004" "K Desktop Environment" "Text-to-Speech Manager" +.SH NAME +kttsmgr +- Text-to-Speech Manager +.SH SYNOPSIS +kttsmgr [Qt-options] [KDE-options] +.SH DESCRIPTION +kttsmgr is an application for starting and configuring the +KDE Text-to-Speech system. +.SH OPTIONS +.SS Generic options: +.TP +.B --help +Show help about options +.TP +.B --help-qt +Show Qt specific options +.TP +.B --help-kde +Show KDE specific options +.TP +.B --help-all +Show all options +.TP +.B --author +Show author information +.TP +.B -v, --version +Show version information +.TP +.B --license +Show license information +.TP +.B -- +End of options +.SS +.SS KDE options: +.TP +.B --caption <caption> +Use 'caption' as name in the titlebar +.TP +.B --icon <icon> +Use 'icon' as the application icon +.TP +.B --miniicon <icon> +Use 'icon' as the icon in the titlebar +.TP +.B --config <filename> +Use alternative configuration file +.TP +.B --dcopserver <server> +Use the DCOP Server specified by 'server' +.TP +.B --nocrashhandler +Disable crash handler, to get core dumps +.TP +.B --waitforwm +Waits for a WM_NET compatible windowmanager +.TP +.B --style <style> +sets the application GUI style +.TP +.B --geometry <geometry> +sets the client geometry of the main widget +.TP +.B --nofork +Don't run in the background. +.SS Qt options: +.TP +.B --display <displayname> +Use the X-server display 'displayname' +.TP +.B --session <sessionId> +Restore the application for the given 'sessionId' +.TP +.B --cmap +Causes the application to install a private color +map on an 8-bit display +.TP +.B --ncols <count> +Limits the number of colors allocated in the color +cube on an 8-bit display, if the application is +using the QApplication::ManyColor color +specification +.TP +.B --nograb +tells Qt to never grab the mouse or the keyboard +.TP +.B --dograb +running under a debugger can cause an implicit +-nograb, use -dograb to override +.TP +.B --sync +switches to synchronous mode for debugging +.TP +.B --fn, --font <fontname> +defines the application font +.TP +.B --bg, --background <color> +sets the default background color and an +application palette (light and dark shades are +calculated) +.TP +.B --fg, --foreground <color> +sets the default foreground color +.TP +.B --btn, --button <color> +sets the default button color +.TP +.B --name <name> +sets the application name +.TP +.B --title <title> +sets the application title (caption) +.TP +.B --visual TrueColor +forces the application to use a TrueColor visual on +an 8-bit display +.TP +.B --inputstyle <inputstyle> +sets XIM (X Input Method) input style. Possible +values are onthespot, overthespot, offthespot and +root +.TP +.B --im <XIM server> +set XIM server +.TP +.B --noxim +disable XIM +.TP +.B --reverse +mirrors the whole layout of widgets +.SS + +.SH SEE ALSO +Full user documentation is available through the KDE Help Center. You can also enter the URL +.BR help:/kttsd/ +directly into konqueror or you can run +.BR "`khelpcenter help:/kttsd/'" +from the command-line. +.br + +kttsd(1) +.SH AUTHORS +.nf +Jos Pablo Ezequiel Fernndez <pupeno@pupeno.com> +.br +Gary Cramblitt <garycramblitt@comcast.net> +.br +Gunnar Schmi Dt <gunnar@schmi-dt.de> +.br +Olaf Schmidt <ojschmidt@kde.org> +.br +Paul Giannaros <ceruleanblaze@gmail.com> +.br + diff --git a/kttsd/debian/man/manpages.in b/kttsd/debian/man/manpages.in new file mode 100644 index 0000000..f0a5d64 --- /dev/null +++ b/kttsd/debian/man/manpages.in @@ -0,0 +1,2 @@ +kttsd kttsd +kttsd kttsmgr diff --git a/kttsd/debian/rules b/kttsd/debian/rules new file mode 100755 index 0000000..eaed76d --- /dev/null +++ b/kttsd/debian/rules @@ -0,0 +1,5 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/kde.mk +DEB_CONFIGURE_EXTRA_FLAGS := --enable-kttsd-gstreamer diff --git a/kttsd/debian/shlibs.kttsd b/kttsd/debian/shlibs.kttsd new file mode 100644 index 0000000..6746322 --- /dev/null +++ b/kttsd/debian/shlibs.kttsd @@ -0,0 +1,2 @@ +libkttsd 1 (>= 1:1.0.0) + diff --git a/kttsd/filters/Makefile.am b/kttsd/filters/Makefile.am new file mode 100644 index 0000000..8c068f0 --- /dev/null +++ b/kttsd/filters/Makefile.am @@ -0,0 +1,26 @@ +# Include paths. INCLUDES is maintained by KDevelop, AM_CPPFLAGS is the preferred variable, +# so keep them synchronized. +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd \ + -I$(kde_includes)/arts \ + $(CFLAGS_GST) \ + $(all_includes) + +SUBDIRS = stringreplacer xmltransformer sbd talkerchooser + +######################################################################### +# APPLICATION SECTION +######################################################################### +# This is the program that gets installed. It's name is used for all +# of the other Makefile.am variables. +check_PROGRAMS = testfilter + +testfilter_SOURCES = main.cpp + +testfilter_LDFLAGS = $(all_libraries) $(KDE_RPATH) +testfilter_LDADD = \ + $(top_builddir)/kttsd/libkttsd/libkttsd.la \ + $(LIB_KDECORE) + +kde_servicetypes_DATA = kttsd_filterplugin.desktop + diff --git a/kttsd/filters/kttsd_filterplugin.desktop b/kttsd/filters/kttsd_filterplugin.desktop new file mode 100644 index 0000000..afede99 --- /dev/null +++ b/kttsd/filters/kttsd_filterplugin.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=KTTSD/FilterPlugin + +[PropertyDef::X-KDE-Languages] +Type=QStringList diff --git a/kttsd/filters/main.cpp b/kttsd/filters/main.cpp new file mode 100644 index 0000000..d358da8 --- /dev/null +++ b/kttsd/filters/main.cpp @@ -0,0 +1,140 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KTTSD Filter Test Program + ------------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <qstring.h> +#include <iostream> +using namespace std; + +#include <qtextstream.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kconfig.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> + +#include "filterproc.h" +#include "talkercode.h" + +static const KCmdLineOptions options[] = +{ + { "+pluginName", I18N_NOOP("Name of a KTTSD filter plugin (required)"), 0 }, + { "t", 0, 0 }, + { "talker <talker>", I18N_NOOP("Talker code passed to filter"), "en" }, + { "a", 0, 0 }, + { "appid <appID>", I18N_NOOP("DCOP application ID passed to filter"), "testfilter" }, + { "g", 0, 0 }, + { "group <filterID>", + I18N_NOOP2("A string that appears in a single config file, not a group of config files", + "Config file group name passed to filter"), "testfilter" }, + { "list", I18N_NOOP("Display list of available Filter PlugIns and exit"), 0 }, + { "b", 0, 0 }, + { "break", I18N_NOOP("Display tabs as \\t, otherwise they are removed"), 0 }, + { "list", I18N_NOOP("Display list of available filter plugins and exit"), 0 }, + KCmdLineLastOption +}; + +int main(int argc, char *argv[]) +{ + KAboutData aboutdata( + "testfilter", I18N_NOOP("testfilter"), + "0.1.0", I18N_NOOP("A utility for testing KTTSD filter plugins."), + KAboutData::License_GPL, "(C) 2005, Gary Cramblitt <garycramblitt@comcast.net>"); + aboutdata.addAuthor("Gary Cramblitt", I18N_NOOP("Maintainer"),"garycramblitt@comcast.net"); + + KCmdLineArgs::init( argc, argv, &aboutdata ); + // Tell which options are supported + KCmdLineArgs::addCmdLineOptions( options ); + + KApplication app( false, false ); + + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin"); + + if (args->isSet("list")) + { + // Iterate thru the offers to list the plugins. + const int offersCount = offers.count(); + for(int ndx=0; ndx < offersCount ; ++ndx) + { + QString name = offers[ndx]->name(); + cout << name.latin1() << endl; + } + return 0; + } + + QString filterName; + if (args->count() > 0) filterName = args->arg(0); + QString talker = args->getOption("talker"); + QCString appId = args->getOption("appid"); + QString groupName = args->getOption("group"); + + if (filterName.isEmpty()) kdError(1) << "No filter name given." << endl; + + const int offersCount = offers.count(); + for(int ndx=0; ndx < offersCount ; ++ndx) + { + if(offers[ndx]->name() == filterName) + { + // When the entry is found, load the plug in + // First create a factory for the library + KLibFactory *factory = KLibLoader::self()->factory(offers[ndx]->library().latin1()); + if(factory) + { + // If the factory is created successfully, instantiate the KttsFilterConf class for the + // specific plug in to get the plug in configuration object. + int errorNo; + KttsFilterProc *plugIn = + KParts::ComponentFactory::createInstanceFromLibrary<KttsFilterProc>( + offers[ndx]->library().latin1(), NULL, offers[ndx]->library().latin1(), + QStringList(), &errorNo); + if(plugIn) + { + KConfig* config = new KConfig("kttsdrc"); + config->setGroup( "General" ); + plugIn->init( config, groupName ); + QTextStream inp ( stdin, IO_ReadOnly ); + QString text; + text = inp.read(); + TalkerCode* talkerCode = new TalkerCode( talker ); + text = plugIn->convert( text, talkerCode, appId ); + if ( args->isSet("break") ) + text.replace( "\t", "\\t" ); + else + text.replace( "\t", "" ); + cout << text.latin1() << endl; + delete config; + delete plugIn; + return 0; + } else + kdError(2) << "Unable to create instance from library." << endl; + } else + kdError(3) << "Unable to create factory." << endl; + } + } + kdError(4) << "Unable to find a plugin named " << filterName << endl; +} diff --git a/kttsd/filters/sbd/Makefile.am b/kttsd/filters/sbd/Makefile.am new file mode 100644 index 0000000..4faa8a9 --- /dev/null +++ b/kttsd/filters/sbd/Makefile.am @@ -0,0 +1,27 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_sbdplugin.la + +libkttsd_sbdplugin_la_SOURCES = \ + sbdproc.cpp \ + sbdconf.cpp \ + sbdconfwidget.ui \ + sbdplugin.cpp + +libkttsd_sbdplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_sbdplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_sbdplugin.desktop +servicesdir = $(kde_servicesdir) + +# Install data files. +sbddatadir = $(kde_datadir)/kttsd/sbd/ +sbddata_DATA = \ + standard_sbdrc \ + polish_festival_sbdrc + +noinst_HEADERS = sbdproc.h diff --git a/kttsd/filters/sbd/kttsd_sbdplugin.desktop b/kttsd/filters/sbd/kttsd_sbdplugin.desktop new file mode 100644 index 0000000..0e8a483 --- /dev/null +++ b/kttsd/filters/sbd/kttsd_sbdplugin.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Name=Sentence Boundary Detector +Name[bg]=Детектор на изречения +Name[ca]=Detector de límit de frase +Name[cs]=Zjištění vět +Name[da]=Grænsedetektor for sætninger +Name[de]=Satzgrenzenerkennung +Name[el]=Ανιχνευτής ορίων πρότασης +Name[es]=Detector de final de frase +Name[et]=Lausepiiri tuvastaja +Name[eu]=Esaldien mugen detektorea +Name[fa]=آشکارساز کرانۀ جمله +Name[fi]=Lauserajojen tunnistin +Name[fr]=Détecteur de fins de phrases +Name[ga]=Brathadóir Teorainneacha Abairtí +Name[gl]=Detector de Límite de Frases +Name[hu]=Mondathatár-detektáló +Name[is]=Setningamarka greinir +Name[it]=Rilevatore del limite delle frasi +Name[ja]=センテンスの境界検出 +Name[ka]=წინადადების საზღვრის დამდგენი +Name[km]= ឧបករណ៍ចាប់ព្រំដែនប្រយោគ +Name[lt]=Sakinio ribų aptikiklis +Name[mk]=Детектор на граница на реченица +Name[ms]=Pengesan Sempadan Ayat +Name[nb]=Finner setningsgrenser +Name[nds]=Satzgrenz-Faststellen +Name[ne]=वाक्य सीमा संसूचक +Name[nl]=Zingrensdetectie +Name[pl]=Wykrywanie granicy zdania +Name[pt]=Detector de Limite de Frase +Name[pt_BR]=Detector de Fim de Sentenças +Name[ru]=Определитель границ предложения +Name[sk]=Detekcia okraja vety +Name[sl]=Detektor meje stavkov +Name[sr]=Детектор граница реченице +Name[sr@Latn]=Detektor granica rečenice +Name[sv]=Meningsgränsdetektering +Name[ta]=வாக்கிய பவுண்டரி கண்டுபிடிப்பான் +Name[tg]=Муайянсозии ҳудуди ҷумлаҳо +Name[tr]=Cümle Sınırlama Algılayıcı +Name[uk]=Виявлення меж речень +Name[vi]=Trình nhận dạng Ranh giới Câu văn +Name[zh_CN]=语句分界检测器 +Name[zh_TW]=句子界限偵測器 +Comment=Sentence Boundary Detection Filter Plugin for KTTS +Comment[bg]=Приставка филтър за определяне границите на изречението +Comment[ca]=Connector del filtre detector de límit de frase pel KTTS +Comment[cs]=Modul filtru zjišťování vět KTTS +Comment[da]=Grænsedetektor for sætninger, filter-plugin for KTTS +Comment[de]=Generisches KTTS-Modul zur Erkennung von Satzgrenzen +Comment[el]=Φίλτρο πρόσθετο ανίχνευσης ορίων πρότασης για το KTTS +Comment[es]=Complemento de filtrado que detecta el final de las frases para KTTS +Comment[et]=KTTS-i lausepiiri tuvastamise filtri plugin +Comment[eu]=KTTS-ren esaldien mugen detektorearen iragazkien plugina +Comment[fa]=وصلۀ پالایۀ آشکارسازی کرانۀ جمله برای KTTS +Comment[fi]=Lauserajojen tunnistussuodatin liitännäinen KTTS-ohjelmalle +Comment[fr]=Module de filtrage de fins de phrases pour KTTS +Comment[gl]=Plugin de Filtro Detector de Límite de Frases para KTTS +Comment[hu]=Mondatvég-detektáló szűrőmodul a KTTS-hez +Comment[is]=Setningamarka greinasía fyrir KTTS +Comment[it]=Plugin per il filtro di rilevazione del limite delle frasi per KTTS +Comment[ja]=KTTS 用センテンスの境界検出フィルタプラグイン +Comment[ka]=წინადადების საზღვრის დამდგენის ფილტრის მოდული KTTS-სთვის +Comment[km]=កម្មវិធីជំនួយតម្រងឧបករណ៍ចាប់ព្រំដែនប្រយោគសម្រាប់ KTTS +Comment[lt]=Sakinio ribų aptikiklio filtro priedas, skirtas KTTS +Comment[mk]=Филтер за KTTS за детекција на границата на реченица +Comment[ms]=Plugin Penapis Pengesan Sempadan Ayat bagi KTTS +Comment[nb]=Programtillegg for KTTS som finner setningsgrenser +Comment[nds]=KTTS-Filtermoduul för dat Faststellen vun Satzgrenzen +Comment[ne]=KTTS का लागि वाक्य सीमा पत्ता लगाउने फिल्टर +Comment[nl]=Zingrensdetectieplugin voor KTTS +Comment[pl]=Wtyczka filtra wykrywania granicy zdania dla KTTS +Comment[pt]='Plugin' de Filtro de Detecção de Limite de Frase para o KTTS +Comment[pt_BR]=Plug-in de Detector de Fim de Sentenças para o KTTSD +Comment[ru]=Фильтр границ предложения для KTTS +Comment[sk]=Modul filtra na detekciu okraja vety v KTTS +Comment[sl]=Filtrni vstavek KTTS za zanavanje mej stavka +Comment[sr]=Филтерски прикључак KTTS-а за откривање граница реченице +Comment[sr@Latn]=Filterski priključak KTTS-a za otkrivanje granica rečenice +Comment[sv]=Insticksprogram för KTTS med filter för meningsgränsdetektering +Comment[ta]=KTTSக்கான வாக்கிய கண்டறி அலங்கார சொருகுப்பொருள் +Comment[tg]=Филтри ҳудуди ҷумлаҳо барои KTTS +Comment[tr]=KTTS için Cümle Sınırlama Algılayıcı Süzgeci Eklentisi +Comment[uk]=Втулок фільтра виявлення меж для KTTS +Comment[vi]=Trình bổ sung Lọc Nhận dạng Ranh giới Câu văn cho KTTS +Comment[zh_TW]=KTTS 使用的偵測句子是否已結束的外掛程式 +Type=Service +ServiceTypes=KTTSD/FilterPlugin +X-KDE-Library=libkttsd_sbdplugin +X-KDE-Languages=en,en_US,en_GB,en_CA,es,es_mx,cy,de,fi,cs,pl diff --git a/kttsd/filters/sbd/polish_festival_sbdrc b/kttsd/filters/sbd/polish_festival_sbdrc new file mode 100644 index 0000000..ad64153 --- /dev/null +++ b/kttsd/filters/sbd/polish_festival_sbdrc @@ -0,0 +1,6 @@ +[Filter] +AppID= +LanguageCodes=pl +SentenceBoundary=\\t +SentenceDelimiterRegExp=([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n)) +UserFilterName=Polish Sentence Boundary Detector diff --git a/kttsd/filters/sbd/sbdconf.cpp b/kttsd/filters/sbd/sbdconf.cpp new file mode 100644 index 0000000..2d4cf5d --- /dev/null +++ b/kttsd/filters/sbd/sbdconf.cpp @@ -0,0 +1,341 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Sentence Boundary Detection Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> +#include <qhbox.h> +#include <qlayout.h> +#include <qdom.h> +#include <qfile.h> +#include <qradiobutton.h> + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> +#include <klistview.h> +#include <klineedit.h> +#include <kdialog.h> +#include <kdialogbase.h> +#include <kpushbutton.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kregexpeditorinterface.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> +#include <kfiledialog.h> + +// KTTS includes. +#include "filterconf.h" + +// SBD includes. +#include "sbdconf.h" +#include "sbdconf.moc" + +/** +* Constructor +*/ +SbdConf::SbdConf( QWidget *parent, const char *name, const QStringList& /*args*/) : + KttsFilterConf(parent, name) +{ + // kdDebug() << "SbdConf::SbdConf: Running" << endl; + + // Create configuration widget. + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "SbdConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new SbdConfWidget(this, "SbdConfigWidget"); + layout->addWidget(m_widget); + + // Determine if kdeutils Regular Expression Editor is installed. + m_reEditorInstalled = !KTrader::self()->query("KRegExpEditor/KRegExpEditor").isEmpty(); + + m_widget->reButton->setEnabled( m_reEditorInstalled ); + if ( m_reEditorInstalled ) + connect( m_widget->reButton, SIGNAL(clicked()), this, SLOT(slotReButton_clicked()) ); + + connect( m_widget->reLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->sbLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->nameLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->appIdLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect(m_widget->languageBrowseButton, SIGNAL(clicked()), + this, SLOT(slotLanguageBrowseButton_clicked())); + connect(m_widget->loadButton, SIGNAL(clicked()), + this, SLOT(slotLoadButton_clicked())); + connect(m_widget->saveButton, SIGNAL(clicked()), + this, SLOT(slotSaveButton_clicked())); + connect(m_widget->clearButton, SIGNAL(clicked()), + this, SLOT(slotClearButton_clicked())); + + // Set up defaults. + defaults(); +} + +/** +* Destructor. +*/ +SbdConf::~SbdConf(){ + // kdDebug() << "SbdConf::~SbdConf: Running" << endl; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void SbdConf::load(KConfig* config, const QString& configGroup){ + // kdDebug() << "SbdConf::load: Running" << endl; + config->setGroup( configGroup ); + m_widget->nameLineEdit->setText( + config->readEntry("UserFilterName", m_widget->nameLineEdit->text()) ); + m_widget->reLineEdit->setText( + config->readEntry("SentenceDelimiterRegExp", m_widget->reLineEdit->text()) ); + m_widget->sbLineEdit->setText( + config->readEntry("SentenceBoundary", m_widget->sbLineEdit->text()) ); + QStringList langCodeList = config->readListEntry("LanguageCodes"); + if (!langCodeList.isEmpty()) + m_languageCodeList = langCodeList; + QString language = ""; + for ( uint ndx=0; ndx < m_languageCodeList.count(); ++ndx) + { + if (!language.isEmpty()) language += ","; + language += KGlobal::locale()->twoAlphaToLanguageName(m_languageCodeList[ndx]); + } + m_widget->languageLineEdit->setText(language); + m_widget->appIdLineEdit->setText( + config->readEntry("AppID", m_widget->appIdLineEdit->text()) ); +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void SbdConf::save(KConfig* config, const QString& configGroup){ + // kdDebug() << "SbdConf::save: Running" << endl; + config->setGroup( configGroup ); + config->writeEntry("UserFilterName", m_widget->nameLineEdit->text() ); + config->writeEntry("SentenceDelimiterRegExp", m_widget->reLineEdit->text() ); + config->writeEntry("SentenceBoundary", m_widget->sbLineEdit->text() ); + config->writeEntry("LanguageCodes", m_languageCodeList ); + config->writeEntry("AppID", m_widget->appIdLineEdit->text().replace(" ", "") ); +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void SbdConf::defaults(){ + // kdDebug() << "SbdConf::defaults: Running" << endl; + m_widget->nameLineEdit->setText( i18n("Standard Sentence Boundary Detector") ); + m_widget->reLineEdit->setText( "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))" ); + m_widget->sbLineEdit->setText( "\\1\\t" ); + m_languageCodeList.clear(); + m_widget->languageLineEdit->setText( "" ); + m_widget->appIdLineEdit->setText( "" ); + // kdDebug() << "SbdConf::defaults: Exiting" << endl; +} + +/** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ +bool SbdConf::supportsMultiInstance() { return true; } + +/** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ +QString SbdConf::userPlugInName() +{ + if ( m_widget->reLineEdit->text().isEmpty() ) + return QString::null; + else + return m_widget->nameLineEdit->text(); +} + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * @return True if this filter is a SBD. + */ +bool SbdConf::isSBD() { return true; } + +void SbdConf::slotReButton_clicked() +{ + // Show Regular Expression Editor dialog if it is installed. + if ( !m_reEditorInstalled ) return; + QDialog *editorDialog = + KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor" ); + if ( editorDialog ) + { + // kdeutils was installed, so the dialog was found. Fetch the editor interface. + KRegExpEditorInterface *reEditor = + static_cast<KRegExpEditorInterface *>(editorDialog->qt_cast( "KRegExpEditorInterface" ) ); + Q_ASSERT( reEditor ); // This should not fail!// now use the editor. + reEditor->setRegExp( m_widget->reLineEdit->text() ); + int dlgResult = editorDialog->exec(); + if ( dlgResult == QDialog::Accepted ) + { + QString re = reEditor->regExp(); + m_widget->reLineEdit->setText( re ); + configChanged(); + } + delete editorDialog; + } else return; +} + +void SbdConf::slotLanguageBrowseButton_clicked() +{ + // Create a QHBox to host KListView. + QHBox* hBox = new QHBox(m_widget, "SelectLanguage_hbox"); + // Create a KListView and fill with all known languages. + KListView* langLView = new KListView(hBox, "SelectLanguage_lview"); + langLView->addColumn(i18n("Language")); + langLView->addColumn(i18n("Code")); + langLView->setSelectionMode(QListView::Extended); + QStringList allLocales = KGlobal::locale()->allLanguagesTwoAlpha(); + QString locale; + QString languageCode; + QString countryCode; + QString charSet; + QString language; + // Blank line so user can select no language. + QListViewItem* item = new KListViewItem(langLView, "", ""); + if (m_languageCodeList.isEmpty()) item->setSelected(true); + const int allLocalesCount = allLocales.count(); + for (int ndx=0; ndx < allLocalesCount; ++ndx) + { + locale = allLocales[ndx]; + KGlobal::locale()->splitLocale(locale, languageCode, countryCode, charSet); + language = KGlobal::locale()->twoAlphaToLanguageName(languageCode); + if (!countryCode.isEmpty()) language += + " (" + KGlobal::locale()->twoAlphaToCountryName(countryCode)+")"; + QListViewItem* item = new KListViewItem(langLView, language, locale); + if (m_languageCodeList.contains(locale)) item->setSelected(true); + } + // Sort by language. + langLView->setSorting(0); + langLView->sort(); + // Display the box in a dialog. + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Select Languages"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_widget, + "SelectLanguage_dlg", + true, + true); + dlg->setMainWidget(hBox); + dlg->setHelp("", "kttsd"); + dlg->setInitialSize(QSize(300, 500), false); + int dlgResult = dlg->exec(); + languageCode = QString::null; + if (dlgResult == QDialog::Accepted) + { + m_languageCodeList.clear(); + QListViewItem* item = langLView->firstChild(); + while (item) + { + if (item->isSelected()) m_languageCodeList += item->text(1); + item = item->nextSibling(); + } + } + delete dlg; + // TODO: Also delete KListView and QHBox? + if (dlgResult != QDialog::Accepted) return; + language = ""; + for ( uint ndx=0; ndx < m_languageCodeList.count(); ++ndx) + { + if (!language.isEmpty()) language += ","; + language += KGlobal::locale()->twoAlphaToLanguageName(m_languageCodeList[ndx]); + } + m_widget->languageLineEdit->setText(language); + configChanged(); +} + +void SbdConf::slotLoadButton_clicked() +{ + // QString dataDir = KGlobal::dirs()->resourceDirs("data").last() + "/kttsd/stringreplacer/"; + QString dataDir = KGlobal::dirs()->findAllResources("data", "kttsd/sbd/").last(); + QString filename = KFileDialog::getOpenFileName( + dataDir, + "*rc|SBD Config (*rc)", + m_widget, + "sbd_loadfile"); + if ( filename.isEmpty() ) return; + KConfig* cfg = new KConfig( filename, true, false, 0 ); + load( cfg, "Filter" ); + delete cfg; + configChanged(); +} + +void SbdConf::slotSaveButton_clicked() +{ + QString filename = KFileDialog::getSaveFileName( + KGlobal::dirs()->saveLocation( "data" ,"kttsd/sbd/", false ), + "*rc|SBD Config (*rc)", + m_widget, + "sbd_savefile"); + if ( filename.isEmpty() ) return; + KConfig* cfg = new KConfig( filename, false, false, 0 ); + save( cfg, "Filter" ); + delete cfg; +} + +void SbdConf::slotClearButton_clicked() +{ + m_widget->nameLineEdit->setText( QString::null ); + m_widget->reLineEdit->setText( QString::null ); + m_widget->sbLineEdit->setText( QString::null ); + m_languageCodeList.clear(); + m_widget->languageLineEdit->setText( QString::null ); + m_widget->appIdLineEdit->setText( QString::null ); + configChanged(); +} diff --git a/kttsd/filters/sbd/sbdconf.h b/kttsd/filters/sbd/sbdconf.h new file mode 100644 index 0000000..131a759 --- /dev/null +++ b/kttsd/filters/sbd/sbdconf.h @@ -0,0 +1,139 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Standard Sentence Boundary Detection Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _SBDCONF_H_ +#define _SBDCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "filterconf.h" + +// SBD includes. +#include "sbdconfwidget.h" + +class KDialogBase; +class EditReplacementWidget; + +class SbdConf : public KttsFilterConf +{ + Q_OBJECT + + public: + /** + * Constructor + */ + SbdConf( QWidget *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor + */ + virtual ~SbdConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ + virtual QString userPlugInName(); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + private slots: + void slotReButton_clicked(); + void slotLanguageBrowseButton_clicked(); + void slotLoadButton_clicked(); + void slotSaveButton_clicked(); + void slotClearButton_clicked(); + + private: + + // Configuration Widget. + SbdConfWidget* m_widget; + // True if kdeutils Regular Expression Editor is installed. + bool m_reEditorInstalled; + // Language Code. + QStringList m_languageCodeList; +}; + +#endif //_SBDCONF_H_ diff --git a/kttsd/filters/sbd/sbdconfwidget.ui b/kttsd/filters/sbd/sbdconfwidget.ui new file mode 100644 index 0000000..17cd525 --- /dev/null +++ b/kttsd/filters/sbd/sbdconfwidget.ui @@ -0,0 +1,363 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>SbdConfWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>SbdConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>544</width> + <height>315</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string><b>WARNING: This filter is a key component of the KTTS system. Please read the KTTS Handbook before modifying these settings.</b></string> + </property> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>nameLabel</cstring> + </property> + <property name="text"> + <string>&Name:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The name of this filter. Enter any descriptive name you like.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>nameLineEdit</cstring> + </property> + <property name="text"> + <string>Standard Sentence Boundary Detector</string> + </property> + <property name="whatsThis" stdset="0"> + <string>The name of this filter. Enter any descriptive name you like.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout17</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>reLabel</cstring> + </property> + <property name="text"> + <string>&Sentence boundary regular expression:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The regular expression that detects boundaries between sentences in text jobs.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>reLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The regular expression that detects boundaries between sentences in text jobs.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>reButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="3" column="0"> + <property name="name"> + <cstring>layout17_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>sbLabel</cstring> + </property> + <property name="text"> + <string>&Replacement sentence boundary:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This string replaces the matched regular expression. <b>Important</b>: must end with tab (\t).</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>sbLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This string replaces the matched regular expression. <b>Important</b>: must end with tab (\t).</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox" row="4" column="0"> + <property name="name"> + <cstring>applyGroupBox</cstring> + </property> + <property name="title"> + <string>Apply This &Filter When</string> + </property> + <property name="whatsThis" stdset="0"> + <string>These settings determines when the filter is applied to text.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>languageLabel</cstring> + </property> + <property name="text"> + <string>&Language is:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>languageLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank the filter applies to all text jobs of any language.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>appIdLabel</cstring> + </property> + <property name="text"> + <string>Application &ID contains:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter a DCOP Application ID. This filter will only apply to text jobs queued by that application. You may enter more than one ID separated by commas. If blank, this filter applies to text jobs queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit"> + <property name="name"> + <cstring>languageLineEdit</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank the filter applies to all text jobs of any language.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>languageBrowseButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select one or more languages. This filter will be applied to text jobs of those languages.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Enter a DCOP Application ID. This filter will only apply to text jobs queued by that application. You may enter more than one ID separated by commas. Use <b>knotify</b> to match all messages sent as KDE notifications. If blank, this filter applies to text jobs queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</qt></string> + </property> + </widget> + </vbox> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="5" column="0"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>loadButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Load...</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to load a Sentence Boundary Detection configuration from a file.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>saveButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Sa&ve...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to save this Sentence Boundary Detection configuration to a file.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>clearButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Clea&r</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to clear everything.</string> + <comment>What's this text</comment> + </property> + </widget> + </hbox> + </widget> + </grid> +</widget> +<tabstops> + <tabstop>nameLineEdit</tabstop> + <tabstop>reLineEdit</tabstop> + <tabstop>reButton</tabstop> + <tabstop>sbLineEdit</tabstop> + <tabstop>languageLineEdit</tabstop> + <tabstop>languageBrowseButton</tabstop> + <tabstop>appIdLineEdit</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/filters/sbd/sbdplugin.cpp b/kttsd/filters/sbd/sbdplugin.cpp new file mode 100644 index 0000000..ab14080 --- /dev/null +++ b/kttsd/filters/sbd/sbdplugin.cpp @@ -0,0 +1,33 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so Sentence Boundary Detection Filter can be used + as plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "sbdconf.h" +#include "sbdproc.h" + +typedef K_TYPELIST_2( SbdProc, SbdConf ) SbdPlugin; +K_EXPORT_COMPONENT_FACTORY( libkttsd_sbdplugin, + KGenericFactory<SbdPlugin>("kttsd_sbd") ) + diff --git a/kttsd/filters/sbd/sbdproc.cpp b/kttsd/filters/sbd/sbdproc.cpp new file mode 100644 index 0000000..cdc80d9 --- /dev/null +++ b/kttsd/filters/sbd/sbdproc.cpp @@ -0,0 +1,784 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Sentence Boundary Detection Filter class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qregexp.h> +#include <qdom.h> +#include <qapplication.h> + +// KDE includes. +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> + +// KTTS includes. +#include "utils.h" +#include "talkercode.h" + +// SdbProc includes. +#include "sbdproc.h" + +/** + * Constructor. + */ +SbdThread::SbdThread( QObject *parent, const char *name ) : + QObject( parent, name ), + QThread() +{ +} + +/** + * Destructor. + */ +/*virtual*/ SbdThread::~SbdThread() +{ +} + +/** + * Get/Set text being processed. + */ +void SbdThread::setText( const QString& text ) { m_text = text; } +QString SbdThread::text() { return m_text; } + +/** + * Set/Get TalkerCode. + */ +void SbdThread::setTalkerCode( TalkerCode* talkerCode ) { m_talkerCode = talkerCode; } +TalkerCode* SbdThread::talkerCode() { return m_talkerCode; } + +/** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ +void SbdThread::setSbRegExp( const QString& re ) { m_re = re; } + +/** + * The configured Sentence Boundary Regular Expression. + * + * @param re The sentence delimiter regular expression. + */ +void SbdThread::setConfiguredSbRegExp( const QString& re ) { m_configuredRe = re; } + +/** + * The configured Sentence Boundary that replaces SB regular expression. + * + * @param sb The sentence boundary replacement. + * + */ +void SbdThread::setConfiguredSentenceBoundary( const QString& sb ) { m_configuredSentenceBoundary = sb; } + +/** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ +void SbdThread::setWasModified(bool wasModified) { m_wasModified = wasModified; } +bool SbdThread::wasModified() { return m_wasModified; } + +// Given a tag name, returns SsmlElemType. +SbdThread::SsmlElemType SbdThread::tagToSsmlElemType( const QString tagName ) +{ + if ( tagName == "speak" ) return etSpeak; + if ( tagName == "voice" ) return etVoice; + if ( tagName == "prosody" ) return etProsody; + if ( tagName == "emphasis" ) return etEmphasis; + if ( tagName == "break" ) return etBreak; + if ( tagName == "s" ) return etPS; + if ( tagName == "p" ) return etPS; + return etNotSsml; +} + +// Parses an SSML element, pushing current settings onto the context stack. +void SbdThread::pushSsmlElem( SsmlElemType et, const QDomElement& elem ) +{ + // TODO: Need to convert relative values into absolute values and also convert + // only to values recognized by SSML2SABLE stylesheet. Either that or enhance all + // the synth stylesheets. + QDomNamedNodeMap attrList = elem.attributes(); + int attrCount = attrList.count(); + switch ( et ) + { + case etSpeak: { + SpeakElem e = m_speakStack.top(); + for ( int ndx=0; ndx < attrCount; ++ndx ) + { + QDomAttr a = attrList.item( ndx ).toAttr(); + if ( a.name() == "lang" ) e.lang = a.value(); + } + m_speakStack.push( e ); + break; } + case etVoice: { + VoiceElem e = m_voiceStack.top(); + // TODO: Since Festival chokes on <voice> tags, don't output them at all. + // This means we can't support voice changes, and probably more irritatingly, + // gender changes either. + m_voiceStack.push( e ); + break; } + case etProsody: { + ProsodyElem e = m_prosodyStack.top(); + for ( int ndx=0; ndx < attrCount; ++ndx ) + { + QDomAttr a = attrList.item( ndx ).toAttr(); + if ( a.name() == "pitch" ) e.pitch = a.value(); + if ( a.name() == "contour" ) e.contour = a.value(); + if ( a.name() == "range" ) e.range = a.value(); + if ( a.name() == "rate" ) e.rate = a.value(); + if ( a.name() == "duration" ) e.duration = a.value(); + if ( a.name() == "volume" ) e.volume = a.value(); + } + m_prosodyStack.push( e ); + break; } + case etEmphasis: { + EmphasisElem e = m_emphasisStack.top(); + for ( int ndx=0; ndx < attrCount; ++ndx ) + { + QDomAttr a = attrList.item( ndx ).toAttr(); + if ( a.name() == "level" ) e.level = a.value(); + } + m_emphasisStack.push( e ); + break; } + case etPS: { + PSElem e = m_psStack.top(); + for ( int ndx=0; ndx < attrCount; ++ndx ) + { + QDomAttr a = attrList.item( ndx ).toAttr(); + if ( a.name() == "lang" ) e.lang = a.value(); + } + m_psStack.push( e ); + break; } + default: break; + } +} + +// Given an attribute name and value, constructs an XML representation of the attribute, +// i.e., name="value". +QString SbdThread::makeAttr( const QString& name, const QString& value ) +{ + if ( value.isEmpty() ) return QString::null; + return " " + name + "=\"" + value + "\""; +} + +// Returns an XML representation of an SSML tag from the top of the context stack. +QString SbdThread::makeSsmlElem( SsmlElemType et ) +{ + QString s; + QString a; + switch ( et ) + { + // Must always output speak tag, otherwise kttsd won't think each sentence is SSML. + // For all other tags, only output the tag if it contains at least one attribute. + case etSpeak: { + SpeakElem e = m_speakStack.top(); + s = "<speak"; + if ( !e.lang.isEmpty() ) s += makeAttr( "lang", e.lang ); + s += ">"; + break; } + case etVoice: { + VoiceElem e = m_voiceStack.top(); + a += makeAttr( "lang", e.lang ); + a += makeAttr( "gender", e.gender ); + a += makeAttr( "age", QString::number(e.age) ); + a += makeAttr( "name", e.name ); + a += makeAttr( "variant", e.variant ); + if ( !a.isEmpty() ) s = "<voice" + a + ">"; + break; } + case etProsody: { + ProsodyElem e = m_prosodyStack.top(); + a += makeAttr( "pitch", e.pitch ); + a += makeAttr( "contour", e.contour ); + a += makeAttr( "range", e.range ); + a += makeAttr( "rate", e.rate ); + a += makeAttr( "duration", e.duration ); + a += makeAttr( "volume", e.volume ); + if ( !a.isEmpty() ) s = "<prosody" + a + ">"; + break; } + case etEmphasis: { + EmphasisElem e = m_emphasisStack.top(); + a += makeAttr( "level", e.level ); + if ( !a.isEmpty() ) s = "<emphasis" + a + ">"; + break; } + case etPS: { + break; } + default: break; + } + return s; +} + +// Pops element from the indicated context stack. +void SbdThread::popSsmlElem( SsmlElemType et ) +{ + switch ( et ) + { + case etSpeak: m_speakStack.pop(); break; + case etVoice: m_voiceStack.pop(); break; + case etProsody: m_prosodyStack.pop(); break; + case etEmphasis: m_emphasisStack.pop(); break; + case etPS: m_psStack.pop(); break; + default: break; + } +} + +// Returns an XML representation of a break element. +QString SbdThread::makeBreakElem( const QDomElement& e ) +{ + QString s = "<break"; + QDomNamedNodeMap attrList = e.attributes(); + int attrCount = attrList.count(); + for ( int ndx=0; ndx < attrCount; ++ndx ) + { + QDomAttr a = attrList.item( ndx ).toAttr(); + s += makeAttr( a.name(), a.value() ); + } + s += ">"; + return s; +} + +// Converts a text fragment into a CDATA section. +QString SbdThread::makeCDATA( const QString& text ) +{ + QString s = "<![CDATA["; + s += text; + s += "]]>"; + return s; +} + +// Returns an XML representation of an utterance node consisting of voice, +// prosody, and emphasis elements. +QString SbdThread::makeSentence( const QString& text ) +{ + QString s; + QString v = makeSsmlElem( etVoice ); + QString p = makeSsmlElem( etProsody ); + QString e = makeSsmlElem( etEmphasis ); + // TODO: Lang settings from psStack. + if ( !v.isEmpty() ) s += v; + if ( !p.isEmpty() ) s += p; + if ( !e.isEmpty() ) s += e; + // Escape ampersands and less thans. + QString newText = text; + newText.replace(QRegExp("&(?!amp;)"), "&"); + newText.replace(QRegExp("<(?!lt;)"), "<"); + s += newText; + if ( !e.isEmpty() ) s += "</emphasis>"; + if ( !p.isEmpty() ) s += "</prosody>"; + if ( !v.isEmpty() ) s += "</voice>"; + return s; +} + +// Starts a sentence by returning a speak tag. +QString SbdThread::startSentence() +{ + if ( m_sentenceStarted ) return QString::null; + QString s; + s += makeSsmlElem( etSpeak ); + m_sentenceStarted = true; + return s; +} + +// Ends a sentence and appends a Tab. +QString SbdThread::endSentence() +{ + if ( !m_sentenceStarted ) return QString::null; + QString s = "</speak>"; + s += "\t"; + m_sentenceStarted = false; + return s; +} + +// Parses a node of the SSML tree and recursively parses its children. +// Returns the filtered text with each sentence a complete ssml tree. +QString SbdThread::parseSsmlNode( QDomNode& n, const QString& re ) +{ + QString result; + switch ( n.nodeType() ) + { + case QDomNode::ElementNode: { // = 1 + QDomElement e = n.toElement(); + QString tagName = e.tagName(); + SsmlElemType et = tagToSsmlElemType( tagName ); + switch ( et ) + { + case etSpeak: + case etVoice: + case etProsody: + case etEmphasis: + case etPS: + { + pushSsmlElem( et, e ); + QDomNode t = n.firstChild(); + while ( !t.isNull() ) + { + result += parseSsmlNode( t, re ); + t = t.nextSibling(); + } + popSsmlElem( et ); + if ( et == etPS ) + result += endSentence(); + break; + } + case etBreak: + { + // Break elements are empty. + result += makeBreakElem( e ); + } + // Ignore any elements we don't recognize. + default: break; + } + break; } + case QDomNode::AttributeNode: { // = 2 + break; } + case QDomNode::TextNode: { // = 3 + QString s = parsePlainText( n.toText().data(), re ); + // QString d = s; + // d.replace("\t", "\\t"); + // kdDebug() << "SbdThread::parseSsmlNode: parsedPlainText = [" << d << "]" << endl; + QStringList sentenceList = QStringList::split( '\t', s, false ); + int lastNdx = sentenceList.count() - 1; + for ( int ndx=0; ndx < lastNdx; ++ndx ) + { + result += startSentence(); + result += makeSentence( sentenceList[ndx] ); + result += endSentence(); + } + // Only output sentence boundary if last text fragment ended a sentence. + if ( lastNdx >= 0 ) + { + result += startSentence(); + result += makeSentence( sentenceList[lastNdx] ); + if ( s.endsWith( "\t" ) ) result += endSentence(); + } + break; } + case QDomNode::CDATASectionNode: { // = 4 + QString s = parsePlainText( n.toCDATASection().data(), re ); + QStringList sentenceList = QStringList::split( '\t', s, false ); + int lastNdx = sentenceList.count() - 1; + for ( int ndx=0; ndx < lastNdx; ++ndx ) + { + result += startSentence(); + result += makeSentence( makeCDATA( sentenceList[ndx] ) ); + result += endSentence(); + } + // Only output sentence boundary if last text fragment ended a sentence. + if ( lastNdx >= 0 ) + { + result += startSentence(); + result += makeSentence( makeCDATA( sentenceList[lastNdx] ) ); + if ( s.endsWith( "\t" ) ) result += endSentence(); + } + break; } + case QDomNode::EntityReferenceNode: { // = 5 + break; } + case QDomNode::EntityNode: { // = 6 + break; } + case QDomNode::ProcessingInstructionNode: { // = 7 + break; } + case QDomNode::CommentNode: { // = 8 + break; } + case QDomNode::DocumentNode: { // = 9 + break; } + case QDomNode::DocumentTypeNode: { // = 10 + break; } + case QDomNode::DocumentFragmentNode: { // = 11 + break; } + case QDomNode::NotationNode: { // = 12 + break; } + case QDomNode::BaseNode: { // = 21 + break; } + case QDomNode::CharacterDataNode: { // = 22 + break; } + } + return result; +} + +// Parses Ssml. +QString SbdThread::parseSsml( const QString& inputText, const QString& re ) +{ + QRegExp sentenceDelimiter = QRegExp( re ); + + // Read the text into xml dom tree. + QDomDocument doc( "" ); + // If an error occurs parsing the SSML, return "invalid S S M L". + if ( !doc.setContent( inputText ) ) return i18n("Invalid S S M L."); + + // Set up context stacks and set defaults for all element attributes. + m_speakStack.clear(); + m_voiceStack.clear(); + m_prosodyStack.clear(); + m_emphasisStack.clear(); + m_psStack.clear(); + SpeakElem se = { "" }; + m_speakStack.push ( se ); + VoiceElem ve = {"", "neutral", 40, "", ""}; + m_voiceStack.push( ve ); + ProsodyElem pe = { "medium", "", "medium", "medium", "", "medium" }; + m_prosodyStack.push( pe ); + EmphasisElem em = { "" }; + m_emphasisStack.push( em ); + PSElem pse = { "" }; + m_psStack.push ( pse ); + + // This flag is used to close out a previous sentence. + m_sentenceStarted = false; + + // Get the root element (speak) and recursively process its children. + QDomElement docElem = doc.documentElement(); + QDomNode n = docElem.firstChild(); + QString ssml = parseSsmlNode( docElem, re ); + + // Close out last sentence. + if ( m_sentenceStarted ) ssml += "</speak>"; + + return ssml; +} + +// Parses code. Each newline is converted into a tab character (\t). +QString SbdThread::parseCode( const QString& inputText ) +{ + QString temp = inputText; + // Replace newlines with tabs. + temp.replace("\n","\t"); + // Remove leading spaces. + temp.replace(QRegExp("\\t +"), "\t"); + // Remove trailing spaces. + temp.replace(QRegExp(" +\\t"), "\t"); + // Remove blank lines. + temp.replace(QRegExp("\t\t+"),"\t"); + return temp; +} + +// Parses plain text. +QString SbdThread::parsePlainText( const QString& inputText, const QString& re ) +{ + // kdDebug() << "SbdThread::parsePlainText: parsing " << inputText << " with re " << re << endl; + QRegExp sentenceDelimiter = QRegExp( re ); + QString temp = inputText; + // Replace sentence delimiters with tab. + temp.replace(sentenceDelimiter, m_configuredSentenceBoundary); + // Replace remaining newlines with spaces. + temp.replace("\n"," "); + temp.replace("\r"," "); + // Remove leading spaces. + temp.replace(QRegExp("\\t +"), "\t"); + // Remove trailing spaces. + temp.replace(QRegExp(" +\\t"), "\t"); + // Remove blank lines. + temp.replace(QRegExp("\t\t+"),"\t"); + return temp; +} + +// This is where the real work takes place. +/*virtual*/ void SbdThread::run() +{ + // kdDebug() << "SbdThread::run: processing text = " << m_text << endl; + + // TODO: Determine if we should do anything or not. + m_wasModified = true; + + // Determine what kind of input text we are dealing with. + int textType; + if ( KttsUtils::hasRootElement( m_text, "speak" ) ) + textType = ttSsml; + else + { + // Examine just the first 500 chars to see if it is code. + QString p = m_text.left( 500 ); + if ( p.contains( QRegExp( "(/\\*)|(if\\b\\()|(^#include\\b)" ) ) ) + textType = ttCode; + else + textType = ttPlain; + } + + // If application specified a sentence delimiter regular expression, use that, + // otherwise use configured default. + QString re = m_re; + if ( re.isEmpty() ) re = m_configuredRe; + + // Replace spaces, tabs, and formfeeds with a single space. + m_text.replace(QRegExp("[ \\t\\f]+"), " "); + + // Perform the filtering based on type of text. + switch ( textType ) + { + case ttSsml: + m_text = parseSsml( m_text, re ); + break; + + case ttCode: + m_text = parseCode( m_text ); + break; + + case ttPlain: + m_text = parsePlainText( m_text, re); + break; + } + + // Clear app-specified sentence delimiter. App must call setSbRegExp for each conversion. + m_re = QString::null; + + // kdDebug() << "SbdThread::run: filtered text = " << m_text << endl; + + // Result is in m_text; + + // Post an event. We need to emit filterFinished signal, but not from the + // separate thread. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 301); + QApplication::postEvent(this, ev); +} + +bool SbdThread::event ( QEvent * e ) +{ + if ( e->type() == (QEvent::User + 301) ) + { + // kdDebug() << "SbdThread::event: emitting filteringFinished signal." << endl; + emit filteringFinished(); + return true; + } + else return false; +} + +// ---------------------------------------------------------------------------- + +/** + * Constructor. + */ +SbdProc::SbdProc( QObject *parent, const char *name, const QStringList& /*args*/) : + KttsFilterProc(parent, name) +{ + // kdDebug() << "SbdProc::SbdProc: Running" << endl; + m_sbdThread = new SbdThread( parent, *name + "_thread" ); + connect( m_sbdThread, SIGNAL(filteringFinished()), this, SLOT(slotSbdThreadFilteringFinished()) ); +} + +/** + * Destructor. + */ +SbdProc::~SbdProc() +{ + // kdDebug() << "SbdProc::~SbdProc: Running" << endl; + if ( m_sbdThread ) + { + if ( m_sbdThread->running() ) + { + m_sbdThread->terminate(); + m_sbdThread->wait(); + } + delete m_sbdThread; + } +} + +/** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ +bool SbdProc::init(KConfig* config, const QString& configGroup){ + // kdDebug() << "PlugInProc::init: Running" << endl; + config->setGroup( configGroup ); +// m_configuredRe = config->readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])\\s|(\\n *\\n)" ); + m_configuredRe = config->readEntry( "SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))" ); + m_sbdThread->setConfiguredSbRegExp( m_configuredRe ); + QString sb = config->readEntry( "SentenceBoundary", "\\1\t" ); + sb.replace( "\\t", "\t" ); + m_sbdThread->setConfiguredSentenceBoundary( sb ); + m_appIdList = config->readListEntry( "AppID" ); + m_languageCodeList = config->readListEntry( "LanguageCodes" ); + return true; +} + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ +/*virtual*/ bool SbdProc::isSBD() { return true; } + +/** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ +/*virtual*/ bool SbdProc::supportsAsync() { return true; } + +/** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ +/*virtual*/ QString SbdProc::convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId) +{ + if ( asyncConvert( inputText, talkerCode, appId) ) + { + waitForFinished(); + // kdDebug() << "SbdProc::convert: returning " << getOutput() << endl; + return getOutput(); + } else return inputText; +} + +/** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ +/*virtual*/ bool SbdProc::asyncConvert(const QString& inputText, TalkerCode* talkerCode, + const QCString& appId) +{ + m_sbdThread->setWasModified( false ); + // If language doesn't match, return input unmolested. + if ( !m_languageCodeList.isEmpty() ) + { + QString languageCode = talkerCode->languageCode(); + // kdDebug() << "StringReplacerProc::convert: converting " << inputText << + // " if language code " << languageCode << " matches " << m_languageCodeList << endl; + if ( !m_languageCodeList.contains( languageCode ) ) + { + if ( !talkerCode->countryCode().isEmpty() ) + { + languageCode += '_' + talkerCode->countryCode(); + // kdDebug() << "StringReplacerProc::convert: converting " << inputText << + // " if language code " << languageCode << " matches " << m_languageCodeList << endl; + if ( !m_languageCodeList.contains( languageCode ) ) return false; + } else return false; + } + } + // If appId doesn't match, return input unmolested. + if ( !m_appIdList.isEmpty() ) + { + // kdDebug() << "SbdProc::convert: converting " << inputText << " if appId " + // << appId << " matches " << m_appIdList << endl; + bool found = false; + QString appIdStr = appId; + for ( uint ndx=0; ndx < m_appIdList.count(); ++ndx ) + { + if ( appIdStr.contains(m_appIdList[ndx]) ) + { + found = true; + break; + } + } + if ( !found ) return false; + } + m_sbdThread->setText( inputText ); + m_sbdThread->setTalkerCode( talkerCode ); + m_state = fsFiltering; + m_sbdThread->start(); + return true; +} + +/** + * Waits for a previous call to asyncConvert to finish. + */ +/*virtual*/ void SbdProc::waitForFinished() +{ + if ( m_sbdThread->running() ) + { + // kdDebug() << "SbdProc::waitForFinished: waiting" << endl; + m_sbdThread->wait(); + // kdDebug() << "SbdProc::waitForFinished: finished waiting" << endl; + m_state = fsFinished; + } +} + +/** + * Returns the state of the Filter. + */ +/*virtual*/ int SbdProc::getState() { return m_state; } + +/** + * Returns the filtered output. + */ +/*virtual*/ QString SbdProc::getOutput() { return m_sbdThread->text(); } + +/** + * Acknowledges the finished filtering. + */ +/*virtual*/ void SbdProc::ackFinished() +{ + m_state = fsIdle; + m_sbdThread->setText( QString::null ); +} + +/** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ +/*virtual*/ void SbdProc::stopFiltering() +{ + if ( m_sbdThread->running() ) + { + m_sbdThread->terminate(); + m_sbdThread->wait(); + delete m_sbdThread; + m_sbdThread = new SbdThread(); + m_sbdThread->setConfiguredSbRegExp( m_configuredRe ); + connect( m_sbdThread, SIGNAL(filteringFinished()), this, SLOT(slotSbdThreadFilteringFinished()) ); + m_state = fsIdle; + emit filteringStopped(); + } +} + +/** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ +/*virtual*/ bool SbdProc::wasModified() { return m_sbdThread->wasModified(); } + +/** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ +/*virtual*/ void SbdProc::setSbRegExp(const QString& re) { m_sbdThread->setSbRegExp( re ); } + +// Received when SBD Thread finishes. +void SbdProc::slotSbdThreadFilteringFinished() +{ + m_state = fsFinished; + // kdDebug() << "SbdProc::slotSbdThreadFilteringFinished: emitting filterFinished signal." << endl; + emit filteringFinished(); +} + +#include "sbdproc.moc" diff --git a/kttsd/filters/sbd/sbdproc.h b/kttsd/filters/sbd/sbdproc.h new file mode 100644 index 0000000..6c181d5 --- /dev/null +++ b/kttsd/filters/sbd/sbdproc.h @@ -0,0 +1,366 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Sentence Boundary Detection (SBD) Filter class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +/****************************************************************************** + + This class performs three kinds of SBD: + 1. If the text is SSML, generates new SSML where the prosody and voice + tags are fully specified for each sentence. This allows user to + advance or rewind by sentence without losing SSML context. + Input is considered to be SSML if the top-level element is a + <speak> tag. + 2. If the text is code, each line is considered to be a sentence. + Input is considered to be code if any of the following strings are + detected: + slash asterisk + if left-paren + pound include + 3. If the text is plain text, performs SBD using specified Regular + Expression. + + Text is output with tab characters (\t) separating sentences. + + ******************************************************************************/ + +#ifndef _SBDPROC_H_ +#define _SBDPROC_H_ + +// Qt includes. +#include <qobject.h> +#include <qstringlist.h> +#include <qthread.h> +#include <qvaluestack.h> +#include <qevent.h> + +// KTTS includes. +#include "filterproc.h" + +class TalkerCode; +class KConfig; +class QDomElement; +class QDomNode; + +class SbdThread: public QObject, public QThread +{ + Q_OBJECT + + public: + /** + * Constructor. + */ + SbdThread( QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + */ + virtual ~SbdThread(); + + /** + * Get/Set text being processed. + */ + void setText( const QString& text ); + QString text(); + + /** + * Set/Get TalkerCode. + */ + void setTalkerCode( TalkerCode* talkerCode ); + TalkerCode* talkerCode(); + + /** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ + void setSbRegExp( const QString& re ); + + /** + * The configured Sentence Boundary Regular Expression. + * + * @param re The sentence delimiter regular expression. + */ + void setConfiguredSbRegExp( const QString& re ); + + /** + * The configured Sentence Boundary that replaces SB regular expression. + * + * @param sb The sentence boundary replacement. + * + */ + void setConfiguredSentenceBoundary( const QString& sb ); + + /** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ + void setWasModified(bool wasModified); + bool wasModified(); + + signals: + void filteringFinished(); + + protected: + virtual void run(); + virtual bool event ( QEvent * e ); + + private: + enum TextType { + ttSsml, // SSML + ttCode, // Code + ttPlain // Plain text + }; + + enum SsmlElemType { + etSpeak, + etVoice, + etProsody, + etEmphasis, + etPS, // Paragraph or sentence (we don't care). + etBreak, + etNotSsml + }; + + // Speak Element. + struct SpeakElem { + QString lang; // xml:lang="en". + }; + + // Voice Element. + struct VoiceElem { + QString lang; // xml:lang="en". + QString gender; // "male", "female", or "neutral". + uint age; // Age in years. + QString name; // Synth-specific voice name. + QString variant; // Ignored. + }; + + // Prosody Element. + struct ProsodyElem { + QString pitch; // "x-low", "low", "medium", "high", "x-high", "default". + QString contour; // Pitch contour (ignored). + QString range; // "x-low", "low", "medium", "high", "x-high", "default". + QString rate; // "x-slow", "slow", "medium", "fast", "x-fast", "default". + QString duration; // Ignored. + QString volume; // "silent", "x-soft", "soft", "medium", "load", "x-load", "default". + }; + + // Emphasis Element. + struct EmphasisElem { + QString level; // "strong", "moderate", "none" and "reduced" + }; + + // Break Element. + struct BreakElem { + QString strength; // "x-weak", "weak", "medium" (default value), "strong", + // or "x-strong", "none" + QString time; // Ignored. + }; + + // Paragraph and Sentence Elements. + struct PSElem { + QString lang; // xml:lang="en". + }; + + // Given a tag name, returns SsmlElemType. + SsmlElemType tagToSsmlElemType(const QString tagName); + // Parses an SSML element, pushing current settings onto the context stack. + void pushSsmlElem( SsmlElemType et, const QDomElement& elem ); + // Given an attribute name and value, constructs an XML representation of the attribute, + // i.e., name="value". + QString makeAttr( const QString& name, const QString& value ); + // Returns an XML representation of an SSML tag from the top of the context stack. + QString makeSsmlElem( SsmlElemType et ); + // Pops element from the indicated context stack. + void popSsmlElem( SsmlElemType et ); + QString makeBreakElem( const QDomElement& e ); + // Converts a text fragment into a CDATA section. + QString makeCDATA( const QString& text ); + // Returns an XML representation of an utterance node consisting of voice, + // prosody, and emphasis elements. + QString makeSentence( const QString& text ); + // Starts a sentence by returning a speak tag. + QString startSentence(); + // Ends a sentence and appends a Tab. + QString endSentence(); + // Parses a node of the SSML tree and recursively parses its children. + // Returns the filtered text with each sentence a complete ssml tree. + QString parseSsmlNode( QDomNode& n, const QString& re ); + + // Parses Ssml. + QString parseSsml( const QString& inputText, const QString& re ); + // Parses code. Each newline is converted into a tab character (\t). + QString parseCode( const QString& inputText ); + // Parses plain text. + QString parsePlainText( const QString& inputText, const QString& re ); + + // Context stacks. + QValueStack<SpeakElem> m_speakStack; + QValueStack<VoiceElem> m_voiceStack; + QValueStack<ProsodyElem> m_prosodyStack; + QValueStack<EmphasisElem> m_emphasisStack; + QValueStack<PSElem> m_psStack; + + // The text being processed. + QString m_text; + // Talker Code. + TalkerCode* m_talkerCode; + // Configured default Sentence Delimiter regular expression. + QString m_configuredRe; + // Configured Sentence Boundary replacement expression. + QString m_configuredSentenceBoundary; + // Application-specified Sentence Delimiter regular expression (if any). + QString m_re; + // False if input was not modified. + bool m_wasModified; + // True when a sentence has been started. + bool m_sentenceStarted; +}; + +class SbdProc : virtual public KttsFilterProc +{ + Q_OBJECT + + public: + /** + * Constructor. + */ + SbdProc( QObject *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor. + */ + virtual ~SbdProc(); + + /** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init( KConfig *config, const QString &configGroup ); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ + virtual bool supportsAsync(); + + /** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ + virtual QString convert( const QString& inputText, TalkerCode* talkerCode, const QCString& appId ); + + /** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ + virtual bool asyncConvert( const QString& inputText, TalkerCode* talkerCode, const QCString& appId ); + + /** + * Waits for a previous call to asyncConvert to finish. + */ + virtual void waitForFinished(); + + /** + * Returns the state of the Filter. + */ + virtual int getState(); + + /** + * Returns the filtered output. + */ + virtual QString getOutput(); + + /** + * Acknowledges the finished filtering. + */ + virtual void ackFinished(); + + /** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ + virtual void stopFiltering(); + + /** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ + virtual bool wasModified(); + + /** + * Set Sentence Boundary Regular Expression. + */ + virtual void setSbRegExp( const QString& re ); + + private slots: + // Received when SBD Thread finishes. + void slotSbdThreadFilteringFinished(); + + private: + // If not empty, apply filters only to apps using talkers speaking these language codes. + QStringList m_languageCodeList; + // If not empty, apply filter only to apps containing this string. + QStringList m_appIdList; + // SBD Thread Object. + SbdThread* m_sbdThread; + // State. + int m_state; + // Configured default Sentence Delimiter regular expression. + QString m_configuredRe; +}; + +#endif // _SBDPROC_H_ diff --git a/kttsd/filters/sbd/standard_sbdrc b/kttsd/filters/sbd/standard_sbdrc new file mode 100644 index 0000000..76037a5 --- /dev/null +++ b/kttsd/filters/sbd/standard_sbdrc @@ -0,0 +1,6 @@ +[Filter] +AppID= +LanguageCodes= +SentenceBoundary=\\1\\t +SentenceDelimiterRegExp=([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n)) +UserFilterName=Standard Sentence Boundary Detector diff --git a/kttsd/filters/stringreplacer/Makefile.am b/kttsd/filters/stringreplacer/Makefile.am new file mode 100644 index 0000000..b52085c --- /dev/null +++ b/kttsd/filters/stringreplacer/Makefile.am @@ -0,0 +1,34 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_stringreplacerplugin.la + +libkttsd_stringreplacerplugin_la_SOURCES = \ + stringreplacerconfwidget.ui \ + editreplacementwidget.ui \ + stringreplacerconf.cpp \ + stringreplacerproc.cpp \ + stringreplacerplugin.cpp +libkttsd_stringreplacerplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_stringreplacerplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_stringreplacerplugin.desktop +servicesdir = $(kde_servicesdir) + +# Install data files. +stringreplacerdatadir = $(kde_datadir)/kttsd/stringreplacer/ +stringreplacerdata_DATA = \ + abbreviations.xml \ + chat.xml \ + chat-de.xml \ + emoticons.xml \ + polish_festival_fixes.xml \ + polish_festival_unspeakables.xml \ + kmail.xml \ + festival_unspeakable_chars.xml \ + qt2plaintext.xml + +noinst_HEADERS = stringreplacerconfwidget.h editreplacementwidget.h diff --git a/kttsd/filters/stringreplacer/abbreviations.xml b/kttsd/filters/stringreplacer/abbreviations.xml new file mode 100644 index 0000000..d04b7aa --- /dev/null +++ b/kttsd/filters/stringreplacer/abbreviations.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wordlist> + <name>Abbreviations</name> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[TODO]]></match> + <subst><![CDATA[To Do]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[CPU]]></match> + <subst><![CDATA[C P U]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[DCOP]]></match> + <subst><![CDATA[D Cop]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[IP]]></match> + <subst><![CDATA[I P]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[KDE]]></match> + <subst><![CDATA[K D E]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[KTTS]]></match> + <subst><![CDATA[K T T S]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[kttsd]]></match> + <subst><![CDATA[K T T S D]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[KTTSD]]></match> + <subst><![CDATA[K T T S D]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[TTS]]></match> + <subst><![CDATA[T T S]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/chat-de.xml b/kttsd/filters/stringreplacer/chat-de.xml new file mode 100644 index 0000000..801c144 --- /dev/null +++ b/kttsd/filters/stringreplacer/chat-de.xml @@ -0,0 +1,616 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wordlist> + <name>Internet Relay Chat and IM (Deutsch)</name> + <language-code>de</language-code> + <appid>knotify</appid> + <appid>kopete</appid> + <appid>kvirc</appid> + <appid>ksirc</appid> + <appid>konversation</appid> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<qt>]]></match> + <subst><![CDATA[ ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<([^>]+)>]]></match> + <subst><![CDATA[ \1 meint ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<br>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</qt>]]></match> + <subst><![CDATA[ ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\;\)]]></match> + <subst><![CDATA[ winkt]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\<]]></match> + <subst><![CDATA[ traurig]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\/]]></match> + <subst><![CDATA[stirnrunzeln]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\>]]></match> + <subst><![CDATA[ grinst]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\|]]></match> + <subst><![CDATA[ernsthaft]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\-\|]]></match> + <subst><![CDATA[ernsthaft]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\-\)]]></match> + <subst><![CDATA[ laechelt]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\'\(]]></match> + <subst><![CDATA[weint]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\(]]></match> + <subst><![CDATA[ traurig]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\)]]></match> + <subst><![CDATA[ laechelt]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\@]]></match> + <subst><![CDATA[ boese]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\$]]></match> + <subst><![CDATA[ schaemt sich]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\@\)]]></match> + <subst><![CDATA[ katzengesicht]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(6\)]]></match> + <subst><![CDATA[ teufelsfratze]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(a\)]]></match> + <subst><![CDATA[ engelsgesicht]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:D]]></match> + <subst><![CDATA[ grins]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(h\)]]></match> + <subst><![CDATA[ kuhl]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\so\_O]]></match> + <subst><![CDATA[ winkt]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:p]]></match> + <subst><![CDATA[ baeh]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(s\)]]></match> + <subst><![CDATA[ gaehn]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ad]]></match> + <subst><![CDATA[fuege hinzu]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[afaik]]></match> + <subst><![CDATA[soviel ich weiss]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[afk]]></match> + <subst><![CDATA[nicht an tastatur]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[asap]]></match> + <subst><![CDATA[so schnell wie moeglich]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[atm]]></match> + <subst><![CDATA[momentan]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ayt]]></match> + <subst><![CDATA[bist du da]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[b4]]></match> + <subst><![CDATA[vorher]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[b4n]]></match> + <subst><![CDATA[tschuess]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[bak]]></match> + <subst><![CDATA[zurueck]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[bbl]]></match> + <subst><![CDATA[komme spaeter wieder]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[bf]]></match> + <subst><![CDATA[freund]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[b/f]]></match> + <subst><![CDATA[freund]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[brb]]></match> + <subst><![CDATA[bin gleich wieder da]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[btw]]></match> + <subst><![CDATA[nebenbei bemerkt]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cu]]></match> + <subst><![CDATA[bis dann]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cul]]></match> + <subst><![CDATA[bis spaeter]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cul8r]]></match> + <subst><![CDATA[bis spaeter]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cya]]></match> + <subst><![CDATA[bis spaeter]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[d/l]]></match> + <subst><![CDATA[daunlohd]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[dvd]]></match> + <subst><![CDATA[die wie die]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[f2f]]></match> + <subst><![CDATA[von angesicht zu angesicht]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[fubar]]></match> + <subst><![CDATA[leuft trotz reparatur nicht]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[fyi]]></match> + <subst><![CDATA[zu deiner information]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[gf]]></match> + <subst><![CDATA[freundin]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[g/f]]></match> + <subst><![CDATA[freundin]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[gtg]]></match> + <subst><![CDATA[ich muss weck]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[hmm]]></match> + <subst><![CDATA[aehm]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[hth]]></match> + <subst><![CDATA[hoffe es hilft]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ic]]></match> + <subst><![CDATA[verstehe]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[iirc]]></match> + <subst><![CDATA[wenn ich recht erinnere]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imao]]></match> + <subst><![CDATA[meiner arroganten meinung nach]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imco]]></match> + <subst><![CDATA[meiner wohlueberlegten meinung nach]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ime]]></match> + <subst><![CDATA[meiner erfahrung nach]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imho]]></match> + <subst><![CDATA[meiner bescheidenen meinung nach]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imnsho]]></match> + <subst><![CDATA[meiner unbescheidenen meinung nach]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imo]]></match> + <subst><![CDATA[meiner meinung nach]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ip]]></match> + <subst><![CDATA[ei pie]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[isdn]]></match> + <subst><![CDATA[i es de en]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[k]]></match> + <subst><![CDATA[ho key]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[kde]]></match> + <subst><![CDATA[ka de eh]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[kbd]]></match> + <subst><![CDATA[tastatur]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[lshmba]]></match> + <subst><![CDATA[halte mir den bauch vor lachen]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[lthtt]]></match> + <subst><![CDATA[unbeschreibliches gelaechter]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ltns]]></match> + <subst><![CDATA[lange nicht gesehen]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[lol]]></match> + <subst><![CDATA[h h h]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[m8]]></match> + <subst><![CDATA[kumpel]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[msg]]></match> + <subst><![CDATA[nachricht]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[np]]></match> + <subst><![CDATA[kein problem]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[oic]]></match> + <subst><![CDATA[oh verstehe]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ok]]></match> + <subst><![CDATA[ho key]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ott]]></match> + <subst><![CDATA[übertreiben]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[pc]]></match> + <subst><![CDATA[pie cie]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ppc]]></match> + <subst><![CDATA[power pie cie]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ppl]]></match> + <subst><![CDATA[leute]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[rotfl]]></match> + <subst><![CDATA[kugelt sich vor lachen]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[rotflol]]></match> + <subst><![CDATA[bruellt vor lachen]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[rsvp]]></match> + <subst><![CDATA[antworte bitte]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[scsi]]></match> + <subst><![CDATA[skasi]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[suse]]></match> + <subst><![CDATA[suse]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[sec]]></match> + <subst><![CDATA[sekunde]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[secs]]></match> + <subst><![CDATA[sekunden]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[thx]]></match> + <subst><![CDATA[danke]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[tnx]]></match> + <subst><![CDATA[danke]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wo]]></match> + <subst><![CDATA[ohne]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wb]]></match> + <subst><![CDATA[willkommen urueck]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wtf]]></match> + <subst><![CDATA[was zum teufel]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wth]]></match> + <subst><![CDATA[was zum teufel]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wtg]]></match> + <subst><![CDATA[gut gemacht]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wtgp]]></match> + <subst><![CDATA[bitte um privatchett]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wth]]></match> + <subst><![CDATA[was zum teufel]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wygiswypf]]></match> + <subst><![CDATA[was du kriegst ist was du bezahlst]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[yw]]></match> + <subst><![CDATA[gruess diche]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\_]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\|]]></match> + <subst><![CDATA[ ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\\o\/]]></match> + <subst><![CDATA[tschuess]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\/o\\]]></match> + <subst><![CDATA[haelt sich die ohren zu]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\:\-\/]]></match> + <subst><![CDATA[stirnrunzeln]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/chat.xml b/kttsd/filters/stringreplacer/chat.xml new file mode 100644 index 0000000..2518824 --- /dev/null +++ b/kttsd/filters/stringreplacer/chat.xml @@ -0,0 +1,616 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wordlist> + <name>Internet Relay Chat and IM</name> + <language-code>en</language-code> + <appid>knotify</appid> + <appid>kopete</appid> + <appid>kvirc</appid> + <appid>ksirc</appid> + <appid>konversation</appid> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<qt>]]></match> + <subst><![CDATA[ ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<([^>]+)>]]></match> + <subst><![CDATA[ \1 says ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<br>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</qt>]]></match> + <subst><![CDATA[ ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\;\)]]></match> + <subst><![CDATA[ winks]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\<]]></match> + <subst><![CDATA[ sad face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\/]]></match> + <subst><![CDATA[ frowns ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\>]]></match> + <subst><![CDATA[ grins]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\|]]></match> + <subst><![CDATA[ straight faced]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\-\|]]></match> + <subst><![CDATA[ straight faced]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\-\)]]></match> + <subst><![CDATA[ smiles]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\'\(]]></match> + <subst><![CDATA[ cries]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\(]]></match> + <subst><![CDATA[ sad face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\)]]></match> + <subst><![CDATA[ smiles]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\@]]></match> + <subst><![CDATA[ angry look]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\$]]></match> + <subst><![CDATA[ blushes]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\@\)]]></match> + <subst><![CDATA[ cat face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(6\)]]></match> + <subst><![CDATA[ devil]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(a\)]]></match> + <subst><![CDATA[ angel]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:D]]></match> + <subst><![CDATA[ grins]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(h\)]]></match> + <subst><![CDATA[ cool shades]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\so\_O]]></match> + <subst><![CDATA[ winks]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:p]]></match> + <subst><![CDATA[ sticks out tung]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(s\)]]></match> + <subst><![CDATA[ sleepy]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ad]]></match> + <subst><![CDATA[add]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[afaik]]></match> + <subst><![CDATA[as far as I know]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[afk]]></match> + <subst><![CDATA[away from keyboard]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[asap]]></match> + <subst><![CDATA[as soon as possable]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[atm]]></match> + <subst><![CDATA[at the moment]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ayt]]></match> + <subst><![CDATA[are you there]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[b4]]></match> + <subst><![CDATA[before]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[b4n]]></match> + <subst><![CDATA[bye for now]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[bak]]></match> + <subst><![CDATA[back]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[bbl]]></match> + <subst><![CDATA[be back later]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[bf]]></match> + <subst><![CDATA[boy friend]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[b/f]]></match> + <subst><![CDATA[boy friend]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[brb]]></match> + <subst><![CDATA[be right back]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[btw]]></match> + <subst><![CDATA[by the way]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cu]]></match> + <subst><![CDATA[see you]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cul]]></match> + <subst><![CDATA[see you later]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cul8r]]></match> + <subst><![CDATA[see you later]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[cya]]></match> + <subst><![CDATA[see you later]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[d/l]]></match> + <subst><![CDATA[down load]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[dvd]]></match> + <subst><![CDATA[de vee de]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[f2f]]></match> + <subst><![CDATA[face to face]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[fubar]]></match> + <subst><![CDATA[fecked up beyond all repair]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[fyi]]></match> + <subst><![CDATA[for your information]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[gf]]></match> + <subst><![CDATA[girl friend]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[g/f]]></match> + <subst><![CDATA[girl friend]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[gtg]]></match> + <subst><![CDATA[got to go]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[hmm]]></match> + <subst><![CDATA[hurrmmm]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[hth]]></match> + <subst><![CDATA[hope that helps]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ic]]></match> + <subst><![CDATA[i see]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[iirc]]></match> + <subst><![CDATA[if I recall correctly]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imao]]></match> + <subst><![CDATA[in my arrogant opinion]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imco]]></match> + <subst><![CDATA[in my considered opinion]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ime]]></match> + <subst><![CDATA[in my experience]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imho]]></match> + <subst><![CDATA[in my humble opinion]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imnsho]]></match> + <subst><![CDATA[in my not so humble opinion]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[imo]]></match> + <subst><![CDATA[in my opinion]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ip]]></match> + <subst><![CDATA[i p]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[isdn]]></match> + <subst><![CDATA[i s d n]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[k]]></match> + <subst><![CDATA[ho kay]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[kde]]></match> + <subst><![CDATA[k d e]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[kbd]]></match> + <subst><![CDATA[keyboard]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[lshmba]]></match> + <subst><![CDATA[laughing so hard my belly aches]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[lthtt]]></match> + <subst><![CDATA[laughing too hard to type]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ltns]]></match> + <subst><![CDATA[long time no see]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[lol]]></match> + <subst><![CDATA[lafes out loud]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[m8]]></match> + <subst><![CDATA[mate]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[msg]]></match> + <subst><![CDATA[message]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[np]]></match> + <subst><![CDATA[no problem]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[oic]]></match> + <subst><![CDATA[oh i see]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ok]]></match> + <subst><![CDATA[ho kay]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ott]]></match> + <subst><![CDATA[over the top]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[pc]]></match> + <subst><![CDATA[p c]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ppc]]></match> + <subst><![CDATA[power p c]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[ppl]]></match> + <subst><![CDATA[people]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[rotfl]]></match> + <subst><![CDATA[rolling on the floor laughing]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[rotflol]]></match> + <subst><![CDATA[rolling on the floor laughing out loud]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[rsvp]]></match> + <subst><![CDATA[please reply]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[scsi]]></match> + <subst><![CDATA[scuzy]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[suse]]></match> + <subst><![CDATA[sue see]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[sec]]></match> + <subst><![CDATA[second]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[secs]]></match> + <subst><![CDATA[seconds]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[thx]]></match> + <subst><![CDATA[thanks]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[tnx]]></match> + <subst><![CDATA[thanks]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wo]]></match> + <subst><![CDATA[without]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wb]]></match> + <subst><![CDATA[welcome back]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wtf]]></match> + <subst><![CDATA[what the heck]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wth]]></match> + <subst><![CDATA[what the heck]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wtg]]></match> + <subst><![CDATA[way to go]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wtgp]]></match> + <subst><![CDATA[want to go private]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wth]]></match> + <subst><![CDATA[what the heck]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[wygiswypf]]></match> + <subst><![CDATA[what you get is what you pay for]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[yw]]></match> + <subst><![CDATA[your welcome]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\_]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\|]]></match> + <subst><![CDATA[ ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\\o\/]]></match> + <subst><![CDATA[ cheers ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\/o\\]]></match> + <subst><![CDATA[ covers ears ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\:\-\/]]></match> + <subst><![CDATA[ frowns ]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/editreplacementwidget.ui b/kttsd/filters/stringreplacer/editreplacementwidget.ui new file mode 100644 index 0000000..917cdf8 --- /dev/null +++ b/kttsd/filters/stringreplacer/editreplacementwidget.ui @@ -0,0 +1,263 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>EditReplacementWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>EditReplacementWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>393</width> + <height>148</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>255</horstretch> + <verstretch>255</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>typeButtonGroup</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>&Type</string> + </property> + <property name="exclusive"> + <bool>true</bool> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>wordRadioButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Word</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>regexpRadioButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Regular &expression</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>caseCheckBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Match &case</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>substLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Replace with:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>matchLineEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>matchLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Match:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>matchLineEdit</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>substLineEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="KPushButton" row="0" column="1"> + <property name="name"> + <cstring>matchButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>...</string> + </property> + </widget> + <widget class="KLineEdit" row="0" column="0"> + <property name="name"> + <cstring>matchLineEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </grid> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>wordRadioButton</tabstop> + <tabstop>regexpRadioButton</tabstop> + <tabstop>caseCheckBox</tabstop> + <tabstop>matchLineEdit</tabstop> + <tabstop>matchButton</tabstop> + <tabstop>substLineEdit</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/kttsd/filters/stringreplacer/emoticons.xml b/kttsd/filters/stringreplacer/emoticons.xml new file mode 100644 index 0000000..f522358 --- /dev/null +++ b/kttsd/filters/stringreplacer/emoticons.xml @@ -0,0 +1,304 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wordlist> + <name>Emoticons (English)</name> + <language-code>en</language-code> + <appid>knotify</appid> + <appid>kopete</appid> + <appid>ksirc</appid> + <appid>kvirc</appid> + <appid>konversation</appid> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\)]]></match> + <subst><![CDATA[. smiles]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\-\)]]></match> + <subst><![CDATA[. smiles]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\p]]></match> + <subst><![CDATA[. sticks out tung]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:D]]></match> + <subst><![CDATA[. grins]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\>]]></match> + <subst><![CDATA[.grins]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\;\)]]></match> + <subst><![CDATA[. winks]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\o\_\0]]></match> + <subst><![CDATA[. winks]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\(]]></match> + <subst><![CDATA[. sad face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\<]]></match> + <subst><![CDATA[. sad face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\\:\|]]></match> + <subst><![CDATA[. straight faced]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\-\|]]></match> + <subst><![CDATA[. straight faced]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\$]]></match> + <subst><![CDATA[. blushes]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\\(h\)]]></match> + <subst><![CDATA[. cool shades]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\@]]></match> + <subst><![CDATA[. angry look]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\'\(]]></match> + <subst><![CDATA[. cries]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(a\)]]></match> + <subst><![CDATA[. angel]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(6\)]]></match> + <subst><![CDATA[. devil]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\%\)]]></match> + <subst><![CDATA[. hand cuffs]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\:\[\]]></match> + <subst><![CDATA[. bat]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\@\)]]></match> + <subst><![CDATA[. cat face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\&\)]]></match> + <subst><![CDATA[. dog face]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\*\)]]></match> + <subst><![CDATA[. star]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(s\)]]></match> + <subst><![CDATA[. sleepy]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(Y\)]]></match> + <subst><![CDATA[. thumbs up]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(n\)]]></match> + <subst><![CDATA[. thumbs down]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\}\)]]></match> + <subst><![CDATA[. girl huggs]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\{\)]]></match> + <subst><![CDATA[. boy huggs]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(l\)]]></match> + <subst><![CDATA[. love]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(U\)]]></match> + <subst><![CDATA[. is hart brooken]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(k\)]]></match> + <subst><![CDATA[. kisses]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\^\)]]></match> + <subst><![CDATA[. birthday cake]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(g\)]]></match> + <subst><![CDATA[. a gift]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(f\)]]></match> + <subst><![CDATA[. flower]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(w\)]]></match> + <subst><![CDATA[. willted flower]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(t\)]]></match> + <subst><![CDATA[. telephone]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(e\)]]></match> + <subst><![CDATA[. e mail]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(m\)]]></match> + <subst><![CDATA[. messenger]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(o\)]]></match> + <subst><![CDATA[. time]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(x\)]]></match> + <subst><![CDATA[. girl]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(z\)]]></match> + <subst><![CDATA[. boy]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(p\)]]></match> + <subst><![CDATA[. picture]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\~\)]]></match> + <subst><![CDATA[. film]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(b\)]]></match> + <subst><![CDATA[. glass of beer]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(d\)]]></match> + <subst><![CDATA[. glass of wine]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(\#\)]]></match> + <subst><![CDATA[. sunny]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(r\)]]></match> + <subst><![CDATA[. rainbow]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(8\)]]></match> + <subst><![CDATA[. note]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(i\)]]></match> + <subst><![CDATA[. idea]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\s\(c\)]]></match> + <subst><![CDATA[. coffee cup]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/festival_unspeakable_chars.xml b/kttsd/filters/stringreplacer/festival_unspeakable_chars.xml new file mode 100644 index 0000000..5086d01 --- /dev/null +++ b/kttsd/filters/stringreplacer/festival_unspeakable_chars.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wordlist> + <name>Fix Festival Unspeakable Characters</name> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x80]]></match> + <subst><![CDATA[ Euro ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x82]]></match> + <subst><![CDATA[']]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x84]]></match> + <subst><![CDATA["]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x85]]></match> + <subst><![CDATA[...]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x89]]></match> + <subst><![CDATA[ per Mille ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x8B]]></match> + <subst><![CDATA[<]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[[\x91-\x92]]]></match> + <subst><![CDATA[']]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[[\x93-\x94]]]></match> + <subst><![CDATA["]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[[\x96-\x97]]]></match> + <subst><![CDATA[-]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x98]]></match> + <subst><![CDATA[~]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x99]]></match> + <subst><![CDATA[ trademark ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\x9B]]></match> + <subst><![CDATA[>]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[[\x80-\x9F]]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\{]]></match> + <subst><![CDATA[ left curly brace ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\}]]></match> + <subst><![CDATA[ right curly brace ]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/kmail.xml b/kttsd/filters/stringreplacer/kmail.xml new file mode 100644 index 0000000..b0f33d6 --- /dev/null +++ b/kttsd/filters/stringreplacer/kmail.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Filter by David Power achiestdragon@gmail.com --> +<wordlist> + <name>kmail notify</name> + <appid>knotify</appid> + <appid>kmail</appid> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<br>]]></match> + <subst><![CDATA[,]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</b>]]></match> + <subst><![CDATA[,]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[in /local/inbox]]></match> + <subst><![CDATA[,]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<b>]]></match> + <subst><![CDATA[,]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/kttsd_stringreplacerplugin.desktop b/kttsd/filters/stringreplacer/kttsd_stringreplacerplugin.desktop new file mode 100644 index 0000000..df8214c --- /dev/null +++ b/kttsd/filters/stringreplacer/kttsd_stringreplacerplugin.desktop @@ -0,0 +1,92 @@ +[Desktop Entry] +Name=String Replacer +Name[bg]=Замяна на низове +Name[ca]=Reemplaçador de cadenes +Name[cs]=Nahrazení řetězce +Name[da]=Streng-erstatter +Name[de]=Zeichenkettenersetzer +Name[el]=Αντικαταστάτης συμβολοσειρών +Name[es]=Reemplazador de cadenas +Name[et]=Stringiasendaja +Name[eu]=Kate-ordezkatzailea +Name[fa]=جایگزینکنندۀ رشته +Name[fi]=Tekstien korvaaja +Name[fr]=Remplacement de chaînes +Name[ga]=Ionadóir Teaghrán +Name[gl]=Reemprazamento de Cadeas +Name[he]=מחליף מחרוזות +Name[hu]=Sztringcserélő +Name[is]=Strengja útskiptir +Name[it]=Sostitutore di stringhe +Name[ja]=文字列置換 +Name[ka]=სტრიქონის ჩამნაცვლებელი +Name[km]= ឧបករណ៍ជំនួសខ្សែអក្សរ +Name[lt]=Eilučių pakeitiklis +Name[mk]=Менувач на низи знаци +Name[ms]=Pengganti Rentetan +Name[nb]=Strengerstatter +Name[nds]=Tekenkeed-Tuscher +Name[ne]=स्ट्रिङ प्रतिस्थापक +Name[nl]=Tekstvervanger +Name[pa]=ਸਤਰ ਤਬਦੀਲ +Name[pl]=Zastępowanie tekstu +Name[pt]=Substituição de Textos +Name[pt_BR]=Substituidor de Strings +Name[ru]=Автозамена +Name[sk]=Náhrada reťazca +Name[sl]=Zamenjevalnik nizov +Name[sr]=Замењивач знаковних низова +Name[sr@Latn]=Zamenjivač znakovnih nizova +Name[sv]=Strängersättning +Name[ta]=சர இடமாற்றி +Name[tg]=Ҷойивазкунии автоматӣ +Name[tr]=Metin Değiştirici +Name[uk]=Заміна рядків +Name[vi]=Trình thay Chuỗi +Name[zh_TW]=字串置換器 +Comment=Generic String Replacement Filter Plugin for KTTS +Comment[bg]=Приставка филтър за замяна на низове за KTTS +Comment[ca]=Connector del filtre reemplaçador genèric de cadenes pel KTTS +Comment[cs]=Modul filtru náhrady řetězce KTTS +Comment[da]=Generisk strengerstatnings-filter-plugin for KTTS +Comment[de]=Ein generisches Modul zur Zeichenkettenersetzung für KTTS +Comment[el]=Γενικό φίλτρο πρόσθετο αντικατάστασης συμβολοσειρών για το KTTS +Comment[es]=Complemento de filtro genérico de sustituición de cadenas para KTTS +Comment[et]=KTTS-i üldine stringiasendusfiltri plugin +Comment[eu]=KTTS-ren kateak ordezkatzeko iragazki-plugin generikoa +Comment[fa]=وصلۀ پالایۀ جایگزینی رشتۀ عمومی برای KTTS +Comment[fi]=Yleinen tekstien korvaussuodatinliitännäinen KTTS-ohjelmaan +Comment[fr]=Module de filtrage de remplacement de chaînes générique pour KTTS +Comment[gl]=Plugin de Filtro Xenérico de Substituición de Cadeas para KTTS +Comment[hu]=Általános sztringcserélő modul a KTTS-hez +Comment[is]=Almennt strengja útskiptinga íforrit fyrir KTTS +Comment[it]=Plugin generico per il filtro di sostituzione di stringhe per KTTS +Comment[ja]=KTTS 用汎用文字列置換フィルタプラグイン +Comment[ka]=ზოგადი სტრიქონის ჩანაცვლების ფილტრის მოდული KTTS-სთვის +Comment[km]=កម្មវិធីជំនួយតម្រងការជំនួសខ្សែអក្សរទូទៅសម្រាប់ KTTS +Comment[lt]=Bendro pobūdžio eilučių pakeitimo filtro priedas, skirtas KTTS +Comment[mk]=Филтер за KTTS за општа замена на низи знаци +Comment[ms]=Plugin Penapis Penggantian Rentetan Generik bagi KTTS +Comment[nb]=Filter-programtillegg til KTTS for streng-erstatning +Comment[nds]=KTTS-Filtermoduul för dat Utwesseln vun Tekenkeden +Comment[ne]=KTTS का लागि जेनेरीक स्ट्रिङ प्रतिस्थापन फिल्टर प्लगइन +Comment[nl]=Generieke tekst-vervang-filter-plugin voor KTTS +Comment[pl]=Wtyczka zastępowania tekstu dla KTTS +Comment[pt]='Plugin' Genérico de Substituição de Textos para o KTTS +Comment[pt_BR]=Plug-in de Filtro genérico de Substituição de Strings Para o KTTS +Comment[ru]=Фильтр автозамены для KTTS +Comment[sk]=Všeobecný modul filtra náhrady reťazca v KTTS +Comment[sl]=Filtrni vstavek KTTS za generični zamenjevalnik nizov +Comment[sr]=Генерички филтерски прикључак KTTS-а за замену знаковних низова +Comment[sr@Latn]=Generički filterski priključak KTTS-a za zamenu znakovnih nizova +Comment[sv]=Insticksprogram för KTTS med generellt strängersättningsfilter +Comment[ta]=KTTSக்கான அலங்கார சொருகுபொருள் சர இடமாற்றி +Comment[tg]=Филтри худивазкунӣ барои KTTS +Comment[tr]=KTTS için Genel Metin Değiştirme Süzgeci Eklentisi +Comment[uk]=Втулок загального фільтра заміни рядків для KTTS +Comment[vi]=Trình bổ sung Lọc Thay Chuỗi Chung cho KTTS +Comment[zh_TW]=KTTS 使用的字串置換外掛程式 +Type=Service +ServiceTypes=KTTSD/FilterPlugin +X-KDE-Library=libkttsd_stringreplacerplugin +X-KDE-Languages=en,en_US,en_GB,en_CA,es,es_mx,cy,de,fi,cs,pl diff --git a/kttsd/filters/stringreplacer/polish_festival_fixes.xml b/kttsd/filters/stringreplacer/polish_festival_fixes.xml new file mode 100644 index 0000000..a6f2533 --- /dev/null +++ b/kttsd/filters/stringreplacer/polish_festival_fixes.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Author of filter: Mikolaj Machowski ( mikmach AT wp DOT pl ) --> +<wordlist> + <name>Filtr polski / Filter for Polish Festival voice</name> + <language-code>pl</language-code> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[[^a-zA-Z0-9ęóąśłżźćńĘÓĄŚŁŻŹĆŃ\,\"\'\\@#$%^*+=`~]]]></match> + <subst><![CDATA[ ]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/polish_festival_unspeakables.xml b/kttsd/filters/stringreplacer/polish_festival_unspeakables.xml new file mode 100644 index 0000000..8fbc18a --- /dev/null +++ b/kttsd/filters/stringreplacer/polish_festival_unspeakables.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Author of filter: Mikolaj Machowski ( mikmach AT wp DOT pl ) --> +<wordlist> + <name>Polskie niewymawialne / Polish Festival unspeakables</name> + <language-code>pl</language-code> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\(]]></match> + <subst><![CDATA[ otwieram nawias ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\)]]></match> + <subst><![CDATA[ zamykam nawias ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\{]]></match> + <subst><![CDATA[ otwieram nawias klamrowy ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\}]]></match> + <subst><![CDATA[ zamykam nawias klamrowy ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\[]]></match> + <subst><![CDATA[ otwieram nawias kwadratowy ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\]]]></match> + <subst><![CDATA[ zamykam nawias kwadratowy ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\<]]></match> + <subst><![CDATA[ otwieram nawias trójkątny ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\>]]></match> + <subst><![CDATA[ zamykam nawias trójkątny ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\_]]></match> + <subst><![CDATA[ znak podkreślenia ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\&]]></match> + <subst><![CDATA[ i ]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[\/]]></match> + <subst><![CDATA[ slesz ]]></subst> + </word> + <word> + <type>Word</type> + <case>No</case> + <match><![CDATA[\\]]></match> + <subst><![CDATA[ bekslesz ]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/qt2plaintext.xml b/kttsd/filters/stringreplacer/qt2plaintext.xml new file mode 100644 index 0000000..8a194ec --- /dev/null +++ b/kttsd/filters/stringreplacer/qt2plaintext.xml @@ -0,0 +1,359 @@ +<?xml version="1.0" encoding="UTF-8"?> +<wordlist> + <name>Qt Rich Text to Plain Text</name> + <appid>knotify</appid> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<qt>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</qt>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<h1>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<h2>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<h3>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</h1>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</h2>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</h3>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<p>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</p>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<center>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</center>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<blockquote>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</blockquote>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<ul>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</ul>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<ol>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</ol>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<li>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</li>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<a>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</a>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<em>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</em>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<strong>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</strong>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<bold>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</bold>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<b>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</b>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<u>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</u>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<big>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</big>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<small>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</small>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<hr/>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<br/>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<nobr>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</nobr>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<table>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</table>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<tr>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</tr>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<td>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</td>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<th>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</th>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<img src\=]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<a href\=]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[/>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<img>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[">]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<pre>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</pre>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<tt>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</tt>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[<code>]]></match> + <subst><![CDATA[]]></subst> + </word> + <word> + <type>RegExp</type> + <case>No</case> + <match><![CDATA[</code>]]></match> + <subst><![CDATA[]]></subst> + </word> +</wordlist> diff --git a/kttsd/filters/stringreplacer/stringreplacerconf.cpp b/kttsd/filters/stringreplacer/stringreplacerconf.cpp new file mode 100644 index 0000000..9afa2d5 --- /dev/null +++ b/kttsd/filters/stringreplacer/stringreplacerconf.cpp @@ -0,0 +1,725 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic String Replacement Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> +#include <qhbox.h> +#include <qlayout.h> +#include <qcheckbox.h> +#include <qdom.h> +#include <qfile.h> +#include <qradiobutton.h> + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> +#include <klistview.h> +#include <klineedit.h> +#include <kdialog.h> +#include <kdialogbase.h> +#include <kpushbutton.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kregexpeditorinterface.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> +#include <kfiledialog.h> +#include <kmessagebox.h> + +// KTTS includes. +#include "filterconf.h" + +// StringReplacer includes. +#include "stringreplacerconf.h" +#include "stringreplacerconf.moc" +#include "editreplacementwidget.h" + +/** +* Constructor +*/ +StringReplacerConf::StringReplacerConf( QWidget *parent, const char *name, const QStringList& /*args*/) : + KttsFilterConf(parent, name), + m_editDlg(0), + m_editWidget(0) +{ + // kdDebug() << "StringReplacerConf::StringReplacerConf: Running" << endl; + + // Create configuration widget. + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "StringReplacerConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new StringReplacerConfWidget(this, "StringReplacerConfigWidget"); + layout->addWidget(m_widget); + m_widget->substLView->setSortColumn(-1); + + connect(m_widget->nameLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->languageBrowseButton, SIGNAL(clicked()), + this, SLOT(slotLanguageBrowseButton_clicked())); + connect(m_widget->addButton, SIGNAL(clicked()), + this, SLOT(slotAddButton_clicked())); + connect(m_widget->upButton, SIGNAL(clicked()), + this, SLOT(slotUpButton_clicked())); + connect(m_widget->downButton, SIGNAL(clicked()), + this, SLOT(slotDownButton_clicked())); + connect(m_widget->editButton, SIGNAL(clicked()), + this, SLOT(slotEditButton_clicked())); + connect(m_widget->removeButton, SIGNAL(clicked()), + this, SLOT(slotRemoveButton_clicked())); + connect(m_widget->loadButton, SIGNAL(clicked()), + this, SLOT(slotLoadButton_clicked())); + connect(m_widget->saveButton, SIGNAL(clicked()), + this, SLOT(slotSaveButton_clicked())); + connect(m_widget->clearButton, SIGNAL(clicked()), + this, SLOT(slotClearButton_clicked())); + connect(m_widget->substLView, SIGNAL(selectionChanged()), + this, SLOT(enableDisableButtons())); + connect(m_widget->appIdLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + + // Determine if kdeutils Regular Expression Editor is installed. + m_reEditorInstalled = !KTrader::self()->query("KRegExpEditor/KRegExpEditor").isEmpty(); + + // Set up defaults. + defaults(); +} + +/** +* Destructor. +*/ +StringReplacerConf::~StringReplacerConf(){ + // kdDebug() << "StringReplacerConf::~StringReplacerConf: Running" << endl; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void StringReplacerConf::load(KConfig* config, const QString& configGroup){ + // kdDebug() << "StringReplacerConf::load: Running" << endl; + // See if this filter previously save its word list. + config->setGroup( configGroup ); + QString wordsFilename = config->readEntry( "WordListFile" ); + if ( !wordsFilename.isEmpty() ) + { + QString errMsg = loadFromFile( wordsFilename, true ); + if ( !errMsg.isEmpty() ) + kdDebug() << "StringReplacerConf::load: " << errMsg << endl; + enableDisableButtons(); + } +} + +// Loads word list and settings from a file. Clearing configuration if clear is True. +QString StringReplacerConf::loadFromFile( const QString& filename, bool clear) +{ + // Open existing word list. + QFile file( filename ); + if ( !file.open( IO_ReadOnly ) ) + { + return i18n("Unable to open file.") + filename; + } + // QDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" ); + QDomDocument doc( "" ); + if ( !doc.setContent( &file ) ) { + file.close(); + return i18n("File not in proper XML format."); + } + // kdDebug() << "StringReplacerConf::load: document successfully parsed." << endl; + file.close(); + + // Clear list view. + if ( clear ) m_widget->substLView->clear(); + + // Name setting. + QDomNodeList nameList = doc.elementsByTagName( "name" ); + QDomNode nameNode = nameList.item( 0 ); + m_widget->nameLineEdit->setText( nameNode.toElement().text() ); + // kdDebug() << "StringReplacerConf::load: name = " << nameNode.toElement().text() << endl; + + // Language Codes setting. List may be single element of comma-separated values, + // or multiple elements. + QString languageCodes; + QDomNodeList languageList = doc.elementsByTagName( "language-code" ); + for ( uint ndx=0; ndx < languageList.count(); ++ndx ) + { + QDomNode languageNode = languageList.item( ndx ); + if (!languageCodes.isEmpty()) languageCodes += ","; + languageCodes += languageNode.toElement().text(); + } + if ( clear ) + m_languageCodeList = QStringList::split(',', languageCodes, false); + else + m_languageCodeList += QStringList::split(',', languageCodes, false); + QString language; + m_languageCodeList.sort(); + // Eliminate dups. + for ( int ndx = m_languageCodeList.count() - 2; ndx >= 0; --ndx ) + { + if ( m_languageCodeList[ndx] == m_languageCodeList[ndx+1] ) + m_languageCodeList.remove(m_languageCodeList.at(ndx+1)); + } + for ( uint ndx=0; ndx < m_languageCodeList.count(); ++ndx ) + { + if (!language.isEmpty()) language += ","; + language += KGlobal::locale()->twoAlphaToLanguageName(m_languageCodeList[ndx]); + } + m_widget->languageLineEdit->setText(language); + + // AppId. Apply this filter only if DCOP appId of application that queued + // the text contains this string. List may be single element of comma-separated values, + // or multiple elements. + QDomNodeList appIdList = doc.elementsByTagName( "appid" ); + QString appIds; + for ( uint ndx=0; ndx < appIdList.count(); ++ndx ) + { + QDomNode appIdNode = appIdList.item( ndx ); + if (!appIds.isEmpty()) appIds += ","; + appIds += appIdNode.toElement().text(); + } + if ( !clear ) appIds = m_widget->appIdLineEdit->text() + appIds; + m_widget->appIdLineEdit->setText( appIds ); + + // Word list. + QListViewItem* item = 0; + if ( !clear ) item = m_widget->substLView->lastChild(); + QDomNodeList wordList = doc.elementsByTagName("word"); + const int wordListCount = wordList.count(); + for (int wordIndex = 0; wordIndex < wordListCount; ++wordIndex) + { + // kdDebug() << "StringReplacerConf::load: start parsing of word " << wordIndex << endl; + QDomNode wordNode = wordList.item(wordIndex); + QDomNodeList propList = wordNode.childNodes(); + QString wordType; + QString matchCase = "No"; // Default for old (v<=3.5.3) config files with no <case/>. + QString match; + QString subst; + const int propListCount = propList.count(); + for (int propIndex = 0; propIndex < propListCount; ++propIndex) + { + QDomNode propNode = propList.item(propIndex); + QDomElement prop = propNode.toElement(); + if (prop.tagName() == "type") wordType = prop.text(); + if (prop.tagName() == "case") matchCase = prop.text(); + if (prop.tagName() == "match") match = prop.text(); + if (prop.tagName() == "subst") subst = prop.text(); + } + QString wordTypeStr = + (wordType=="RegExp"?i18n("Abbreviation for 'Regular Expression'", "RegExp"):i18n("Word")); + QString matchCaseStr = + (matchCase=="Yes"?i18n("Yes"):i18n("No")); + if (!item) + item = new KListViewItem(m_widget->substLView, wordTypeStr, matchCaseStr, match, subst); + else + item = new KListViewItem(m_widget->substLView, item, wordTypeStr, matchCaseStr, match, subst); + } + + return QString::null; +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void StringReplacerConf::save(KConfig* config, const QString& configGroup){ + // kdDebug() << "StringReplacerConf::save: Running" << endl; + QString wordsFilename = + KGlobal::dirs()->saveLocation( "data" ,"kttsd/stringreplacer/", true ); + if ( wordsFilename.isEmpty() ) + { + kdDebug() << "StringReplacerConf::save: no save location" << endl; + return; + } + wordsFilename += configGroup; + QString errMsg = saveToFile( wordsFilename ); + if ( errMsg.isEmpty() ) + { + config->setGroup( configGroup ); + config->writeEntry( "WordListFile", realFilePath(wordsFilename) ); + } + else + kdDebug() << "StringReplacerConf::save: " << errMsg << endl; +} + +// Saves word list and settings to a file. +QString StringReplacerConf::saveToFile(const QString& filename) +{ + // kdDebug() << "StringReplacerConf::saveToFile: saving to file " << wordsFilename << endl; + + QFile file( filename ); + if ( !file.open( IO_WriteOnly ) ) + return i18n("Unable to open file ") + filename; + + // QDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" ); + QDomDocument doc( "" ); + + QDomElement root = doc.createElement( "wordlist" ); + doc.appendChild( root ); + + // Name. + QDomElement name = doc.createElement( "name" ); + root.appendChild( name ); + QDomText t = doc.createTextNode( m_widget->nameLineEdit->text() ); + name.appendChild( t ); + + // Language code. + for ( uint ndx=0; ndx < m_languageCodeList.count(); ++ndx ) + { + QDomElement languageCode = doc.createElement( "language-code" ); + root.appendChild( languageCode ); + t = doc.createTextNode( m_languageCodeList[ndx] ); + languageCode.appendChild( t ); + } + + // Application ID + QString appId = m_widget->appIdLineEdit->text().replace(" ", ""); + if ( !appId.isEmpty() ) + { + QStringList appIdList = QStringList::split(",", appId); + for ( uint ndx=0; ndx < appIdList.count(); ++ndx ) + { + QDomElement appIdElem = doc.createElement( "appid" ); + root.appendChild( appIdElem ); + t = doc.createTextNode( appIdList[ndx] ); + appIdElem.appendChild( t ); + } + } + + // Words. + QListView* lView = m_widget->substLView; + QListViewItem* item = lView->firstChild(); + while (item) + { + QDomElement wordTag = doc.createElement( "word" ); + root.appendChild( wordTag ); + QDomElement propTag = doc.createElement( "type" ); + wordTag.appendChild( propTag); + QDomText t = doc.createTextNode( item->text(0)==i18n("Word")?"Word":"RegExp" ); + propTag.appendChild( t ); + + propTag = doc.createElement( "case" ); + wordTag.appendChild( propTag); + t = doc.createTextNode( item->text(1)==i18n("Yes")?"Yes":"No" ); + propTag.appendChild( t ); + + propTag = doc.createElement( "match" ); + wordTag.appendChild( propTag); + t = doc.createCDATASection( item->text(2) ); + propTag.appendChild( t ); + + propTag = doc.createElement( "subst" ); + wordTag.appendChild( propTag); + t = doc.createCDATASection( item->text(3) ); + propTag.appendChild( t ); + + item = item->nextSibling(); + } + + // Write it all out. + QTextStream ts( &file ); + ts.setEncoding( QTextStream::UnicodeUTF8 ); + ts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + ts << doc.toString(); + // kdDebug() << "StringReplacerConf::saveToFile: writing out " << doc.toString() << endl; + file.close(); + + return QString::null; +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void StringReplacerConf::defaults(){ + // kdDebug() << "StringReplacerConf::defaults: Running" << endl; + // Default language is none. + m_languageCodeList.clear(); + m_widget->languageLineEdit->setText( "" ); + // Default name. + m_widget->nameLineEdit->setText( i18n("String Replacer") ); + m_widget->substLView->clear(); + // Default App ID is blank. + m_widget->appIdLineEdit->setText( "" ); + enableDisableButtons(); + // kdDebug() << "StringReplacerConf::defaults: Exiting" << endl; +} + +/** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ +bool StringReplacerConf::supportsMultiInstance() { return true; } + +/** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ +QString StringReplacerConf::userPlugInName() +{ + if ( m_widget->substLView->childCount() == 0 ) return QString::null; + QString instName = m_widget->nameLineEdit->text(); + if ( instName.isEmpty() ) + { + QString language; + if (m_languageCodeList.count() == 1) + language = KGlobal::locale()->twoAlphaToLanguageName(m_languageCodeList[0]); + if (m_languageCodeList.count() > 1) + language = i18n("Multiple Languages"); + if (!language.isEmpty()) + instName = i18n("String Replacer") + " (" + language + ")"; + } + return instName; +} + +void StringReplacerConf::slotLanguageBrowseButton_clicked() +{ + // Create a QHBox to host KListView. + QHBox* hBox = new QHBox(m_widget, "SelectLanguage_hbox"); + // Create a KListView and fill with all known languages. + KListView* langLView = new KListView(hBox, "SelectLanguage_lview"); + langLView->addColumn(i18n("Language")); + langLView->addColumn(i18n("Code")); + langLView->setSelectionMode(QListView::Extended); + QStringList allLocales = KGlobal::locale()->allLanguagesTwoAlpha(); + QString locale; + QString languageCode; + QString countryCode; + QString charSet; + QString language; + // Blank line so user can select no language. + QListViewItem* item = new KListViewItem(langLView, "", ""); + if (m_languageCodeList.isEmpty()) item->setSelected(true); + const int allLocalesCount = allLocales.count(); + for (int ndx=0; ndx < allLocalesCount; ++ndx) + { + locale = allLocales[ndx]; + KGlobal::locale()->splitLocale(locale, languageCode, countryCode, charSet); + language = KGlobal::locale()->twoAlphaToLanguageName(languageCode); + if (!countryCode.isEmpty()) language += + " (" + KGlobal::locale()->twoAlphaToCountryName(countryCode)+")"; + item = new KListViewItem(langLView, language, locale); + if (m_languageCodeList.contains(locale)) item->setSelected(true); + } + // Sort by language. + langLView->setSorting(0); + langLView->sort(); + // Display the box in a dialog. + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Select Languages"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_widget, + "SelectLanguage_dlg", + true, + true); + dlg->setMainWidget(hBox); + dlg->setHelp("", "kttsd"); + dlg->setInitialSize(QSize(300, 500), false); + int dlgResult = dlg->exec(); + languageCode = QString::null; + if (dlgResult == QDialog::Accepted) + { + m_languageCodeList.clear(); + QListViewItem* item = langLView->firstChild(); + while (item) + { + if (item->isSelected()) m_languageCodeList += item->text(1); + item = item->nextSibling(); + } + } + delete dlg; + // TODO: Also delete KListView and QHBox? + if (dlgResult != QDialog::Accepted) return; + language = ""; + for ( uint ndx=0; ndx < m_languageCodeList.count(); ++ndx) + { + if (!language.isEmpty()) language += ","; + language += KGlobal::locale()->twoAlphaToLanguageName(m_languageCodeList[ndx]); + } + QString s1 = m_widget->languageLineEdit->text(); + m_widget->languageLineEdit->setText(language); + // Replace language in the user's filter name. + QString s2 = m_widget->nameLineEdit->text(); + if (m_languageCodeList.count() > 1) language = i18n("Multiple Languages"); + if ( !s1.isEmpty() ) + { + s2.replace( s1, language ); + s2.replace( i18n("Multiple Languages"), language ); + } + s2.replace(" ()", ""); + if ( !s2.contains("(") && !language.isEmpty() ) s2 += " (" + language + ")"; + m_widget->nameLineEdit->setText(s2); + configChanged(); +} + +void StringReplacerConf::enableDisableButtons() +{ + bool enableBtn = (m_widget->substLView->selectedItem() != 0); + if (enableBtn) + { + m_widget->upButton->setEnabled( + m_widget->substLView->selectedItem()->itemAbove() != 0); + m_widget->downButton->setEnabled( + m_widget->substLView->selectedItem()->itemBelow() != 0); + } else { + m_widget->upButton->setEnabled(false); + m_widget->downButton->setEnabled(false); + } + m_widget->editButton->setEnabled(enableBtn); + m_widget->removeButton->setEnabled(enableBtn); + m_widget->clearButton->setEnabled(m_widget->substLView->firstChild()); + m_widget->saveButton->setEnabled(m_widget->substLView->firstChild()); +} + +void StringReplacerConf::slotUpButton_clicked() +{ + QListViewItem* item = m_widget->substLView->selectedItem(); + if (!item) return; + QListViewItem* prevItem = item->itemAbove(); + if (!prevItem) return; + prevItem->moveItem(item); + m_widget->substLView->setSelected(item, true); + m_widget->substLView->ensureItemVisible(item); + enableDisableButtons(); + configChanged(); +} + +void StringReplacerConf::slotDownButton_clicked() +{ + QListViewItem* item = m_widget->substLView->selectedItem(); + if (!item) return; + QListViewItem* nextItem = item->itemBelow(); + if (!nextItem) return; + item->moveItem(nextItem); + m_widget->substLView->setSelected(item, true); + m_widget->substLView->ensureItemVisible(item); + enableDisableButtons(); + configChanged(); +} + +void StringReplacerConf::slotAddButton_clicked() +{ + addOrEditSubstitution( true ); +} + +void StringReplacerConf::slotEditButton_clicked() +{ + addOrEditSubstitution( false ); +} + +// Displays the add/edit string replacement dialog. +void StringReplacerConf::addOrEditSubstitution(bool isAdd) +{ + QListViewItem* item = 0; + if (isAdd) + item = m_widget->substLView->lastChild(); + else + { + item = m_widget->substLView->selectedItem(); + if (!item) return; + } + // Create a QHBox to host widget. + QHBox* hBox = new QHBox(m_widget, "AddOrEditSubstitution_hbox" ); + // Create widget. + m_editWidget = new EditReplacementWidget( hBox, "AddOrEditSubstitution_widget" ); + // Set controls if editing existing. + m_editWidget->matchButton->setEnabled( false ); + if (!isAdd) + { + if ( item->text(0) == i18n("Abbreviation for 'Regular Expression'", "RegExp") ) + { + m_editWidget->regexpRadioButton->setChecked( true ); + m_editWidget->matchButton->setEnabled( m_reEditorInstalled ); + } + m_editWidget->caseCheckBox->setChecked( (item->text(1))==i18n("Yes") ); + m_editWidget->matchLineEdit->setText( item->text(2) ); + m_editWidget->substLineEdit->setText( item->text(3) ); + } + // The match box may not be blank. + connect( m_editWidget->matchLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(slotMatchLineEdit_textChanged(const QString&)) ); + connect( m_editWidget->regexpRadioButton, SIGNAL(clicked()), + this, SLOT(slotTypeButtonGroup_clicked()) ); + connect( m_editWidget->wordRadioButton, SIGNAL(clicked()), + this, SLOT(slotTypeButtonGroup_clicked()) ); + connect( m_editWidget->matchButton, SIGNAL(clicked()), + this, SLOT(slotMatchButton_clicked()) ); + // Display the box in a dialog. + m_editDlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Edit String Replacement"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_widget, + "AddOrEditSubstitution_dlg", + true, + true); + // Disable OK button if match field blank. + m_editDlg->setMainWidget( hBox ); + m_editDlg->setHelp( "", "kttsd" ); + m_editDlg->enableButton( KDialogBase::Ok, !m_editWidget->matchLineEdit->text().isEmpty() ); + int dlgResult = m_editDlg->exec(); + QString substType = i18n( "Word" ); + if ( m_editWidget->regexpRadioButton->isChecked() ) + substType = i18n("Abbreviation for 'Regular Expression'", "RegExp"); + QString matchCase = i18n("No"); + if ( m_editWidget->caseCheckBox->isChecked() ) matchCase = i18n("Yes"); + QString match = m_editWidget->matchLineEdit->text(); + QString subst = m_editWidget->substLineEdit->text(); + delete m_editDlg; + m_editDlg = 0; + m_editWidget = 0; + if (dlgResult != QDialog::Accepted) return; + // TODO: Also delete hBox and w? + if ( match.isEmpty() ) return; + if ( isAdd ) + { + if ( item ) + item = new KListViewItem( m_widget->substLView, item, substType, matchCase, match, subst ); + else + item = new KListViewItem( m_widget->substLView, substType, matchCase, match, subst ); + m_widget->substLView->setSelected( item, true ); + } + else + { + item->setText( 0, substType ); + item->setText( 1, matchCase ); + item->setText( 2, match ); + item->setText( 3, subst ); + } + m_widget->substLView->ensureItemVisible( item ); + enableDisableButtons(); + configChanged(); +} + +void StringReplacerConf::slotMatchLineEdit_textChanged(const QString& text) +{ + // Disable OK button if match field blank. + if ( !m_editDlg ) return; + m_editDlg->enableButton( KDialogBase::Ok, !text.isEmpty() ); +} + +void StringReplacerConf::slotRemoveButton_clicked() +{ + QListViewItem* item = m_widget->substLView->selectedItem(); + if (!item) return; + delete item; + enableDisableButtons(); + configChanged(); +} + +void StringReplacerConf::slotTypeButtonGroup_clicked() +{ + // Enable Regular Expression Editor button if editor is installed (requires kdeutils). + if ( !m_editWidget ) return; + m_editWidget->matchButton->setEnabled( m_editWidget->regexpRadioButton->isOn() && m_reEditorInstalled ); +} + +void StringReplacerConf::slotMatchButton_clicked() +{ + // Show Regular Expression Editor dialog if it is installed. + if ( !m_editWidget ) return; + if ( !m_editDlg ) return; + if ( !m_reEditorInstalled ) return; + QDialog *editorDialog = + KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor" ); + if ( editorDialog ) + { + // kdeutils was installed, so the dialog was found. Fetch the editor interface. + KRegExpEditorInterface *reEditor = + static_cast<KRegExpEditorInterface *>(editorDialog->qt_cast( "KRegExpEditorInterface" ) ); + Q_ASSERT( reEditor ); // This should not fail!// now use the editor. + reEditor->setRegExp( m_editWidget->matchLineEdit->text() ); + int dlgResult = editorDialog->exec(); + if ( dlgResult == QDialog::Accepted ) + { + QString re = reEditor->regExp(); + m_editWidget->matchLineEdit->setText( re ); + m_editDlg->enableButton( KDialogBase::Ok, !re.isEmpty() ); + } + delete editorDialog; + } else return; +} + +void StringReplacerConf::slotLoadButton_clicked() +{ + // QString dataDir = KGlobal::dirs()->resourceDirs("data").last() + "/kttsd/stringreplacer/"; + QString dataDir = KGlobal::dirs()->findAllResources("data", "kttsd/stringreplacer/").last(); + QString filename = KFileDialog::getOpenFileName( + dataDir, + "*.xml|String Replacer Word List (*.xml)", + m_widget, + "stringreplacer_loadfile"); + if ( filename.isEmpty() ) return; + QString errMsg = loadFromFile( filename, false ); + enableDisableButtons(); + if ( !errMsg.isEmpty() ) + KMessageBox::sorry( m_widget, errMsg, i18n("Error Opening File") ); + else + configChanged(); +} + +void StringReplacerConf::slotSaveButton_clicked() +{ + QString filename = KFileDialog::getSaveFileName( + KGlobal::dirs()->saveLocation( "data" ,"kttsd/stringreplacer/", false ), + "*.xml|String Replacer Word List (*.xml)", + m_widget, + "stringreplacer_savefile"); + if ( filename.isEmpty() ) return; + QString errMsg = saveToFile( filename ); + enableDisableButtons(); + if ( !errMsg.isEmpty() ) + KMessageBox::sorry( m_widget, errMsg, i18n("Error Opening File") ); +} + +void StringReplacerConf::slotClearButton_clicked() +{ + m_widget->substLView->clear(); + enableDisableButtons(); +} diff --git a/kttsd/filters/stringreplacer/stringreplacerconf.h b/kttsd/filters/stringreplacer/stringreplacerconf.h new file mode 100644 index 0000000..f0312a6 --- /dev/null +++ b/kttsd/filters/stringreplacer/stringreplacerconf.h @@ -0,0 +1,158 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic String Replacement Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _STRINGREPLACERCONF_H_ +#define _STRINGREPLACERCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "filterconf.h" + +// StringReplacer includes. +#include "stringreplacerconfwidget.h" + +class KDialogBase; +class EditReplacementWidget; + +class StringReplacerConf : public KttsFilterConf +{ + Q_OBJECT + + public: + /** + * Constructor + */ + StringReplacerConf( QWidget *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor + */ + virtual ~StringReplacerConf(); + + enum SubstitutionType { + stWord, // Word + stRegExp // Regular Expression + }; + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ + virtual QString userPlugInName(); + + private slots: + void slotLanguageBrowseButton_clicked(); + void slotAddButton_clicked(); + void slotUpButton_clicked(); + void slotDownButton_clicked(); + void slotEditButton_clicked(); + void slotRemoveButton_clicked(); + void slotMatchLineEdit_textChanged(const QString& text); + void slotTypeButtonGroup_clicked(); + void slotMatchButton_clicked(); + void slotLoadButton_clicked(); + void slotSaveButton_clicked(); + void slotClearButton_clicked(); + + // EnablesDisables buttons depending upon current item in list view. + void enableDisableButtons(); + + private: + // Displays the add/edit string replacement dialog. + void addOrEditSubstitution(bool isAdd); + // Loads word list and settings from a file. Clearing configuration if clear is True. + QString loadFromFile( const QString& filename, bool clear); + // Saves word list and settings to a file. + QString saveToFile( const QString& filename ); + + + // Configuration Widget. + StringReplacerConfWidget* m_widget; + // Edit Dialog and widget. + KDialogBase* m_editDlg; + EditReplacementWidget* m_editWidget; + // True if kdeutils Regular Expression Editor is installed. + bool m_reEditorInstalled; + // Language Codes. + QStringList m_languageCodeList; +}; + +#endif //_STRINGREPLACERCONF_H_ diff --git a/kttsd/filters/stringreplacer/stringreplacerconfwidget.ui b/kttsd/filters/stringreplacer/stringreplacerconfwidget.ui new file mode 100644 index 0000000..e88307f --- /dev/null +++ b/kttsd/filters/stringreplacer/stringreplacerconfwidget.ui @@ -0,0 +1,474 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>StringReplacerConfWidget</class> +<comment>Widget for configuring String Replacer Filter for KTTSD.</comment> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>StringReplacerConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>548</width> + <height>421</height> + </rect> + </property> + <property name="caption"> + <string>Configure String Replacer</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>nameLabel</cstring> + </property> + <property name="text"> + <string>&Name:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter any name you like for this filter.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter any name you like for this filter.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox" row="3" column="0"> + <property name="name"> + <cstring>applyGroupBox</cstring> + </property> + <property name="title"> + <string>Apply This &Filter When</string> + </property> + <property name="whatsThis" stdset="0"> + <string>These settings determines when the filter is applied to text.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>languageLabel</cstring> + </property> + <property name="text"> + <string>Lan&guage is:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>languageLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank the filter applies to all text jobs of any language.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>appIdLabel</cstring> + </property> + <property name="text"> + <string>Application &ID contains:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter a DCOP Application ID. This filter will only apply to text jobs queued by that application. You may enter more than one ID separated by commas. If blank, this filter applies to text jobs queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit"> + <property name="name"> + <cstring>languageLineEdit</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank the filter applies to all text jobs of any language.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>languageBrowseButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select one or more languages. This filter will be applied to text jobs of those languages.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Enter a DCOP Application ID. This filter will only apply to text jobs queued by that application. You may enter more than one ID separated by commas. Use <b>knotify</b> to match all messages sent as KDE notifications. If blank, this filter applies to text jobs queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</qt></string> + </property> + </widget> + </vbox> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>loadButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Load...</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to load a word list from a file.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>saveButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Save...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to save word list to a file.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>clearButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>C&lear</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to empty the word list.</string> + <comment>What's this text</comment> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout24</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KListView" row="0" column="0"> + <column> + <property name="text"> + <string>Type</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Match Case</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Match</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Replace With</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>substLView</cstring> + </property> + <property name="fullWidth"> + <bool>true</bool> + </property> + </widget> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout23</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>addButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Add...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to add another word or regular expression to the list.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>upButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Up</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to move selected word up in the list. Words higher in the list are applied first.</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>downButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Do&wn</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to move a word down in the list. Words lower in the list are applied last.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>editButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Edit...</string> + </property> + <property name="toggleButton"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to modify an existing word or regular expression in the list.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>removeButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Remove</string> + </property> + <property name="toggleButton"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to remove a word or regular expression from the list.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer13</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>nameLineEdit</tabstop> + <tabstop>substLView</tabstop> + <tabstop>addButton</tabstop> + <tabstop>upButton</tabstop> + <tabstop>downButton</tabstop> + <tabstop>editButton</tabstop> + <tabstop>removeButton</tabstop> + <tabstop>loadButton</tabstop> + <tabstop>saveButton</tabstop> + <tabstop>clearButton</tabstop> + <tabstop>languageLineEdit</tabstop> + <tabstop>languageBrowseButton</tabstop> + <tabstop>appIdLineEdit</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/filters/stringreplacer/stringreplacerplugin.cpp b/kttsd/filters/stringreplacer/stringreplacerplugin.cpp new file mode 100644 index 0000000..c3f9678 --- /dev/null +++ b/kttsd/filters/stringreplacer/stringreplacerplugin.cpp @@ -0,0 +1,32 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so String Replacer Filter can be used as plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "stringreplacerconf.h" +#include "stringreplacerproc.h" + +typedef K_TYPELIST_2( StringReplacerProc, StringReplacerConf ) StringReplacerPlugin; +K_EXPORT_COMPONENT_FACTORY( libkttsd_stringreplacerplugin, + KGenericFactory<StringReplacerPlugin>("kttsd_stringreplacer") ) + diff --git a/kttsd/filters/stringreplacer/stringreplacerproc.cpp b/kttsd/filters/stringreplacer/stringreplacerproc.cpp new file mode 100644 index 0000000..1b03810 --- /dev/null +++ b/kttsd/filters/stringreplacer/stringreplacerproc.cpp @@ -0,0 +1,231 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic String Replacement Filter Processing class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qdom.h> +#include <qfile.h> +#include <qlistview.h> + +// KDE includes. +#include <kdebug.h> +#include <klocale.h> +#include <kconfig.h> +#include <kglobal.h> +#include <kstandarddirs.h> + +// KTTS includes. +#include "filterproc.h" +#include "talkercode.h" + +// StringReplacer includes. +#include "stringreplacerproc.h" +#include "stringreplacerproc.moc" + +/** + * Constructor. + */ +StringReplacerProc::StringReplacerProc( QObject *parent, const char *name, const QStringList& ) : + KttsFilterProc(parent, name) +{ +} + +/** + * Destructor. + */ +/*virtual*/ StringReplacerProc::~StringReplacerProc() +{ + m_matchList.clear(); + m_caseList.clear(); + m_substList.clear(); +} + +/** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ +bool StringReplacerProc::init(KConfig* config, const QString& configGroup){ + // kdDebug() << "StringReplacerProc::init: Running" << endl; + QString wordsFilename = + KGlobal::dirs()->saveLocation( "data" ,"kttsd/stringreplacer/", false ); + if ( wordsFilename.isEmpty() ) return false; + wordsFilename += configGroup; + config->setGroup( configGroup ); + wordsFilename = config->readEntry( "WordListFile", wordsFilename ); + + // Open existing word list. + QFile file( wordsFilename ); + if ( !file.open( IO_ReadOnly ) ) + return false; + QDomDocument doc( "" ); + if ( !doc.setContent( &file ) ) { + file.close(); + return false; + } + file.close(); + + // Clear list. + m_matchList.clear(); + m_caseList.clear(); + m_substList.clear(); + + // Name setting. + // QDomNodeList nameList = doc.elementsByTagName( "name" ); + // QDomNode nameNode = nameList.item( 0 ); + // m_widget->nameLineEdit->setText( nameNode.toElement().text() ); + + // Language Codes setting. List may be single element of comma-separated values, + // or multiple elements. + m_languageCodeList.clear(); + QDomNodeList languageList = doc.elementsByTagName( "language-code" ); + for ( uint ndx=0; ndx < languageList.count(); ++ndx ) + { + QDomNode languageNode = languageList.item( ndx ); + m_languageCodeList += QStringList::split(',', languageNode.toElement().text(), false); + } + + // AppId. Apply this filter only if DCOP appId of application that queued + // the text contains this string. List may be single element of comma-separated values, + // or multiple elements. + m_appIdList.clear(); + QDomNodeList appIdList = doc.elementsByTagName( "appid" ); + for ( uint ndx=0; ndx < appIdList.count(); ++ndx ) + { + QDomNode appIdNode = appIdList.item( ndx ); + m_appIdList += QStringList::split(',', appIdNode.toElement().text(), false); + } + + // Word list. + QDomNodeList wordList = doc.elementsByTagName("word"); + const int wordListCount = wordList.count(); + for (int wordIndex = 0; wordIndex < wordListCount; ++wordIndex) + { + QDomNode wordNode = wordList.item(wordIndex); + QDomNodeList propList = wordNode.childNodes(); + QString wordType; + QString matchCase = "No"; // Default for old (v<=3.5.3) config files with no <case/>. + QString match; + QString subst; + const int propListCount = propList.count(); + for (int propIndex = 0; propIndex < propListCount; ++propIndex) + { + QDomNode propNode = propList.item(propIndex); + QDomElement prop = propNode.toElement(); + if (prop.tagName() == "type") wordType = prop.text(); + if (prop.tagName() == "case") matchCase = prop.text(); + if (prop.tagName() == "match") match = prop.text(); + if (prop.tagName() == "subst") subst = prop.text(); + } + // Build Regular Expression for each word's match string. + QRegExp rx; + rx.setCaseSensitive(matchCase == "Yes"); + if ( wordType == "Word" ) + { + // TODO: Does \b honor strange non-Latin1 encodings? + rx.setPattern( "\\b" + match + "\\b" ); + } + else + { + rx.setPattern( match ); + } + // Add Regular Expression to list (if valid). + if ( rx.isValid() ) + { + m_matchList.append( rx ); + m_substList.append( subst ); + } + } + return true; +} + +/** + * Convert input, returning output. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ +/*virtual*/ QString StringReplacerProc::convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId) +{ + m_wasModified = false; + // If language doesn't match, return input unmolested. + if ( !m_languageCodeList.isEmpty() ) + { + QString languageCode = talkerCode->languageCode(); + // kdDebug() << "StringReplacerProc::convert: converting " << inputText << + // " if language code " << languageCode << " matches " << m_languageCodeList << endl; + if ( !m_languageCodeList.contains( languageCode ) ) + { + if ( !talkerCode->countryCode().isEmpty() ) + { + languageCode += '_' + talkerCode->countryCode(); + // kdDebug() << "StringReplacerProc::convert: converting " << inputText << + // " if language code " << languageCode << " matches " << m_languageCodeList << endl; + if ( !m_languageCodeList.contains( languageCode ) ) return inputText; + } else return inputText; + } + } + // If appId doesn't match, return input unmolested. + if ( !m_appIdList.isEmpty() ) + { + // kdDebug() << "StringReplacerProc::convert: converting " << inputText << " if appId " + // << appId << " matches " << m_appIdList << endl; + bool found = false; + QString appIdStr = appId; + for ( uint ndx=0; ndx < m_appIdList.count(); ++ndx ) + { + if ( appIdStr.contains(m_appIdList[ndx]) ) + { + found = true; + break; + } + } + if ( !found ) + { + // kdDebug() << "StringReplacerProc::convert: appId not found" << endl; + return inputText; + } + } + QString newText = inputText; + const int listCount = m_matchList.count(); + for ( int index = 0; index < listCount; ++index ) + { + //kdDebug() << "newtext = " << newText << " matching " << m_matchList[index].pattern() << " replacing with " << m_substList[index] << endl; + newText.replace( m_matchList[index], m_substList[index] ); + } + m_wasModified = true; + return newText; +} + +/** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ +/*virtual*/ bool StringReplacerProc::wasModified() { return m_wasModified; } + diff --git a/kttsd/filters/stringreplacer/stringreplacerproc.h b/kttsd/filters/stringreplacer/stringreplacerproc.h new file mode 100644 index 0000000..114ebaf --- /dev/null +++ b/kttsd/filters/stringreplacer/stringreplacerproc.h @@ -0,0 +1,97 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic String Replacement Filter Processing class. + This is the interface definition for text filters. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _STRINGREPLACERPROC_H_ +#define _STRINGREPLACERPROC_H_ + +// Qt includes. +#include <qobject.h> +#include <qtextstream.h> +#include <qvaluelist.h> +#include <qregexp.h> +#include <qstringlist.h> + +// KTTS includes. +#include "filterproc.h" + +class StringReplacerProc : virtual public KttsFilterProc +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + StringReplacerProc( QObject *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor. + */ + virtual ~StringReplacerProc(); + + /** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Convert input, returning output. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ + virtual bool wasModified(); + +private: + // Language codes supported by the filter. + QStringList m_languageCodeList; + // If not empty, apply filter only to apps containing one or more of these strings. + QStringList m_appIdList; + + // List of regular expressions to match. + QValueList<QRegExp> m_matchList; + // List of match case flag + QValueList<QString> m_caseList; + // List of substitutions to replace matches. + QValueList<QString> m_substList; + // True if this filter did anything to the text. + bool m_wasModified; +}; + +#endif // _STRINGREPLACERPROC_H_ diff --git a/kttsd/filters/talkerchooser/Makefile.am b/kttsd/filters/talkerchooser/Makefile.am new file mode 100644 index 0000000..66f857d --- /dev/null +++ b/kttsd/filters/talkerchooser/Makefile.am @@ -0,0 +1,25 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_talkerchooserplugin.la + +libkttsd_talkerchooserplugin_la_SOURCES = \ + talkerchooserconfwidget.ui \ + talkerchooserconf.cpp \ + talkerchooserproc.cpp \ + talkerchooserplugin.cpp +libkttsd_talkerchooserplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_talkerchooserplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_talkerchooserplugin.desktop +servicesdir = $(kde_servicesdir) + +# Install data files. +talkerchooserdatadir = $(kde_datadir)/kttsd/talkerchooser/ +talkerchooserdata_DATA = \ + female_notifications_rc + +noinst_HEADERS = talkerchooserconfwidget.h diff --git a/kttsd/filters/talkerchooser/female_notifications_rc b/kttsd/filters/talkerchooser/female_notifications_rc new file mode 100644 index 0000000..1cbd94c --- /dev/null +++ b/kttsd/filters/talkerchooser/female_notifications_rc @@ -0,0 +1,10 @@ +[Filter] +AppIDs=knotify +Gender=*female +LanguageCode= +MatchRegExp= +Rate= +SynthName= +TalkerCode=<voice gender="*female" /> +UserFilterName=Obsolete. Use Notifications tab instead. +Volume= diff --git a/kttsd/filters/talkerchooser/kttsd_talkerchooserplugin.desktop b/kttsd/filters/talkerchooser/kttsd_talkerchooserplugin.desktop new file mode 100644 index 0000000..cdf1e45 --- /dev/null +++ b/kttsd/filters/talkerchooser/kttsd_talkerchooserplugin.desktop @@ -0,0 +1,89 @@ +[Desktop Entry] +Name=Talker Chooser +Name[bg]=Избор на глас +Name[ca]=Selector de parla +Name[cs]=Výběr hlasu +Name[da]=Vælger af oplæser +Name[de]=Sprecherauswahl +Name[el]=Επιλογή εκφωνητή +Name[es]=Selector de lectores +Name[et]=Kõneleja valimine +Name[fa]=انتخابکنندۀ گوینده +Name[fi]=Puhujan valitsin +Name[fr]=Sélecteur de voix +Name[ga]=Roghnóir Cainteora +Name[gl]=Escolla de Locutor +Name[hu]=Hangválasztó +Name[is]=Talveljari +Name[it]=Selezione dell'oratore +Name[ja]=話者選択 +Name[ka]=მოლაპარაკის ამომრჩეველი +Name[km]=ឧបករណ៍ជ្រើសអ្នកនិយាយ +Name[mk]=Избирач на говорникот +Name[ms]=Pemilih Penutur +Name[nb]=Snakkervelger +Name[nds]=Snacker-Utwahl +Name[ne]=वक्ता चयनकर्ता +Name[nl]=Spreker kiezen +Name[pa]=ਬੋਲਚਾਲ ਚੋਣਕਾਰ +Name[pl]=Wybieranie Mówców +Name[pt]=Escolha de Orador +Name[pt_BR]=Escolha do Talker +Name[ru]=Диктор +Name[sk]=Voľba hovorcu +Name[sl]=Izbirnik govornika +Name[sr]=Бирач говорника +Name[sr@Latn]=Birač govornika +Name[sv]=Urval av talare +Name[ta]=டாக்கர் தேர்ந்தெடுப்பான் +Name[tg]=Диктор +Name[tr]=Konuşmacı Seçici +Name[uk]=Вибір диктора +Name[vi]=Trình chọn Máy nói +Name[zh_TW]=語音選擇器 +Comment=Generic Talker Chooser Filter Plugin for KTTS +Comment[bg]=Приставка за избор на глас за KTTS +Comment[ca]=Connector del filtre selector genèric de parla pel KTTS +Comment[cs]=Modul filtru výběru hlasu KTTS +Comment[da]=Generisk vælger af oplæser for KTTS +Comment[de]=Generischer Sprecherauswahlfilter für KTTS +Comment[el]=Γενικό φίλτρο πρόσθετο επιλογής εκφωνητή για το KTTS +Comment[es]=Complemento de audio para KTTSD +Comment[et]=KTTS-i üldine kõneleja valimise filtri plugin +Comment[eu]=KTTS-ren hizlariak hautatzeko iragazk-plugin generikoa +Comment[fa]=وصلۀ پالایۀ انتخابکنندۀ گویندۀ عمومی برای KTTS +Comment[fi]=Yleinen puhujan valitsin suodatin liitännäinen KTTS-ohjelmalle +Comment[fr]=Module de filtrage de sélection de voix pour KTTS +Comment[gl]=Plugin para Escoller o Locutor para KTTS +Comment[hu]=Általános hangkiválasztó modul a KTTS-hez +Comment[is]=Almennt talvalsíforrit fyrir KTTS +Comment[it]=Plugin generico per il filtro di scelta dell'oratore per KTTS +Comment[ja]=KTTS 用汎用話者選択フィルタプラグイン +Comment[ka]=მოლაპარაკის ამომრჩეველის ზოგადი ფილტრის მოდული KTTS-სთვის +Comment[km]=កម្មវិធីជំនួយឧបករណ៍ជ្រើសរើសអ្នកនិយាយទូទៅសម្រាប់ KTTS +Comment[mk]=Филтер за KTTS за избирач на општ говорник +Comment[ms]=Plugin Penapis Pemilih Penutur Generik bagi KTTS +Comment[nb]=Filter-programtillegg til KTTS for snakkervalg +Comment[nds]=KTTS-Filtermoduul för dat Utsöken vun den Snacker +Comment[ne]=KTTS का लागि जेनेरीक वक्ता चयनकर्ता फिल्टर प्लगइन +Comment[nl]=Generieke plugin om de spreker te kiezen voor KTTS +Comment[pa]=KTTS ਲਈ ਸਧਾਰਨ ਬੋਲੀ ਚੋਣਕਾਰ ਫਿਲਟਰ ਪਲੱਗਿੰਨ +Comment[pl]=Wtyczka wyboru Mówców dla KTTS +Comment[pt]='Plugin' Genérico de Escolha de Orador para o KTTS +Comment[pt_BR]=Plug-in de Filtro Genérico Para Escolha do Talker do KTTS +Comment[ru]=Фильтр выбора диктора для KTTS +Comment[sk]=Všeobecný modul filra na voľbu hovorcu v KTTS +Comment[sl]=Filtrni vstavek KTTS za generični izbirnik govorca +Comment[sr]=Генерички филтерски прикључак KTTS-а за бирач говорника +Comment[sr@Latn]=Generički filterski priključak KTTS-a za birač govornika +Comment[sv]=Insticksprogram för KTTS med generellt urvalsfilter för talare +Comment[ta]=KTTS க்கான ஜெனரிக் பேச்சாளர் தேர்ந்தெடுப்பான அலங்கார சொருகுப்பொருள் +Comment[tg]=Филтри интихоби диктор барои KTTS +Comment[tr]=KTTS için Genel Konuşma Seçici Süzgeci Eklentisi +Comment[uk]=Загальний втулок Фільтра вибору диктора для KTTS +Comment[vi]=Trình bổ sung Lọc Chọn Máy nói Chung cho KTTS +Comment[zh_TW]=KTTS 使用的語音選擇外掛程式 +Type=Service +ServiceTypes=KTTSD/FilterPlugin +X-KDE-Library=libkttsd_talkerchooserplugin +X-KDE-Languages=en,en_US,en_GB,en_CA,es,es_mx,cy,de,fi,cs,pl diff --git a/kttsd/filters/talkerchooser/talkerchooserconf.cpp b/kttsd/filters/talkerchooser/talkerchooserconf.cpp new file mode 100644 index 0000000..0dbf53f --- /dev/null +++ b/kttsd/filters/talkerchooser/talkerchooserconf.cpp @@ -0,0 +1,269 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic Talker Chooser Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qstring.h> +#include <qhbox.h> +#include <qlayout.h> + +// KDE includes. +#include <klocale.h> +#include <klineedit.h> +#include <kdialog.h> +#include <kdialogbase.h> +#include <kcombobox.h> +#include <kpushbutton.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kregexpeditorinterface.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> +#include <kfiledialog.h> + +// KTTS includes. + +// TalkerChooser includes. +#include "talkerchooserconf.h" +#include "talkerchooserconf.moc" + +/** +* Constructor +*/ +TalkerChooserConf::TalkerChooserConf( QWidget *parent, const char *name, const QStringList& /*args*/) : + KttsFilterConf(parent, name) +{ + // kdDebug() << "TalkerChooserConf::TalkerChooserConf: Running" << endl; + + // Create configuration widget. + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "TalkerChooserConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new TalkerChooserConfWidget(this, "TalkerChooserConfigWidget"); + layout->addWidget(m_widget); + + // Determine if kdeutils Regular Expression Editor is installed. + m_reEditorInstalled = !KTrader::self()->query("KRegExpEditor/KRegExpEditor").isEmpty(); + m_widget->reEditorButton->setEnabled(m_reEditorInstalled); + + connect(m_widget->nameLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->reLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->reEditorButton, SIGNAL(clicked()), + this, SLOT(slotReEditorButton_clicked())); + connect(m_widget->appIdLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->talkerButton, SIGNAL(clicked()), + this, SLOT(slotTalkerButton_clicked())); + + connect(m_widget->loadButton, SIGNAL(clicked()), + this, SLOT(slotLoadButton_clicked())); + connect(m_widget->saveButton, SIGNAL(clicked()), + this, SLOT(slotSaveButton_clicked())); + connect(m_widget->clearButton, SIGNAL(clicked()), + this, SLOT(slotClearButton_clicked())); + + // Set up defaults. + defaults(); +} + +/** +* Destructor. +*/ +TalkerChooserConf::~TalkerChooserConf(){ + // kdDebug() << "TalkerChooserConf::~TalkerChooserConf: Running" << endl; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void TalkerChooserConf::load(KConfig* config, const QString& configGroup){ + // kdDebug() << "TalkerChooserConf::load: Running" << endl; + config->setGroup( configGroup ); + m_widget->nameLineEdit->setText( config->readEntry( "UserFilterName", m_widget->nameLineEdit->text() ) ); + m_widget->reLineEdit->setText( + config->readEntry("MatchRegExp", m_widget->reLineEdit->text()) ); + m_widget->appIdLineEdit->setText( + config->readEntry("AppIDs", m_widget->appIdLineEdit->text()) ); + + m_talkerCode = TalkerCode(config->readEntry("TalkerCode"), false); + // Legacy settings. + QString s = config->readEntry( "LanguageCode" ); + if (!s.isEmpty()) m_talkerCode.setFullLanguageCode(s); + s = config->readEntry( "SynthInName" ); + if (!s.isEmpty()) m_talkerCode.setPlugInName(s); + s = config->readEntry( "Gender" ); + if (!s.isEmpty()) m_talkerCode.setGender(s); + s = config->readEntry( "Volume" ); + if (!s.isEmpty()) m_talkerCode.setVolume(s); + s = config->readEntry( "Rate" ); + if (!s.isEmpty()) m_talkerCode.setRate(s); + + m_widget->talkerLineEdit->setText(m_talkerCode.getTranslatedDescription()); +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void TalkerChooserConf::save(KConfig* config, const QString& configGroup){ + // kdDebug() << "TalkerChooserConf::save: Running" << endl; + config->setGroup( configGroup ); + config->writeEntry( "UserFilterName", m_widget->nameLineEdit->text() ); + config->writeEntry( "MatchRegExp", m_widget->reLineEdit->text() ); + config->writeEntry( "AppIDs", m_widget->appIdLineEdit->text().replace(" ", "") ); + config->writeEntry( "TalkerCode", m_talkerCode.getTalkerCode()); +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void TalkerChooserConf::defaults(){ + // kdDebug() << "TalkerChooserConf::defaults: Running" << endl; + // Default name. + m_widget->nameLineEdit->setText( i18n("Talker Chooser") ); + // Default regular expression is blank. + m_widget->reLineEdit->setText( "" ); + // Default App ID is blank. + m_widget->appIdLineEdit->setText( "" ); + // Default to using default Talker. + m_talkerCode = TalkerCode( QString::null, false ); + m_widget->talkerLineEdit->setText( m_talkerCode.getTranslatedDescription() ); +} + +/** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ +bool TalkerChooserConf::supportsMultiInstance() { return true; } + +/** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be TalkerCode::translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ +QString TalkerChooserConf::userPlugInName() +{ + if (m_widget->talkerLineEdit->text().isEmpty()) return QString::null; + if (m_widget->appIdLineEdit->text().isEmpty() && + m_widget->reLineEdit->text().isEmpty()) return QString::null; + QString instName = m_widget->nameLineEdit->text(); + if (instName.isEmpty()) return QString::null; + return instName; +} + +void TalkerChooserConf::slotReEditorButton_clicked() +{ + // Show Regular Expression Editor dialog if it is installed. + if ( !m_reEditorInstalled ) return; + QDialog *editorDialog = + KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor" ); + if ( editorDialog ) + { + // kdeutils was installed, so the dialog was found. Fetch the editor interface. + KRegExpEditorInterface *reEditor = + static_cast<KRegExpEditorInterface *>(editorDialog->qt_cast( "KRegExpEditorInterface" ) ); + Q_ASSERT( reEditor ); // This should not fail!// now use the editor. + reEditor->setRegExp( m_widget->reLineEdit->text() ); + int dlgResult = editorDialog->exec(); + if ( dlgResult == QDialog::Accepted ) + { + QString re = reEditor->regExp(); + m_widget->reLineEdit->setText( re ); + } + delete editorDialog; + } else return; +} + +void TalkerChooserConf::slotTalkerButton_clicked() +{ + QString talkerCode = m_talkerCode.getTalkerCode(); + SelectTalkerDlg dlg( m_widget, "selecttalkerdialog", i18n("Select Talker"), talkerCode, true ); + int dlgResult = dlg.exec(); + if ( dlgResult != KDialogBase::Accepted ) return; + m_talkerCode = TalkerCode( dlg.getSelectedTalkerCode(), false ); + m_widget->talkerLineEdit->setText( m_talkerCode.getTranslatedDescription() ); + configChanged(); +} + +void TalkerChooserConf::slotLoadButton_clicked() +{ + QString dataDir = KGlobal::dirs()->findAllResources("data", "kttsd/talkerchooser/").last(); + QString filename = KFileDialog::getOpenFileName( + dataDir, + "*rc|Talker Chooser Config (*rc)", + m_widget, + "talkerchooser_loadfile"); + if ( filename.isEmpty() ) return; + KConfig* cfg = new KConfig( filename, true, false, 0 ); + load( cfg, "Filter" ); + delete cfg; + configChanged(); +} + +void TalkerChooserConf::slotSaveButton_clicked() +{ + QString filename = KFileDialog::getSaveFileName( + KGlobal::dirs()->saveLocation( "data" ,"kttsd/talkerchooser/", false ), + "*rc|Talker Chooser Config (*rc)", + m_widget, + "talkerchooser_savefile"); + if ( filename.isEmpty() ) return; + KConfig* cfg = new KConfig( filename, false, false, 0 ); + save( cfg, "Filter" ); + delete cfg; +} + +void TalkerChooserConf::slotClearButton_clicked() +{ + m_widget->nameLineEdit->setText( QString::null ); + m_widget->reLineEdit->setText( QString::null ); + m_widget->appIdLineEdit->setText( QString::null ); + m_talkerCode = TalkerCode( QString::null, false ); + m_widget->talkerLineEdit->setText( m_talkerCode.getTranslatedDescription() ); + configChanged(); +} diff --git a/kttsd/filters/talkerchooser/talkerchooserconf.h b/kttsd/filters/talkerchooser/talkerchooserconf.h new file mode 100644 index 0000000..d6b3959 --- /dev/null +++ b/kttsd/filters/talkerchooser/talkerchooserconf.h @@ -0,0 +1,134 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic Talker Chooser Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _TALKERCHOOSERCONF_H_ +#define _TALKERCHOOSERCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "filterconf.h" +#include "talkercode.h" +#include "selecttalkerdlg.h" + +// TalkerChooser includes. +#include "talkerchooserconfwidget.h" + +class KDialogBase; +class EditReplacementWidget; + +class TalkerChooserConf : public KttsFilterConf +{ + Q_OBJECT + + public: + /** + * Constructor + */ + TalkerChooserConf( QWidget *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor + */ + virtual ~TalkerChooserConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ + virtual QString userPlugInName(); + + private slots: + void slotReEditorButton_clicked(); + void slotTalkerButton_clicked(); + void slotLoadButton_clicked(); + void slotSaveButton_clicked(); + void slotClearButton_clicked(); + + private: + // Configuration Widget. + TalkerChooserConfWidget* m_widget; + // True if kdeutils Regular Expression Editor is installed. + bool m_reEditorInstalled; + // User's chosen Talker Code. + TalkerCode m_talkerCode; +}; + +#endif //_TALKERCHOOSERCONF_H_ diff --git a/kttsd/filters/talkerchooser/talkerchooserconfwidget.ui b/kttsd/filters/talkerchooser/talkerchooserconfwidget.ui new file mode 100644 index 0000000..3cdee2e --- /dev/null +++ b/kttsd/filters/talkerchooser/talkerchooserconfwidget.ui @@ -0,0 +1,319 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>TalkerChooserConfWidget</class> +<comment>Widget for configuring Talker Chooser Filter for KTTSD.</comment> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>TalkerChooserConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>547</width> + <height>240</height> + </rect> + </property> + <property name="caption"> + <string>Configure Talker Chooser</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>nameLabel</cstring> + </property> + <property name="text"> + <string>&Name:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter any name you like for this filter.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter any name you like for this filter.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox" row="1" column="0"> + <property name="name"> + <cstring>applyGroupBox</cstring> + </property> + <property name="title"> + <string>&Apply This Filter When</string> + </property> + <property name="whatsThis" stdset="0"> + <string>These settings determines when the filter is applied to text.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>reLabel</cstring> + </property> + <property name="text"> + <string>Te&xt contains:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>reLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank, the filter applies to all text jobs of any language.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>appIdLabel</cstring> + </property> + <property name="text"> + <string>Application &ID contains:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter a DCOP Application ID. This filter will only apply to text jobs queued by that application. You may enter more than one ID separated by commas. If blank, this filter applies to text jobs queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit"> + <property name="name"> + <cstring>reLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank, the filter applies to all text jobs of any language.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>reEditorButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select one or more languages. This filter will be applied to text jobs of those languages.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Enter a DCOP Application ID. This filter will only apply to text jobs queued by that application. You may enter more than one ID separated by commas. Use <b>knotify</b> to match all messages sent as KDE notifications. If blank, this filter applies to text jobs queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</qt></string> + </property> + </widget> + </vbox> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout19_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>talkerLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Talker:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>talkerLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The new Talker that will be used when the conditions above are met. The default Talker is the topmost in the Talkers tab. Click the button to choose a Talker.</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>talkerLineEdit</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="paletteForegroundColor"> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="whatsThis" stdset="0"> + <string>The new Talker that will be used when the conditions above are met. The default Talker is the topmost in the Talkers tab. Click the button to choose a Talker.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>talkerButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select a Talker.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="3" column="0"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>loadButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Load...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to load a Talker Chooser configuration from a file.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>saveButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Save...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to save this Talker Chooser to a file.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>clearButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Cl&ear</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to clear everything.</string> + <comment>What's this text</comment> + </property> + </widget> + </hbox> + </widget> + </grid> +</widget> +<tabstops> + <tabstop>nameLineEdit</tabstop> + <tabstop>reLineEdit</tabstop> + <tabstop>reEditorButton</tabstop> + <tabstop>appIdLineEdit</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/filters/talkerchooser/talkerchooserplugin.cpp b/kttsd/filters/talkerchooser/talkerchooserplugin.cpp new file mode 100644 index 0000000..4181f6a --- /dev/null +++ b/kttsd/filters/talkerchooser/talkerchooserplugin.cpp @@ -0,0 +1,32 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so Talker Chooser Filter can be used as plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "talkerchooserconf.h" +#include "talkerchooserproc.h" + +typedef K_TYPELIST_2( TalkerChooserProc, TalkerChooserConf ) TalkerChooserPlugin; +K_EXPORT_COMPONENT_FACTORY( libkttsd_talkerchooserplugin, + KGenericFactory<TalkerChooserPlugin>("kttsd_talkerchooser") ) + diff --git a/kttsd/filters/talkerchooser/talkerchooserproc.cpp b/kttsd/filters/talkerchooser/talkerchooserproc.cpp new file mode 100644 index 0000000..628b68a --- /dev/null +++ b/kttsd/filters/talkerchooser/talkerchooserproc.cpp @@ -0,0 +1,148 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic Talker Chooser Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qregexp.h> + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> + +// KTTS includes. +#include "talkercode.h" + +// Talker Chooser includes. +#include "talkerchooserproc.h" +#include "talkerchooserproc.moc" + +/** + * Constructor. + */ +TalkerChooserProc::TalkerChooserProc( QObject *parent, const char *name, const QStringList& /*args*/ ) : + KttsFilterProc(parent, name) +{ + // kdDebug() << "TalkerChooserProc::TalkerChooserProc: Running" << endl; +} + +/** + * Destructor. + */ +TalkerChooserProc::~TalkerChooserProc() +{ + // kdDebug() << "TalkerChooserProc::~TalkerChooserProc: Running" << endl; +} + +/** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ +bool TalkerChooserProc::init(KConfig* config, const QString& configGroup){ + // kdDebug() << "PlugInProc::init: Running" << endl; + config->setGroup( configGroup ); + m_re = config->readEntry( "MatchRegExp" ); + m_appIdList = config->readListEntry( "AppIDs" ); + m_chosenTalkerCode = TalkerCode(config->readEntry("TalkerCode"), false); + // Legacy settings. + QString s = config->readEntry( "LanguageCode" ); + if (!s.isEmpty()) m_chosenTalkerCode.setFullLanguageCode(s); + s = config->readEntry( "SynthInName" ); + if (!s.isEmpty()) m_chosenTalkerCode.setPlugInName(s); + s = config->readEntry( "Gender" ); + if (!s.isEmpty()) m_chosenTalkerCode.setGender(s); + s = config->readEntry( "Volume" ); + if (!s.isEmpty()) m_chosenTalkerCode.setVolume(s); + s = config->readEntry( "Rate" ); + if (!s.isEmpty()) m_chosenTalkerCode.setRate(s); + return true; +} + +/** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ +/*virtual*/ bool TalkerChooserProc::supportsAsync() { return false; } + +/** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ +/*virtual*/ QString TalkerChooserProc::convert(const QString& inputText, TalkerCode* talkerCode, + const QCString& appId) +{ + if ( !m_re.isEmpty() ) + { + int pos = inputText.find( QRegExp(m_re) ); + if ( pos < 0 ) return inputText; + } + // If appId doesn't match, return input unmolested. + if ( !m_appIdList.isEmpty() ) + { + // kdDebug() << "TalkerChooserProc::convert: converting " << inputText << " if appId " + // << appId << " matches " << m_appIdList << endl; + bool found = false; + QString appIdStr = appId; + for ( uint ndx=0; ndx < m_appIdList.count(); ++ndx ) + { + if ( appIdStr.contains(m_appIdList[ndx]) ) + { + found = true; + break; + } + } + if ( !found ) + { + // kdDebug() << "TalkerChooserProc::convert: appId not found" << endl; + return inputText; + } + } + + // Set the talker. + // kdDebug() << "TalkerChooserProc::convert: setting lang " << m_languageCode << + // " gender " << m_gender << " synth " << m_synth << + // " volume " << m_volume << " rate " << m_rate << endl; + // Only override the language if user specified a language code. + if (!m_chosenTalkerCode.fullLanguageCode().isEmpty()) + talkerCode->setFullLanguageCode(m_chosenTalkerCode.fullLanguageCode()); + talkerCode->setVoice(m_chosenTalkerCode.voice()); + talkerCode->setGender(m_chosenTalkerCode.gender()); + talkerCode->setPlugInName(m_chosenTalkerCode.plugInName()); + talkerCode->setVolume(m_chosenTalkerCode.volume()); + talkerCode->setRate(m_chosenTalkerCode.rate()); + return inputText; +} diff --git a/kttsd/filters/talkerchooser/talkerchooserproc.h b/kttsd/filters/talkerchooser/talkerchooserproc.h new file mode 100644 index 0000000..07cde68 --- /dev/null +++ b/kttsd/filters/talkerchooser/talkerchooserproc.h @@ -0,0 +1,87 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic Talker Chooser Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _TALKERCHOOSERPROC_H_ +#define _TALKERCHOOSERPROC_H_ + +// KTTS includes. +#include "filterproc.h" +#include "talkercode.h" + +class TalkerChooserProc : virtual public KttsFilterProc +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + TalkerChooserProc( QObject *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor. + */ + virtual ~TalkerChooserProc(); + + /** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ + virtual bool supportsAsync(); + + /** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + +private: + + QString m_re; + QStringList m_appIdList; + TalkerCode m_chosenTalkerCode; +}; + +#endif // _TALKERCHOOSERPROC_H_ diff --git a/kttsd/filters/xhtml2ssml/Doxyfile b/kttsd/filters/xhtml2ssml/Doxyfile new file mode 100644 index 0000000..0061358 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/Doxyfile @@ -0,0 +1,266 @@ +# Doxyfile 1.3.7-KDevelop + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = xhtml2ssml.kdevelop +PROJECT_NUMBER = $VERSION$ +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = /home/paul/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +DISTRIBUTE_GROUP_DOC = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = /home/paul/Kdecvs/kdeaccessibility/kttsd/filters +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.C \ + *.H \ + *.tlh \ + *.diff \ + *.patch \ + *.moc \ + *.xpm \ + *.dox +RECURSIVE = yes +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = yes +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = xhtml2ssml.tag +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/kttsd/filters/xhtml2ssml/demonstration.html b/kttsd/filters/xhtml2ssml/demonstration.html new file mode 100644 index 0000000..89bf8a3 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/demonstration.html @@ -0,0 +1,5 @@ +<html> + <body> + <p>Isn't it such a nice and <strong>bold</strong> day today?</p> + </body> +</html> diff --git a/kttsd/filters/xhtml2ssml/main.cpp b/kttsd/filters/xhtml2ssml/main.cpp new file mode 100644 index 0000000..822d068 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/main.cpp @@ -0,0 +1,22 @@ + +#include <qapplication.h> +#include <qfile.h> +#include <qxml.h> +#include <qmap.h> +#include <iostream> +#include "xhtml2ssml.h" +#include "xmlelement.h" + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QFile f("demonstration.html"); + QXmlInputSource input(&f); + QXmlSimpleReader reader; + XHTMLToSSMLParser *parser = new XHTMLToSSMLParser(); + reader.setContentHandler(parser); + reader.parse(input); + std::cout << parser->convertedText() << "\n"; + delete parser; + return 0; +} + diff --git a/kttsd/filters/xhtml2ssml/tagmappingrc b/kttsd/filters/xhtml2ssml/tagmappingrc new file mode 100644 index 0000000..425af8e --- /dev/null +++ b/kttsd/filters/xhtml2ssml/tagmappingrc @@ -0,0 +1,5 @@ +# This is a comment +# and another one +# a html 'b' or 'strong' tag is mapped to 'emphasis level="strong"' +b:emphasis level="strong" +strong:emphasis level="strong" diff --git a/kttsd/filters/xhtml2ssml/xhtml2ssml.cpp b/kttsd/filters/xhtml2ssml/xhtml2ssml.cpp new file mode 100644 index 0000000..7c77b9e --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xhtml2ssml.cpp @@ -0,0 +1,105 @@ + + +/**************************************************************************** + XHTMLToSSMLParser class + + Parses a piece of XHTML markup and converts into SSML. + ------------------- + Copyright: + (C) 2004 by Paul Giannaros <ceruleanblaze@gmail.com> + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> +******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#include <qstring.h> +#include <qdict.h> +#include <qxml.h> +#include <qfile.h> +#include <iostream> + +#include "xmlelement.h" +#include "xhtml2ssml.h" + + +/// Document parsing begin. Init stuff here. +bool XHTMLToSSMLParser::startDocument() { + /// Read the file which maps xhtml tags -> ssml tags. Look at the file for more information. + QFile file("tagmappingrc"); + if(!file.open(IO_ReadOnly)) { + std::cerr << "Could not read config file 'tagmappingrc'. Please check that it exists and is readable.\n"; + // Kill further parsing + return false; + } + QTextStream stream(&file); + // File parsing. + bool linestatus = true; + while(!stream.atEnd()) { + linestatus = readFileConfigEntry(stream.readLine()); + // If there's some syntactical error in the file then return false. + if(!linestatus) + return false; + /// Maybe call processEvents() to prevent GUI blockages? + } + return true; +} + +bool XHTMLToSSMLParser::startElement(const QString &, const QString &, const QString &qName, const QXmlAttributes &atts) { + QString attributes = ""; + if(atts.length() > 0) { + const int attsLength = atts.lenght(); + for(int i = 0; i < attsLength; ++i) + attributes += " " + atts.qName(i) + "=\"" + atts.value(i) + "\""; + } + QString fromelement = qName + attributes; + // If this element is one of the keys that was specified in the configuration file, get what it should be converted to and + // append to the output string. + QString toelement = m_xhtml2ssml[fromelement]; + if(toelement) + m_output.append(XMLElement::fromQString(toelement).startTag()); + return true; +} + +bool XHTMLToSSMLParser::endElement(const QString &, const QString &, const QString &qName) { + QString fromelement = qName; + QString toelement = m_xhtml2ssml[fromelement]; + if(toelement) + m_output.append(XMLElement::fromQString(toelement).endTag()); + return true; +} + +bool XHTMLToSSMLParser::characters(const QString &characters) { + m_output.append(characters); + return true; +} + + +QString XHTMLToSSMLParser::convertedText() { + return m_output.simplifyWhiteSpace(); +} + +/// Parse a line from the configuration file which maps xhtml : ssml equivalent. +/// It makes entries in the m_xhtml2ssml map accordingly. +/// @param line A line from a file to parse +/// @returns true if the syntax of the line was okay and the parsing succeeded - false otherwise. +bool XHTMLToSSMLParser::readFileConfigEntry(const QString &line) { + // comments + if(line.stripWhiteSpace().startsWith("#")) { + return true; + } + // break into QStringList + // the second parameter to split is the string, with all space simplified and all space around the : removed, i.e + // "something : somethingelse" -> "something:somethingelse" + QStringList keyvalue = QStringList::split(":", line.simplifyWhiteSpace().replace(" :", ":").replace(": ", ":")); + if(keyvalue.count() != 2) + return false; + m_xhtml2ssml[keyvalue[0]] = keyvalue[1]; + return true; +} diff --git a/kttsd/filters/xhtml2ssml/xhtml2ssml.h b/kttsd/filters/xhtml2ssml/xhtml2ssml.h new file mode 100644 index 0000000..7271dc0 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xhtml2ssml.h @@ -0,0 +1,61 @@ + +/**************************************************************************** + XHTMLToSSMLParser class + + Parses a piece of XHTML markup and converts into SSML. + ------------------- + Copyright: + (C) 2004 by Paul Giannaros <ceruleanblaze@gmail.com> + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> +******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + + +#ifndef _XHTML2SSML_H_ +#define _XHTML2SSML_H_ + +#include <qxml.h> +#include <qmap.h> + +typedef QMap<QString, QString> QStringMap; +class QString; + +class XHTMLToSSMLParser : public QXmlDefaultHandler { + +public: + /// No need to reimplement constructor.. + /// The document parsing starts + bool startDocument(); + /// start of an element encountered (<element foo="bar">) + bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes &atts); + /// end of an element encountered (</element>) + bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName); + /// text encountered (blah bah blah) + bool characters(const QString &); + + /// Get the output text that was generated during the parsing. + /// @returns The converted text. + QString convertedText(); + + /// Parse a line from the configuration file which maps xhtml : ssml equivalent. + /// It makes entries in the m_xhtml2ssml map accordingly. + /// @param line A line from a file to parse + /// @returns true if the syntax of the line was okay and the parsing succeeded - false otherwise. + bool readFileConfigEntry(const QString &line); + +private: + /// Dict of xhtml tags -> ssml tags + QStringMap m_xhtml2ssml; + /// The output of the conversion + QString m_output; +}; + +#endif diff --git a/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevelop b/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevelop new file mode 100644 index 0000000..691735f --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevelop @@ -0,0 +1,112 @@ +<?xml version = '1.0'?> +<kdevelop> + <general> + <author>Paul Giannaros</author> + <email>ceruleanblaze@gmail.com</email> + <version>$VERSION$</version> + <projectmanagement>KDevTrollProject</projectmanagement> + <primarylanguage>C++</primarylanguage> + <keywords> + <keyword>Qt</keyword> + </keywords> + </general> + <kdevfileview> + <groups> + <group pattern="*.cpp;*.cxx;*.h" name="Sources" /> + <group pattern="*.ui" name="User Interface" /> + <group pattern="*.png" name="Icons" /> + <group pattern="*" name="Others" /> + <hidenonprojectfiles>false</hidenonprojectfiles> + <hidenonlocation>false</hidenonlocation> + </groups> + <tree> + <hidepatterns>*.o,*.lo,CVS</hidepatterns> + <hidenonprojectfiles>false</hidenonprojectfiles> + </tree> + </kdevfileview> + <kdevdoctreeview> + <ignoretocs> + <toc>bash</toc> + <toc>bash_bugs</toc> + <toc>clanlib</toc> + <toc>w3c-dom-level2-html</toc> + <toc>fortran_bugs_gcc</toc> + <toc>gnome1</toc> + <toc>gnustep</toc> + <toc>gtk</toc> + <toc>gtk_bugs</toc> + <toc>haskell</toc> + <toc>haskell_bugs_ghc</toc> + <toc>java_bugs_gcc</toc> + <toc>java_bugs_sun</toc> + <toc>kde2book</toc> + <toc>opengl</toc> + <toc>pascal_bugs_fp</toc> + <toc>php</toc> + <toc>php_bugs</toc> + <toc>perl</toc> + <toc>perl_bugs</toc> + <toc>python</toc> + <toc>python_bugs</toc> + <toc>qt-kdev3</toc> + <toc>ruby</toc> + <toc>ruby_bugs</toc> + <toc>sdl</toc> + <toc>stl</toc> + <toc>w3c-svg</toc> + <toc>sw</toc> + <toc>w3c-uaag10</toc> + <toc>wxwidgets_bugs</toc> + </ignoretocs> + <ignoredoxygen> + <toc>KDE Libraries (Doxygen)</toc> + </ignoredoxygen> + </kdevdoctreeview> + <kdevdebugger> + <general> + <dbgshell/> + </general> + </kdevdebugger> + <kdevfilecreate> + <useglobaltypes> + <type ext="ui" /> + <type ext="cpp" /> + <type ext="h" /> + </useglobaltypes> + </kdevfilecreate> + <kdevtrollproject> + <general> + <activedir></activedir> + </general> + <run> + <directoryradio>executable</directoryradio> + </run> + </kdevtrollproject> + <kdevcppsupport> + <references> + <pcs>kdeincludedb</pcs> + <pcs>qtincludedb</pcs> + <pcs>qtincludesdb</pcs> + <pcs>kgmailtoincludedb</pcs> + </references> + <codecompletion> + <includeGlobalFunctions>true</includeGlobalFunctions> + <includeTypes>true</includeTypes> + <includeEnums>true</includeEnums> + <includeTypedefs>false</includeTypedefs> + <automaticCodeCompletion>true</automaticCodeCompletion> + <automaticArgumentsHint>true</automaticArgumentsHint> + <automaticHeaderCompletion>true</automaticHeaderCompletion> + <codeCompletionDelay>250</codeCompletionDelay> + <argumentsHintDelay>400</argumentsHintDelay> + <headerCompletionDelay>250</headerCompletionDelay> + </codecompletion> + </kdevcppsupport> + <kdevcvsservice> + <recursivewhenupdate>true</recursivewhenupdate> + <prunedirswhenupdate>true</prunedirswhenupdate> + <createdirswhenupdate>true</createdirswhenupdate> + <recursivewhencommitremove>true</recursivewhencommitremove> + <revertoptions>-C</revertoptions> + </kdevcvsservice> +</kdevelop> diff --git a/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevelop.pcs b/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevelop.pcs Binary files differnew file mode 100644 index 0000000..4cdb884 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevelop.pcs diff --git a/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevses b/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevses new file mode 100644 index 0000000..7128bc6 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xhtml2ssml.kdevses @@ -0,0 +1,38 @@ +<?xml version = '1.0' encoding = 'UTF-8'?> +<!DOCTYPE KDevPrjSession> +<KDevPrjSession> + <DocsAndViews NumberOfDocuments="5" > + <Doc0 NumberOfViews="1" URL="file:/home/paul/Kdecvs/kdeaccessibility/kttsd/filters/xhtml2ssml.cpp" > + <View0 line="63" Type="Source" /> + </Doc0> + <Doc1 NumberOfViews="1" URL="file:/home/paul/Kdecvs/kdeaccessibility/kttsd/filters/xhtml2ssml.h" > + <View0 line="21" Type="Source" /> + </Doc1> + <Doc2 NumberOfViews="1" URL="file:/home/paul/Kdecvs/kdeaccessibility/kttsd/filters/main.cpp" > + <View0 line="17" Type="Source" /> + </Doc2> + <Doc3 NumberOfViews="1" URL="file:/home/paul/Kdecvs/kdeaccessibility/kttsd/filters/xmlelement.cpp" > + <View0 line="72" Type="Source" /> + </Doc3> + <Doc4 NumberOfViews="1" URL="file:/home/paul/Kdecvs/kdeaccessibility/kttsd/filters/xmlelement.h" > + <View0 line="75" Type="Source" /> + </Doc4> + </DocsAndViews> + <pluginList> + <kdevbookmarks> + <bookmarks/> + </kdevbookmarks> + <kdevsubversion> + <subversion recurseresolve="1" recurserelocate="1" recursemerge="1" recursecommit="1" base="" recursepropget="1" recurseswitch="1" recurseupdate="1" recursepropset="1" recursediff="1" recurserevert="1" forcemove="1" recursecheckout="1" forceremove="1" recurseadd="1" recurseproplist="1" forcemerge="1" /> + </kdevsubversion> + <kdevvalgrind> + <executable path="" params="" /> + <valgrind path="" params="" /> + <calltree path="" params="" /> + <kcachegrind path="" /> + </kdevvalgrind> + <kdevdebugger> + <breakpointList/> + </kdevdebugger> + </pluginList> +</KDevPrjSession> diff --git a/kttsd/filters/xhtml2ssml/xhtml2ssml.pro b/kttsd/filters/xhtml2ssml/xhtml2ssml.pro new file mode 100644 index 0000000..a9b5a4a --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xhtml2ssml.pro @@ -0,0 +1,10 @@ +###################################################################### +# Automatically generated by qmake (1.07a) Tue Dec 21 21:18:47 2004 +###################################################################### + +TEMPLATE = app +INCLUDEPATH += . + +# Input +HEADERS += xhtml2ssml.h xmlelement.h +SOURCES += main.cpp xhtml2ssml.cpp xmlelement.cpp diff --git a/kttsd/filters/xhtml2ssml/xmlelement.cpp b/kttsd/filters/xhtml2ssml/xmlelement.cpp new file mode 100644 index 0000000..53fa4f6 --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xmlelement.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** + XMLElement class + + Representation of an XML element with methods for getting/setting + attributes and generating "opening" and "closing" tags. + ------------------- + Copyright: + (C) 2004 by Paul Giannaros <ceruleanblaze@gmail.com> + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> +******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#include "xmlelement.h" +#include <qstringlist.h> +#include <iostream> + +/// Constructors +XMLElement::XMLElement() { + m_name = ""; + m_attrmapper = AttributeToValueMap(); +} +XMLElement::XMLElement(const QString &name) { + m_name = name; + m_attrmapper = AttributeToValueMap(); +} +/// Destructor +XMLElement::~XMLElement() { + return; +} + +/// Copy constructor +XMLElement::XMLElement(const XMLElement &element) { + m_attrmapper = element.m_attrmapper; + m_name = element.m_name; +} + +/// Assignement operator +XMLElement XMLElement::operator=(const XMLElement &element) { + m_attrmapper = element.m_attrmapper; + m_name = element.m_name; + return *this; +} + +QString XMLElement::name() { + return m_name; +} +QString XMLElement::startTag() { + QString output = "<" + m_name + " "; + for(AttributeToValueMap::Iterator it = m_attrmapper.begin(); it != m_attrmapper.end(); ++it) { + output.append(it.key() + "=\"" + it.data() + "\" "); + } + output = output.left(output.length() - 1); + // Get rid of the space at the end and then append a '>' + output.append(">"); + return output; +} + +QString XMLElement::endTag() { + return "</" + m_name + ">"; +} + +void XMLElement::setAttribute(const QString &attr, const QString &value) { + m_attrmapper[attr] = value; +} +QString XMLElement::attribute(const QString &attr) { + return m_attrmapper[attr]; +} + +QString XMLElement::toQString() { + QString tag = startTag(); + return tag.left(tag.length() - 1).right(tag.length() - 2); +} + +XMLElement XMLElement::fromQString(const QString &str) { + QStringList sections = QStringList::split(" ", str); + QString tagname = sections[0]; + XMLElement e(tagname.latin1()); + + sections.pop_front(); + // Loop over the remaining strings which are attributes="values" + if(sections.count()) { + const int sectionsCount = sections.count(); + for(int i = 0; i < sectionsCount; ++i) { + QStringList list = QStringList::split("=", sections[i]); + if(list.count() != 2) { + std::cerr << "XMLElement::fromQString: Cannot convert list: " << list.join("|") << ". `" << str << "' is not in valid format.\n"; + return XMLElement(" "); + } + e.setAttribute(list[0], list[1].left(list[1].length() - 1).right(list[1].length() -2)); + } + } + return e; +} + diff --git a/kttsd/filters/xhtml2ssml/xmlelement.h b/kttsd/filters/xhtml2ssml/xmlelement.h new file mode 100644 index 0000000..c78cd6f --- /dev/null +++ b/kttsd/filters/xhtml2ssml/xmlelement.h @@ -0,0 +1,91 @@ +/**************************************************************************** + XMLElement class + + Representation of an XML element with methods for getting/setting + attributes and generating "opening" and "closing" tags. + ------------------- + Copyright: + (C) 2004 by Paul Giannaros <ceruleanblaze@gmail.com> + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> +******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + + +#ifndef XMLELEMENT_H +#define XMLELEMENT_H + +#include <qmap.h> + +class QString; + +typedef QMap<QString, QString> AttributeToValueMap; + +class XMLElement { +public: + XMLElement(); + XMLElement(const QString &name); + ~XMLElement(); + + /// Copy constructor + XMLElement(const XMLElement &); + + /// Assignment operator + XMLElement operator=(const XMLElement &element); + + /// Get the name of the tag (the text between the greater than and less than symbols). + /// @returns the name of the tag. + QString name(); + + /// set the name of the tag. + /// @param name the new name of the tag. + void setName(const QString &name); + + /// Get a textual representation of the starting of the tag with all attributes and their values set out. + /// @verbatim + /// XMLElement element("elem"); + /// element.addAttribute("foo", "bar"); + /// element.startTag(); <- <elem foo="bar"> + /// @endverbatim + /// @returns A textual representation of the start of the element. + QString startTag(); + + /// Get a textual representation of the closed tag that XMLElement represents. + /// @returns A textual representation of the closed tag represented by the XMLElement. + QString endTag(); + + /// Create an attribute and set its value. + /// @param attr The attribute. + /// @param value The value of the attribute. + void setAttribute(const QString &attr, const QString &value); + + /// Get the value of an attribute. + /// @param attr The attribute. + /// @returns The value of @param attr + QString attribute(const QString &attr); + + /// Convert to a QString. + /// Had issues with QMap and custom classes. For now you can just convert to/from QString and use + /// That as the key/value pair. + /// @returns A QString representation of the XMLAttribute. + QString toQString(); + + /// Create an XMLElement from a QString. + /// @param str The QString to convert from. Must be of the following syntax- "foo name=\"bar\"" + static XMLElement fromQString(const QString &str); + +private: + /// The name of the tag. + QString m_name; + /// Attribute : value mappings. + AttributeToValueMap m_attrmapper; +}; + +#endif diff --git a/kttsd/filters/xmltransformer/Makefile.am b/kttsd/filters/xmltransformer/Makefile.am new file mode 100644 index 0000000..93577e5 --- /dev/null +++ b/kttsd/filters/xmltransformer/Makefile.am @@ -0,0 +1,24 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_xmltransformerplugin.la + +libkttsd_xmltransformerplugin_la_SOURCES = \ + xmltransformerconfwidget.ui \ + xmltransformerconf.cpp \ + xmltransformerproc.cpp \ + xmltransformerplugin.cpp +libkttsd_xmltransformerplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_xmltransformerplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_xmltransformerplugin.desktop +servicesdir = $(kde_servicesdir) + +# Install data files. +xmltransformerdatadir = $(kde_datadir)/kttsd/xmltransformer/ +xmltransformerdata_DATA = xhtml2ssml.xsl xhtml2ssml_simple.xsl + +noinst_HEADERS = xmltransformerconfwidget.h diff --git a/kttsd/filters/xmltransformer/kttsd_xmltransformerplugin.desktop b/kttsd/filters/xmltransformer/kttsd_xmltransformerplugin.desktop new file mode 100644 index 0000000..6935088 --- /dev/null +++ b/kttsd/filters/xmltransformer/kttsd_xmltransformerplugin.desktop @@ -0,0 +1,88 @@ +[Desktop Entry] +Name=XML Transformer +Name[bg]=Трансформиране на XML +Name[bs]=XML transformacije +Name[ca]=Transformador XML +Name[cs]=XML transformace +Name[da]=XML Transformatør +Name[de]=XML-Umformer +Name[el]=XML μεταμορφωτής +Name[es]=Transformador XML +Name[et]=XML-i teisendus +Name[fa]=تبدیلکنندۀ XML +Name[fi]=XML-muunnin +Name[fr]=Transformation XML +Name[ga]=Trasfhoirmeoir XML +Name[gl]=Transformador XML +Name[hu]=XML-átalakító +Name[is]=XML ummyndari +Name[it]=Trasformatore XML +Name[ja]=XML 変換 +Name[ka]=XML გარდამქმნელი +Name[km]= កម្មវិធីប្លែង XML +Name[mk]=XML-трансформирач +Name[ms]=Transformer XML +Name[nds]=XML-Ümwanneln +Name[ne]=XML रुपान्तरणकर्ता +Name[nl]=XML-omvormer +Name[pa]=XML ਤਬਦੀਲੀਕਾਰ +Name[pl]=Transformacja XML +Name[pt]=Transformação de XML +Name[pt_BR]=Transformador XML +Name[ru]=Преобразователь XML +Name[sk]=Transformácia XML +Name[sl]=Pretvornik XML +Name[sr]=XML трансформатор +Name[sr@Latn]=XML transformator +Name[sv]=XML-transformering +Name[tg]=Тағйирдиҳии XML +Name[tr]=XML Dönüştürücü +Name[uk]=Перетворення XML +Name[vi]=Trình chuyển đổi XML +Name[zh_TW]=XML 轉換器 +Comment=Generic XML Tranformation Filter Plugin for KTTS +Comment[bg]=Приставка филтър за трансформиране на XML за KTTS +Comment[ca]=Connector del filtre genèric del transformador XML pel KTTS +Comment[cs]=Modul filtru obecné XML transformace KTTS +Comment[da]=Generisk XML Tranformationsfilter-plugin for KTTS +Comment[de]=Generisches XML-Umformungs-Filtermodul für KTTS +Comment[el]=Γενικό φίλτρο πρόσθετο XML μεταμόρφωσης για το KTTS +Comment[es]=Complemento de Audio para KTTSD +Comment[et]=KTTS-i üldine XML-i teisenduse filtri plugin +Comment[eu]=KTTS-ren XML transformazioen iragazki-plugin generikoa +Comment[fa]=وصلۀ پالایۀ انتقال XML عمومی برای KTTS +Comment[fi]=Yleinen XML-muuntimen suodatin liitännäinen KTTS-ohjelmalle +Comment[fr]=Module de filtrage de transformation XML générique pour KTTS +Comment[gl]=Plugin de Transformación de XML para KTTS +Comment[hu]=Általános XML-átalakító modul a KTTS-hez +Comment[is]=Almennt XML ummynda íforrit fyrir KTTS +Comment[it]=Plugin generico per il filtro di trasformazione XML per KTTS +Comment[ja]=KTTS 用汎用 XML 変換フィルタプラグイン +Comment[ka]=ზოგადი XML გარდამქმნელის ფილტრის მოდული KTTS-სთვის +Comment[km]=កម្មវិធីជំនួសតម្រងការប្លែង XML ទូទៅសម្រាប់ KTTS +Comment[mk]=Филтер за KTTS за трансформација на општ XML +Comment[ms]=Plugin Penapis Transformasi XML Generik bagi KTTS +Comment[nb]=Filter-programtillegg til KTTS for alminnelig XML-transformasjon +Comment[nds]=KTTS-Filtermoduul för dat Ümwanneln vun XML +Comment[ne]=KTTS का लागि जेनेरीक XML रुपान्तरण फिल्टर प्लगइन +Comment[nl]=Generieke filterplugin voor XML-omvorming voor KTTS +Comment[pa]=KTTS ਲਈ XML ਸੰਚਾਰ ਫਿਲਟਰ +Comment[pl]=Wtyczka transformacji XML dla KTTS +Comment[pt]='Plugin' Genérico de Transformação de XML para o KTTS +Comment[pt_BR]=Plug-in de Filtro Genérico de Transformação XML: para o KTTSD +Comment[ru]=Фильтр XML для KTTS +Comment[sk]=Všeobecný modul filtra pre transformáciu XML v KTTS +Comment[sl]=Filtrni vstavek KTTS za generično preoblikovanje XML +Comment[sr]=Генерички филтерски прикључак KTTS-а за трансформацију XML-а +Comment[sr@Latn]=Generički filterski priključak KTTS-a za transformaciju XML-a +Comment[sv]=Insticksprogram för KTTS med generellt XML-transformeringsfilter +Comment[ta]=KTTSக்கான ஜெனரிக் XML மாற்று அலங்கார சொருகுப்பொருள் +Comment[tg]=Филтри XMLбарои KTTS +Comment[tr]=KTTS için genel XML Dönüştürücü Süzgeci Eklentisi +Comment[uk]=Втулок загального фільтра перетворення XML для KTTS +Comment[vi]=Trình bổ sung Lọc Chuyển đổi XML Chung cho KTTS +Comment[zh_TW]=KTTS 使用的一般 XML 轉換外掛程式 +Type=Service +ServiceTypes=KTTSD/FilterPlugin +X-KDE-Library=libkttsd_xmltransformerplugin +X-KDE-Languages=en,en_US,en_GB,en_CA,es,es_mx,cy,de,fi,cs,pl diff --git a/kttsd/filters/xmltransformer/xhtml2ssml.xsl b/kttsd/filters/xmltransformer/xhtml2ssml.xsl new file mode 100644 index 0000000..5c7ca33 --- /dev/null +++ b/kttsd/filters/xmltransformer/xhtml2ssml.xsl @@ -0,0 +1,252 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- *********************************************************************** + Stylesheet for transforming XHTML into SSML markup. + ============ + Copyright : (C) 2005 by Gary Cramblitt + ============ + Original author: Gary Cramblitt <garycramblitt@comcast.net> + *************************************************************************** + + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + *************************************************************************** --> + +<!-- *********************************************************************** + The rendering is roughly based on the stylesheet from Appendix A of the + CSS2 specification, http://www.w3.org/TR/REC-CSS2/sample.html + + @media speech { + H1, H2, H3, + H4, H5, H6 { voice-family: paul, male; stress: 20; richness: 90 } + H1 { pitch: x-low; pitch-range: 90 } + H2 { pitch: x-low; pitch-range: 80 } + H3 { pitch: low; pitch-range: 70 } + H4 { pitch: medium; pitch-range: 60 } + H5 { pitch: medium; pitch-range: 50 } + H6 { pitch: medium; pitch-range: 40 } + LI, DT, DD { pitch: medium; richness: 60 } + DT { stress: 80 } + PRE, CODE, TT { pitch: medium; pitch-range: 0; stress: 0; richness: 80 } + EM { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } + STRONG { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } + DFN { pitch: high; pitch-range: 60; stress: 60 } + S, STRIKE { richness: 0 } + I { pitch: medium; pitch-range: 60; stress: 60; richness: 50 } + B { pitch: medium; pitch-range: 60; stress: 90; richness: 90 } + U { richness: 0 } + A:link { voice-family: harry, male } + A:visited { voice-family: betty, female } + A:active { voice-family: betty, female; pitch-range: 80; pitch: x-high } +} + +As SSML does not seem to offer an equivalent for "stress" and "richness". +They are mapped to rate and volume respectively. + + H1 { male; pitch: x-low; range: x-high; rate: slow; volume: x-loud} + H2 { male; pitch: x-low; range: high; rate: slow; volume: x-loud } + H3 { male; pitch: low; range: high; rate: slow; volume: x-loud } + H4 { male; pitch: medium; range: medium; rate: slow; volume: x-loud } + H5 { male; pitch: medium; range: low; rate: slow; volume: x-loud } + H6 { male; pitch: medium; range: x-low; rate: slow; volume: x-loud } + LI, DD { pitch: medium; } + DT { pitch: medium; rate: x-fast } + PRE, CODE, TT { pitch: medium; range: x-low; rate: slow; volume: loud } + EM { pitch: medium; range: medium; rate: medium; volume: loud } + STRONG { pitch: medium; range: medium; rate: x-fast; volume: x-loud } + DFN { pitch: high; range: medium; rate: medium } + S, STRIKE { volume: x-soft } + I { pitch: high; range: medium; rate: fast; volume: medium } + B { pitch: high; range: medium; rate: x-fast; volume: x-loud } + U { volume: medium } + A { female } + + *********************************************************************** --> + +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="xml" indent="no"/> + +<!-- root --> +<xsl:template match="/"> + <xsl:apply-templates/> +</xsl:template> + +<!-- html --> +<!-- local-name() must be used in order to ignore namespaces. --> +<xsl:template match="*[local-name()='html' or local-name()='HTML']"> + <xsl:apply-templates/> +</xsl:template> + +<!-- Ignore header, speak the body of xhtml document. --> +<xsl:template match="*[local-name()='head' or local-name()='HEAD']"/> +<xsl:template match="*[local-name()='body' or local-name()='BODY']"> + <xsl:element name="speak"> + <xsl:copy-of select="/html/@lang"/> + <xsl:copy-of select="/HTML/@lang"/> + <xsl:apply-templates/> + </xsl:element> +</xsl:template> + +<!-- Paragraph --> +<xsl:template match="*[local-name()='p' or local-name()='P']"> + <p><xsl:apply-templates/></p> +</xsl:template> + +<!-- H1 { pitch: x-low; range: x-high; rate: slow; volume: x-loud } --> +<xsl:template match="*[local-name()='h1' or local-name()='H1']"> + <voice gender="male"><prosody pitch="x-low" range="x-high" rate="slow" volume="x-loud"> + <xsl:apply-templates/> + </prosody></voice> +</xsl:template> + +<!-- H2 { male; pitch: x-low; range: high; rate: slow; volume: x-loud } --> +<xsl:template match="*[local-name()='h2' or local-name()='H2']"> + <voice gender="male"><prosody pitch="x-low" range="high" rate="slow" volume="x-loud"> + <xsl:apply-templates/> + </prosody></voice> +</xsl:template> + +<!-- H3 { male; pitch: low; range: high; rate: slow; volume: x-loud } --> +<xsl:template match="*[local-name()='h3' or local-name()='H3']"> + <voice gender="male"><prosody pitch="low" range="high" rate="slow" volume="x-loud"> + <xsl:apply-templates/> + </prosody></voice> +</xsl:template> + +<!-- H4 { male; pitch: medium; range: medium; rate: slow; volume: x-loud } --> +<xsl:template match="*[local-name()='h4' or local-name()='H4']"> + <voice gender="male"><prosody pitch="medium" range="medium" rate="slow" volume="x-loud"> + <xsl:apply-templates/> + </prosody></voice> +</xsl:template> + +<!-- H5 { male; pitch: medium; range: low; rate: slow; volume: x-loud } --> +<xsl:template match="*[local-name()='h5' or local-name()='H5']"> + <voice gender="male"><prosody pitch="low" range="low" rate="slow" volume="x-loud"> + <xsl:apply-templates/> + </prosody></voice> +</xsl:template> + +<!-- H6 { male; pitch: medium; range: x-low; rate: slow; volume: x-loud } --> +<xsl:template match="*[local-name()='h6' or local-name()='H6']"> + <voice gender="male"><prosody pitch="medium" range="x-low" rate="slow" volume="x-loud"> + <xsl:apply-templates/> + </prosody></voice> +</xsl:template> + +<!-- LI, DD { pitch: medium; } --> +<xsl:template match="*[local-name()='li' or local-name()='LI']"> + <prosody pitch="medium"> + <xsl:apply-templates/> + </prosody> +</xsl:template> +<xsl:template match="*[local-name()='dd' or local-name()='DD']"> + <prosody pitch="medium"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- DT { pitch: medium; rate: x-fast } --> +<xsl:template match="*[local-name()='dt' or local-name()='DT']"> + <prosody pitch="medium" rate="x-fast"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- PRE, CODE, TT { pitch: medium; range: x-low; rate: slow; volume: loud } --> +<xsl:template match="*[local-name()='pre' or local-name()='PRE']"> + <prosody pitch="medium" range="x-low" rate="slow" volume="loud"> + <xsl:apply-templates/> + </prosody> +</xsl:template> +<xsl:template match="*[local-name()='code' or local-name()='CODE']"> + <prosody pitch="medium" range="x-low" rate="slow" volume="loud"> + <xsl:apply-templates/> + </prosody> +</xsl:template> +<xsl:template match="*[local-name()='tt' or local-name()='TT']"> + <prosody pitch="medium" range="x-low" rate="slow" volume="loud"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- EM { pitch: medium; range: medium; rate: medium; volume: loud } --> +<xsl:template match="*[local-name()='em' or local-name()='EM']"> + <prosody pitch="medium" range="medium" rate="medium" volume="loud"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- STRONG { pitch: medium; range: medium; rate: x-fast; volume: x-loud } --> +<xsl:template match="*[local-name()='strong' or local-name()='STRONG']"> + <prosody pitch="medium" range="medium" rate="x-fast" volume="x-loud"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- DFN { pitch: high; range: medium; rate: medium } --> +<xsl:template match="*[local-name()='dfn' or local-name()='DFN']"> + <prosody pitch="high" range="medium" rate="medium"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- S, STRIKE { volume: x-soft } --> +<xsl:template match="*[local-name()='s' or local-name()='S']"> + <prosody volume="x-soft"> + <xsl:apply-templates/> + </prosody> +</xsl:template> +<xsl:template match="*[local-name()='strike' or local-name()='STRIKE']"> + <prosody volume="x-soft"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- I { pitch: high; range: medium; rate: fast; volume: medium } --> +<xsl:template match="*[local-name()='i' or local-name()='I']"> + <prosody pitch="high" range="medium" rate="fast" volume="medium"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- B { pitch: high; range: medium; rate: x-fast; volume: x-loud } --> +<xsl:template match="*[local-name()='b' or local-name()='B']"> + <prosody pitch="high" range="medium" rate="x-fast" volume="x-loud"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- U { volume: medium } --> +<xsl:template match="*[local-name()='u' or local-name()='U']"> + <prosody pitch="medium"> + <xsl:apply-templates/> + </prosody> +</xsl:template> + +<!-- A { female } --> +<xsl:template match="*[local-name()='a' or local-name()='A']"> + <voice gender="female"> + <xsl:apply-templates/> + </voice> +</xsl:template> + +<!-- HREF attribute --> +<xsl:template match="@href"> + <prosody volume="soft">Address</prosody> + <prosody rate="x-fast"> + <xsl:value-of select="."/> + </prosody> +</xsl:template> + +<!-- Ignore scripts. --> +<xsl:template match="*[local-name()='script' or local-name()='SCRIPT']"/> + +<!-- Ignore styles. --> +<xsl:template match="*[local-name()='style' or local-name()='STYLE']"/> + +</xsl:stylesheet> diff --git a/kttsd/filters/xmltransformer/xhtml2ssml_simple.xsl b/kttsd/filters/xmltransformer/xhtml2ssml_simple.xsl new file mode 100644 index 0000000..2c1f3be --- /dev/null +++ b/kttsd/filters/xmltransformer/xhtml2ssml_simple.xsl @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- *********************************************************************** + Stylesheet for transforming XHTML into SSML markup. + ============ + Copyright : (C) 2005 by Gary Cramblitt + ============ + Original author: Gary Cramblitt <garycramblitt@comcast.net> + *************************************************************************** + + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + *************************************************************************** --> + +<!-- *********************************************************************** + The rendering takes a minimalist approach, mapping <b>, <em>, <i> + etc. to louder voices. Everything else is pretty much mapped to just + paragraph and sentence tags. Hyperlink addresses are spoken fast. + *********************************************************************** --> + +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="xml" indent="no"/> + +<!-- root --> +<xsl:template match="/"> + <xsl:apply-templates/> +</xsl:template> + +<!-- html --> +<!-- local-name() must be used in order to ignore namespaces. --> +<xsl:template match="*[local-name()='html' or local-name()='HTML']"> + <xsl:apply-templates/> +</xsl:template> + +<!-- Ignore header, speak the body of xhtml document. --> +<xsl:template match="*[local-name()='head' or local-name()='HEAD']"/> +<xsl:template match="*[local-name()='body' or local-name()='BODY']"> + <xsl:element name="speak"> + <xsl:copy-of select="/html/@lang"/> + <xsl:copy-of select="/HTML/@lang"/> + <xsl:apply-templates/> + </xsl:element> +</xsl:template> + +<!-- Paragraph --> +<xsl:template match="*[local-name()='p' or local-name()='P']"> + <xsl:apply-templates/> +</xsl:template> + +<!-- H1, H2, H3, H4, H5, H6: ignore tag, speak content as sentence. --> +<xsl:template match="*[contains('h1|h2|h3|h4|h5|h6|H1|H2|H3|H4|H5|H6|',concat(local-name(),'|'))]"> + <xsl:apply-templates/> +</xsl:template> + +<!-- DFN, LI, DD, DT: ignore tag, speak content. --> +<xsl:template match="*[contains('dfn|li|dd|dt|DFN|LI|DD|DT|',concat(local-name(),'|'))]"> + <xsl:apply-templates/> +</xsl:template> + +<!-- PRE, CODE, TT; ignore tag, speak content. --> +<xsl:template match="*[contains('pre|code|tt|PRE|CODE|TT|',concat(local-name(),'|'))]"> + <xsl:apply-templates/> +</xsl:template> + +<!-- EM, STRONG, I, B, S, STRIKE, U: speak emphasized. --> +<xsl:template match="*[contains('em|strong|i|b|s|strike|EM|STRONG|I|B|S|STRIKE|',concat(local-name(),'|'))]"> + <emphasis level="strong"> + <xsl:apply-templates/> + </emphasis> +</xsl:template> + +<!-- A: speak hyperlink emphasized, address fast. --> +<xsl:template match="*[local-name()='a' or local-name()='A']"> + <xsl:if test="@href"> + <emphasis level="moderate"> + <xsl:apply-templates/> + </emphasis> + <prosody rate="fast" volume="soft"> + <xsl:value-of select="'Link to. '"/> + <xsl:value-of select="@href"/> + </prosody> + </xsl:if> +</xsl:template> + +<!-- Ignore scripts. --> +<xsl:template match="*[local-name()='script' or local-name()='SCRIPT']"/> + +<!-- Ignore styles. --> +<xsl:template match="*[local-name()='style' or local-name()='STYLE']"/> + +</xsl:stylesheet> diff --git a/kttsd/filters/xmltransformer/xmltransformerconf.cpp b/kttsd/filters/xmltransformer/xmltransformerconf.cpp new file mode 100644 index 0000000..76cfd70 --- /dev/null +++ b/kttsd/filters/xmltransformer/xmltransformerconf.cpp @@ -0,0 +1,182 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic XML Transformation Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qstring.h> +#include <qlayout.h> + +// KDE includes. +#include <klocale.h> +#include <klineedit.h> +#include <kconfig.h> +#include <kdialog.h> +#include <kurlrequester.h> +#include <kstandarddirs.h> + +// KTTS includes. +#include "filterconf.h" + +// XmlTransformer includes. +#include "xmltransformerconf.h" +#include "xmltransformerconf.moc" + +/** +* Constructor +*/ +XmlTransformerConf::XmlTransformerConf( QWidget *parent, const char *name, const QStringList& /*args*/) : + KttsFilterConf(parent, name) +{ + // kdDebug() << "XmlTransformerConf::XmlTransformerConf: Running" << endl; + + // Create configuration widget. + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "XmlTransformerConfWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new XmlTransformerConfWidget(this, "XmlTransformerConfigWidget"); + layout->addWidget(m_widget); + + // Set up defaults. + defaults(); + + // Connect signals. + connect( m_widget->nameLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect( m_widget->xsltPath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->xsltprocPath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->rootElementLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->doctypeLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); + connect( m_widget->appIdLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged()) ); +} + +/** +* Destructor. +*/ +XmlTransformerConf::~XmlTransformerConf(){ + // kdDebug() << "XmlTransformerConf::~XmlTransformerConf: Running" << endl; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void XmlTransformerConf::load(KConfig* config, const QString& configGroup){ + // kdDebug() << "XmlTransformerConf::load: Running" << endl; + config->setGroup( configGroup ); + m_widget->nameLineEdit->setText( config->readEntry( "UserFilterName", m_widget->nameLineEdit->text() ) ); + m_widget->xsltPath->setURL( config->readEntry( "XsltFilePath", m_widget->xsltPath->url() ) ); + m_widget->xsltprocPath->setURL( config->readEntry( "XsltprocPath", m_widget->xsltprocPath->url() ) ); + m_widget->rootElementLineEdit->setText( + config->readEntry( "RootElement", m_widget->rootElementLineEdit->text() ) ); + m_widget->doctypeLineEdit->setText( + config->readEntry( "DocType", m_widget->doctypeLineEdit->text() ) ); + m_widget->appIdLineEdit->setText( + config->readEntry( "AppID", m_widget->appIdLineEdit->text() ) ); +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void XmlTransformerConf::save(KConfig* config, const QString& configGroup){ + // kdDebug() << "XmlTransformerConf::save: Running" << endl; + config->setGroup( configGroup ); + config->writeEntry( "UserFilterName", m_widget->nameLineEdit->text() ); + config->writeEntry( "XsltFilePath", realFilePath( m_widget->xsltPath->url() ) ); + config->writeEntry( "XsltprocPath", realFilePath( m_widget->xsltprocPath->url() ) ); + config->writeEntry( "RootElement", m_widget->rootElementLineEdit->text() ); + config->writeEntry( "DocType", m_widget->doctypeLineEdit->text() ); + config->writeEntry( "AppID", m_widget->appIdLineEdit->text().replace(" ", "") ); +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void XmlTransformerConf::defaults(){ + // kdDebug() << "XmlTransformerConf::defaults: Running" << endl; + // Default name. + m_widget->nameLineEdit->setText(i18n( "XML Transformer" )); + // Default XSLT path to installed xsl files. + m_widget->xsltPath->setURL( locate("data", "kttsd/xmltransformer/") ); + // Default path to xsltproc. + m_widget->xsltprocPath->setURL( "xsltproc" ); + // Default root element to "html". + m_widget->rootElementLineEdit->setText( "html" ); + // Default doctype to blank. + m_widget->doctypeLineEdit->setText( "" ); + // Default App ID to blank. + m_widget->appIdLineEdit->setText( "" ); + // kdDebug() << "XmlTransformerConf::defaults: Exiting" << endl; +} + +/** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ +bool XmlTransformerConf::supportsMultiInstance() { return true; } + +/** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ +QString XmlTransformerConf::userPlugInName() +{ + QString filePath = realFilePath(m_widget->xsltprocPath->url()); + if (filePath.isEmpty()) return QString::null; + if (getLocation(filePath).isEmpty()) return QString::null; + + filePath = realFilePath(m_widget->xsltPath->url()); + if (filePath.isEmpty()) return QString::null; + if (getLocation(filePath).isEmpty()) return QString::null; + if (!QFileInfo(filePath).isFile()) return QString::null; + + return m_widget->nameLineEdit->text(); +} diff --git a/kttsd/filters/xmltransformer/xmltransformerconf.h b/kttsd/filters/xmltransformer/xmltransformerconf.h new file mode 100644 index 0000000..86df0e2 --- /dev/null +++ b/kttsd/filters/xmltransformer/xmltransformerconf.h @@ -0,0 +1,120 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic XML Transformation Filter Configuration class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _XMLTRANSFORMERCONF_H_ +#define _XMLTRANSFORMERCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "filterconf.h" + +// XmlTransformer includes. +#include "xmltransformerconfwidget.h" + +class XmlTransformerConf : public KttsFilterConf +{ + Q_OBJECT + + public: + /** + * Constructor + */ + XmlTransformerConf( QWidget *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor + */ + virtual ~XmlTransformerConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ + virtual QString userPlugInName(); + + private slots: + + private: + // Configuration Widget. + XmlTransformerConfWidget* m_widget; +}; + +#endif //_XMLTRANSFORMERCONF_H_ diff --git a/kttsd/filters/xmltransformer/xmltransformerconfwidget.ui b/kttsd/filters/xmltransformer/xmltransformerconfwidget.ui new file mode 100644 index 0000000..4975d9c --- /dev/null +++ b/kttsd/filters/xmltransformer/xmltransformerconfwidget.ui @@ -0,0 +1,249 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>XmlTransformerConfWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>XmlTransformerConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>548</width> + <height>256</height> + </rect> + </property> + <property name="caption"> + <string>Configure XML Transformer</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit"> + <property name="name"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter any descriptive name you like for this filter.</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>xsltPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the full path to an XML Style Language - Transforms (XSLT) stylesheet file. XSLT files usually end with extension .xsl.</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>xsltprocPath</cstring> + </property> + <property name="url" stdset="0"> + <string>xsltproc</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the path to the xsltproc executable program. If it is in the PATH environment variable, just enter "xsltproc".</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>nameLabel</cstring> + </property> + <property name="text"> + <string>&Name:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>nameLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter any descriptive name you like for this filter.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>xsltLabel</cstring> + </property> + <property name="text"> + <string>&XSLT file:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>xsltPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the full path to an XML Style Language - Transforms (XSLT) stylesheet file. XSLT files usually end with extension .xsl.</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>xsltprocLabel</cstring> + </property> + <property name="text"> + <string>xsltproc &executable:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>xsltprocPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Enter the path to the xsltproc executable program. If it is in the PATH environment variable, just enter "xsltproc".</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QGroupBox" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>applyGroupBox</cstring> + </property> + <property name="title"> + <string>Apply This &Filter When</string> + </property> + <property name="whatsThis" stdset="0"> + <string>These settings determines when the filter is applied to text.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KLineEdit"> + <property name="name"> + <cstring>rootElementLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter will be applied only to text having the specified XML root element. If blank, applies to all text. You may enter more than one root element separated by commas. Example: "html".</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>doctypeLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter will be applied only to text having the specified DOCTYPE specification. If blank, applies to all text. You may enter more than one DOCTYPE separated by commas. Example: "xhtml".</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Enter a DCOP Application ID. This filter will only apply to text queued by that application. You may enter more than one ID separated by commas. Use <b>knotify</b> to match all messages sent as KDE notifications. If blank, this filter applies to text queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</qt></string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>rootElementLabel</cstring> + </property> + <property name="text"> + <string>&Root element is:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>rootElementLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter will be applied only to text having the specified XML root element. If blank, applies to all text. You may enter more than one root element separated by commas. Example: "html".</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>doctypeLabel</cstring> + </property> + <property name="text"> + <string>or DOC&TYPE is:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>doctypeLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter will be applied only to text having the specified DOCTYPE specification. If blank, applies to all text. You may enter more than one DOCTYPE separated by commas. Example: "xhtml".</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>appIdLabel</cstring> + </property> + <property name="text"> + <string>and Application &ID contains:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>appIdLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Enter a DCOP Application ID. This filter will only apply to text queued by that application. You may enter more than one ID separated by commas. Use <b>knotify</b> to match all messages sent as KDE notifications. If blank, this filter applies to text queued by all applications. Tip: Use kdcop from the command line to get the Application IDs of running applications. Example: "konversation, kvirc,ksirc,kopete"</qt></string> + </property> + </widget> + </vbox> + </widget> + </grid> + </widget> + </grid> +</widget> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klineedit.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>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/kttsd/filters/xmltransformer/xmltransformerplugin.cpp b/kttsd/filters/xmltransformer/xmltransformerplugin.cpp new file mode 100644 index 0000000..c76c889 --- /dev/null +++ b/kttsd/filters/xmltransformer/xmltransformerplugin.cpp @@ -0,0 +1,31 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so XML Transformer Filter can be used as plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "xmltransformerconf.h" +#include "xmltransformerproc.h" + +typedef K_TYPELIST_2( XmlTransformerProc, XmlTransformerConf ) XmlTransformerPlugin; +K_EXPORT_COMPONENT_FACTORY( libkttsd_xmltransformerplugin, + KGenericFactory<XmlTransformerPlugin>("kttsd_xmltransformer") ) diff --git a/kttsd/filters/xmltransformer/xmltransformerproc.cpp b/kttsd/filters/xmltransformer/xmltransformerproc.cpp new file mode 100644 index 0000000..d4aa1c5 --- /dev/null +++ b/kttsd/filters/xmltransformer/xmltransformerproc.cpp @@ -0,0 +1,385 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic XML Transformation Filter Processing class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qfile.h> +#include <qregexp.h> + +// KDE includes. +#include <kdeversion.h> +#include <kconfig.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kprocess.h> +#include <kdebug.h> + +// KTTS includes. +#include "filterproc.h" +#include "utils.h" + +// XmlTransformer includes. +#include "xmltransformerproc.h" +#include "xmltransformerproc.moc" + +/** + * Constructor. + */ +XmlTransformerProc::XmlTransformerProc( QObject *parent, const char *name, const QStringList& ) : + KttsFilterProc(parent, name) +{ + m_xsltProc = 0; +} + +/** + * Destructor. + */ +/*virtual*/ XmlTransformerProc::~XmlTransformerProc() +{ + delete m_xsltProc; + if (!m_inFilename.isEmpty()) QFile::remove(m_inFilename); + if (!m_outFilename.isEmpty()) QFile::remove(m_outFilename); +} + +/** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ +bool XmlTransformerProc::init(KConfig* config, const QString& configGroup) +{ + // kdDebug() << "XmlTransformerProc::init: Running." << endl; + config->setGroup( configGroup ); + m_UserFilterName = config->readEntry( "UserFilterName" ); + m_xsltFilePath = config->readEntry( "XsltFilePath" ); + m_xsltprocPath = config->readEntry( "XsltprocPath" ); + m_rootElementList = config->readListEntry( "RootElement", ',' ); + m_doctypeList = config->readListEntry( "DocType", ',' ); + m_appIdList = config->readListEntry( "AppID", ',' ); + kdDebug() << "XmlTransformerProc::init: m_xsltprocPath = " << m_xsltprocPath << endl; + kdDebug() << "XmlTransformerProc::init: m_xsltFilePath = " << m_xsltFilePath << endl; + return ( m_xsltFilePath.isEmpty() || m_xsltprocPath.isEmpty() ); +} + +/** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ +/*virtual*/ bool XmlTransformerProc::supportsAsync() { return true; } + +/** + * Convert input, returning output. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ +/*virtual*/ QString XmlTransformerProc::convert(const QString& inputText, TalkerCode* talkerCode, + const QCString& appId) +{ + // kdDebug() << "XmlTransformerProc::convert: Running." << endl; + // If not properly configured, do nothing. + if ( m_xsltFilePath.isEmpty() || m_xsltprocPath.isEmpty() ) + { + kdDebug() << "XmlTransformerProc::convert: not properly configured" << endl; + return inputText; + } + // Asynchronously convert and wait for completion. + if (asyncConvert(inputText, talkerCode, appId)) + { + waitForFinished(); + m_state = fsIdle; + return m_text; + } else + return inputText; +} + +/** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ +/*virtual*/ bool XmlTransformerProc::asyncConvert(const QString& inputText, TalkerCode* /*talkerCode*/, + const QCString& appId) +{ + m_wasModified = false; + + // kdDebug() << "XmlTransformerProc::asyncConvert: Running." << endl; + m_text = inputText; + // If not properly configured, do nothing. + if ( m_xsltFilePath.isEmpty() || m_xsltprocPath.isEmpty() ) + { + kdDebug() << "XmlTransformerProc::asyncConvert: not properly configured." << endl; + return false; + } + + bool found = false; + // If not correct XML type, or DOCTYPE, do nothing. + if ( !m_rootElementList.isEmpty() ) + { + // kdDebug() << "XmlTransformerProc::asyncConvert:: searching for root elements " << m_rootElementList << endl; + for ( uint ndx=0; ndx < m_rootElementList.count(); ++ndx ) + { + if ( KttsUtils::hasRootElement( inputText, m_rootElementList[ndx] ) ) + { + found = true; + break; + } + } + if ( !found && m_doctypeList.isEmpty() ) + { + kdDebug() << "XmlTransformerProc::asyncConvert: Did not find root element(s)" << m_rootElementList << endl; + return false; + } + } + if ( !found && !m_doctypeList.isEmpty() ) + { + for ( uint ndx=0; ndx < m_doctypeList.count(); ++ndx ) + { + if ( KttsUtils::hasDoctype( inputText, m_doctypeList[ndx] ) ) + { + found = true; + break; + } + } + if ( !found ) + { + // kdDebug() << "XmlTransformerProc::asyncConvert: Did not find doctype(s)" << m_doctypeList << endl; + return false; + } + } + + // If appId doesn't match, return input unmolested. + if ( !m_appIdList.isEmpty() ) + { + QString appIdStr = appId; + // kdDebug() << "XmlTransformrProc::convert: converting " << inputText << " if appId " + // << appId << " matches " << m_appIdList << endl; + found = false; + for ( uint ndx=0; ndx < m_appIdList.count(); ++ndx ) + { + if ( appIdStr.contains(m_appIdList[ndx]) ) + { + found = true; + break; + } + } + if ( !found ) + { + // kdDebug() << "XmlTransformerProc::asyncConvert: Did not find appId(s)" << m_appIdList << endl; + return false; + } + } + + /// Write @param text to a temporary file. + KTempFile inFile(locateLocal("tmp", "kttsd-"), ".xml"); + m_inFilename = inFile.file()->name(); + QTextStream* wstream = inFile.textStream(); + if (wstream == 0) { + /// wtf... + kdDebug() << "XmlTransformerProc::convert: Can't write to " << m_inFilename << endl;; + return false; + } + // TODO: Is encoding an issue here? + // If input does not have xml processing instruction, add it. + if (!inputText.startsWith("<?xml")) *wstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + // FIXME: Temporary Fix until Konqi returns properly formatted xhtml with & coded as & + // This will change & inside a CDATA section, which is not good, and also within comments and + // processing instructions, which is OK because we don't speak those anyway. + QString text = inputText; + text.replace(QRegExp("&(?!amp;)"),"&"); + *wstream << text; + inFile.close(); +#if KDE_VERSION >= KDE_MAKE_VERSION (3,3,0) + inFile.sync(); +#endif + + // Get a temporary output file name. + KTempFile outFile(locateLocal("tmp", "kttsd-"), ".output"); + m_outFilename = outFile.file()->name(); + outFile.close(); + // outFile.unlink(); // only activate this if necessary. + + /// Spawn an xsltproc process to apply our stylesheet to input file. + m_xsltProc = new KProcess; + *m_xsltProc << m_xsltprocPath; + *m_xsltProc << "-o" << m_outFilename << "--novalid" + << m_xsltFilePath << m_inFilename; + // Warning: This won't compile under KDE 3.2. See FreeTTS::argsToStringList(). + // kdDebug() << "SSMLConvert::transform: executing command: " << + // m_xsltProc->args() << endl; + + m_state = fsFiltering; + connect(m_xsltProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + connect(m_xsltProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_xsltProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + if (!m_xsltProc->start(KProcess::NotifyOnExit, + static_cast<KProcess::Communication>(KProcess::Stdout | KProcess::Stderr))) + { + kdDebug() << "XmlTransformerProc::convert: Error starting xsltproc" << endl; + m_state = fsIdle; + return false; + } + return true; +} + +// Process output when xsltproc exits. +void XmlTransformerProc::processOutput() +{ + QFile::remove(m_inFilename); + + int exitStatus = 11; + if (m_xsltProc->normalExit()) + exitStatus = m_xsltProc->exitStatus(); + else + kdDebug() << "XmlTransformerProc::processOutput: xsltproc was killed." << endl; + + delete m_xsltProc; + m_xsltProc = 0; + + if (exitStatus != 0) + { + kdDebug() << "XmlTransformerProc::processOutput: xsltproc abnormal exit. Status = " << exitStatus << endl; + m_state = fsFinished; + QFile::remove(m_outFilename); + emit filteringFinished(); + return; + } + + /// Read back the data that was written to /tmp/fileName.output. + QFile readfile(m_outFilename); + if(!readfile.open(IO_ReadOnly)) { + /// uhh yeah... Issues writing to the output file. + kdDebug() << "XmlTransformerProc::processOutput: Could not read file " << m_outFilename << endl; + m_state = fsFinished; + emit filteringFinished(); + } + QTextStream rstream(&readfile); + m_text = rstream.read(); + readfile.close(); + + kdDebug() << "XmlTransformerProc::processOutput: Read file at " + m_inFilename + " and created " + m_outFilename + " based on the stylesheet at " << m_xsltFilePath << endl; + + // Clean up. + QFile::remove(m_outFilename); + + m_state = fsFinished; + m_wasModified = true; + emit filteringFinished(); +} + +/** + * Waits for a previous call to asyncConvert to finish. + */ +/*virtual*/ void XmlTransformerProc::waitForFinished() +{ + if (m_xsltProc) + { + if (m_xsltProc->isRunning()) + { + if ( !m_xsltProc->wait( 15 ) ) + { + m_xsltProc->kill(); + kdDebug() << "XmlTransformerProc::waitForFinished: After waiting 15 seconds, xsltproc process seems to hung. Killing it." << endl; + processOutput(); + } + } + } +} + +/** + * Returns the state of the Filter. + */ +/*virtual*/ int XmlTransformerProc::getState() { return m_state; } + +/** + * Returns the filtered output. + */ +/*virtual*/ QString XmlTransformerProc::getOutput() { return m_text; } + +/** + * Acknowledges the finished filtering. + */ +/*virtual*/ void XmlTransformerProc::ackFinished() +{ + m_state = fsIdle; + m_text = QString::null; +} + +/** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ +/*virtual*/ void XmlTransformerProc::stopFiltering() +{ + m_state = fsStopping; + m_xsltProc->kill(); +} + +/** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ +/*virtual*/ bool XmlTransformerProc::wasModified() { return m_wasModified; } + +void XmlTransformerProc::slotProcessExited(KProcess*) +{ + // kdDebug() << "XmlTransformerProc::slotProcessExited: xsltproc has exited." << endl; + processOutput(); +} + +void XmlTransformerProc::slotReceivedStdout(KProcess*, char* /*buffer*/, int /*buflen*/) +{ + // QString buf = QString::fromLatin1(buffer, buflen); + // kdDebug() << "XmlTransformerProc::slotReceivedStdout: Received from xsltproc: " << buf << endl; +} + +void XmlTransformerProc::slotReceivedStderr(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "XmlTransformerProc::slotReceivedStderr: Received error from xsltproc: " << buf << endl; +} + diff --git a/kttsd/filters/xmltransformer/xmltransformerproc.h b/kttsd/filters/xmltransformer/xmltransformerproc.h new file mode 100644 index 0000000..7c88dd1 --- /dev/null +++ b/kttsd/filters/xmltransformer/xmltransformerproc.h @@ -0,0 +1,167 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generic XML Transformation Filter Processing class. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _XMLTRANSFORMERPROC_H_ +#define _XMLTRANSFORMERPROC_H_ + +// Qt includes. +#include <qobject.h> +#include <qstringlist.h> + +// KTTS includes. +#include "filterproc.h" + +class KProcess; + +class XmlTransformerProc : virtual public KttsFilterProc +{ + Q_OBJECT + +public: + /** + * Constructor. + */ + XmlTransformerProc( QObject *parent, const char *name, const QStringList &args = QStringList() ); + + /** + * Destructor. + */ + virtual ~XmlTransformerProc(); + + /** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ + virtual bool supportsAsync(); + + /** + * Convert input, returning output. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ + virtual bool asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Waits for a previous call to asyncConvert to finish. + */ + virtual void waitForFinished(); + + /** + * Returns the state of the Filter. + */ + virtual int getState(); + + /** + * Returns the filtered output. + */ + virtual QString getOutput(); + + /** + * Acknowledges the finished filtering. + */ + virtual void ackFinished(); + + /** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ + virtual void stopFiltering(); + + /** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ + virtual bool wasModified(); + +private slots: + void slotProcessExited(KProcess*); + void slotReceivedStdout(KProcess* proc, char* buffer, int buflen); + void slotReceivedStderr(KProcess* proc, char* buffer, int buflen); + +private: + // Process output when xsltproc exits. + void processOutput(); + + // If not empty, only apply to text queued by an applications containing one of these strings. + QStringList m_appIdList; + // If not empty, only apply to XML that has the specified root element. + QStringList m_rootElementList; + // If not empty, only apply to XML that has the specified DOCTYPE spec. + QStringList m_doctypeList; + // The text that is being filtered. + QString m_text; + // Processing state. + int m_state; + // xsltproc process. + KProcess* m_xsltProc; + // Input and Output filenames. + QString m_inFilename; + QString m_outFilename; + // User's name for the filter. + QString m_UserFilterName; + // XSLT file. + QString m_xsltFilePath; + // Path to xsltproc processor. + QString m_xsltprocPath; + // Did this filter modify the text? + bool m_wasModified; +}; + +#endif // _XMLTRANSFORMERPROC_H_ diff --git a/kttsd/icons/Makefile.am b/kttsd/icons/Makefile.am new file mode 100644 index 0000000..e5515a8 --- /dev/null +++ b/kttsd/icons/Makefile.am @@ -0,0 +1 @@ +KDE_ICON = AUTO diff --git a/kttsd/icons/hi16-action-female.png b/kttsd/icons/hi16-action-female.png Binary files differnew file mode 100644 index 0000000..702710f --- /dev/null +++ b/kttsd/icons/hi16-action-female.png diff --git a/kttsd/icons/hi16-action-male.png b/kttsd/icons/hi16-action-male.png Binary files differnew file mode 100644 index 0000000..59074a4 --- /dev/null +++ b/kttsd/icons/hi16-action-male.png diff --git a/kttsd/icons/hi16-action-nospeak.png b/kttsd/icons/hi16-action-nospeak.png Binary files differnew file mode 100644 index 0000000..313ad5a --- /dev/null +++ b/kttsd/icons/hi16-action-nospeak.png diff --git a/kttsd/icons/hi16-action-speak.png b/kttsd/icons/hi16-action-speak.png Binary files differnew file mode 100644 index 0000000..69078b9 --- /dev/null +++ b/kttsd/icons/hi16-action-speak.png diff --git a/kttsd/icons/hi22-action-nospeak.png b/kttsd/icons/hi22-action-nospeak.png Binary files differnew file mode 100644 index 0000000..39de8d3 --- /dev/null +++ b/kttsd/icons/hi22-action-nospeak.png diff --git a/kttsd/icons/hi22-action-speak.png b/kttsd/icons/hi22-action-speak.png Binary files differnew file mode 100644 index 0000000..1052d56 --- /dev/null +++ b/kttsd/icons/hi22-action-speak.png diff --git a/kttsd/icons/hi32-action-nospeak.png b/kttsd/icons/hi32-action-nospeak.png Binary files differnew file mode 100644 index 0000000..7882b63 --- /dev/null +++ b/kttsd/icons/hi32-action-nospeak.png diff --git a/kttsd/icons/hi32-action-speak.png b/kttsd/icons/hi32-action-speak.png Binary files differnew file mode 100644 index 0000000..617025f --- /dev/null +++ b/kttsd/icons/hi32-action-speak.png diff --git a/kttsd/icons/hi48-action-nospeak.png b/kttsd/icons/hi48-action-nospeak.png Binary files differnew file mode 100644 index 0000000..86d5220 --- /dev/null +++ b/kttsd/icons/hi48-action-nospeak.png diff --git a/kttsd/icons/hi48-action-speak.png b/kttsd/icons/hi48-action-speak.png Binary files differnew file mode 100644 index 0000000..26d22ce --- /dev/null +++ b/kttsd/icons/hi48-action-speak.png diff --git a/kttsd/icons/lo16-action-speak.png b/kttsd/icons/lo16-action-speak.png Binary files differnew file mode 100644 index 0000000..69078b9 --- /dev/null +++ b/kttsd/icons/lo16-action-speak.png diff --git a/kttsd/icons/lo22-action-speak.png b/kttsd/icons/lo22-action-speak.png Binary files differnew file mode 100644 index 0000000..1052d56 --- /dev/null +++ b/kttsd/icons/lo22-action-speak.png diff --git a/kttsd/icons/lo32-action-speak.png b/kttsd/icons/lo32-action-speak.png Binary files differnew file mode 100644 index 0000000..617025f --- /dev/null +++ b/kttsd/icons/lo32-action-speak.png diff --git a/kttsd/kcmkttsmgr/Makefile.am b/kttsd/kcmkttsmgr/Makefile.am new file mode 100644 index 0000000..094f59c --- /dev/null +++ b/kttsd/kcmkttsmgr/Makefile.am @@ -0,0 +1,51 @@ +# Include paths. +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(CFLAGS_GST) \ + $(KTTS_KSPEECH_INCLUDE) \ + $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# KCMODULE SECTION +######################################################################### +# This is the kcmodule that gets installed. It's name is used for all +# of the other Makefile.am variables. + +kde_module_LTLIBRARIES = kcm_kttsd.la + +kspeech_DIR = $(KTTS_KSPEECH_DIR) +kspeechsink_DIR = $(KTTS_KSPEECH_DIR) + +# The source, library search path, and link libraries. +kcm_kttsd_la_SOURCES = \ + kspeech.stub \ + kspeechsink.skel \ + kcmkttsmgr.cpp \ + kcmkttsmgrwidget.ui \ + addtalker.cpp \ + addtalkerwidget.ui \ + selectevent.cpp \ + selecteventwidget.ui + +kcm_kttsd_la_LDFLAGS = -avoid-version -module -no-undefined $(all_libraries) +kcm_kttsd_la_LIBADD = \ + $(top_builddir)/kttsd/libkttsd/libkttsd.la \ + $(LIB_KIO) + +# Header files that should not be installed. +noinst_HEADERS = \ + kcmkttsmgr.h \ + kcmkttsmgrwidget.h \ + kcmkttsmgrwidget.ui.h \ + addtalker.h \ + selectevent.h + +# K Menu entry. +xdg_apps_DATA = kcmkttsd.desktop + +# Install data files. +kcmkttsddatadir = $(kde_datadir)/kttsd/ +kcmkttsddata_DATA = kcmkttsd_testmessage.desktop diff --git a/kttsd/kcmkttsmgr/addtalker.cpp b/kttsd/kcmkttsmgr/addtalker.cpp new file mode 100644 index 0000000..2353ada --- /dev/null +++ b/kttsd/kcmkttsmgr/addtalker.cpp @@ -0,0 +1,225 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Dialog to allow user to add a new Talker by selecting a language and synthesizer + (button). Uses addtalkerwidget.ui. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qradiobutton.h> + +// KDE includes. +#include <kcombobox.h> +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> + +// KTTS includes. +#include "addtalker.h" + +AddTalker::AddTalker(SynthToLangMap synthToLangMap, QWidget* parent, const char* name, WFlags fl) + : AddTalkerWidget(parent,name,fl) +{ + // Build maps. + setSynthToLangMap(synthToLangMap); + + // Fill comboboxes. + applyFilter(); + + // Default to user's desktop language. + QString languageCode = KGlobal::locale()->defaultLanguage(); + // If there is not a synth that supports the locale, try stripping country code. + if (!m_langToSynthMap.contains(languageCode)) + { + QString countryCode; + QString charSet; + QString twoAlpha; + KGlobal::locale()->splitLocale(languageCode, twoAlpha, countryCode, charSet); + languageCode = twoAlpha; + } + // If there is still not a synth that supports the language code, default to "other". + if (!m_langToSynthMap.contains(languageCode)) languageCode = "other"; + + // Select the language in the language combobox. + QString language = languageCodeToLanguage(languageCode); + languageSelection->setCurrentItem(language, false); + + // Filter comboboxes. + applyFilter(); + + // Connect widgets to slots. + connect(languageRadioButton, SIGNAL(clicked()), this, SLOT(applyFilter())); + connect(synthesizerRadioButton, SIGNAL(clicked()), this, SLOT(applyFilter())); + connect(languageSelection, SIGNAL(activated(int)), this, SLOT(applyFilter())); + connect(synthesizerSelection, SIGNAL(activated(int)), this, SLOT(applyFilter())); +} + +AddTalker::~AddTalker() +{ +} + +/** +* Returns user's chosen language code. +*/ +QString AddTalker::getLanguageCode() +{ + return m_languageToLanguageCodeMap[languageSelection->currentText()]; +} + +/** +* Returns user's chosen synthesizer. +*/ +QString AddTalker::getSynthesizer() { return synthesizerSelection->currentText(); } + +// Set the synthesizer-to-languages map. +// @param synthToLang QMap of supported language codes indexed by synthesizer. +void AddTalker::setSynthToLangMap(SynthToLangMap synthToLangMap) +{ + m_synthToLangMap = synthToLangMap; + // "Invert" the map, i.e., map language codes to synthesizers. + QStringList synthList = m_synthToLangMap.keys(); + const int synthListCount = synthList.count(); + for (int synthNdx=0; synthNdx < synthListCount; ++synthNdx) + { + QString synth = synthList[synthNdx]; + QStringList languageCodeList = m_synthToLangMap[synth]; + const int languageCodeListCount = languageCodeList.count(); + for (int langNdx=0; langNdx < languageCodeListCount; ++langNdx) + { + QString languageCode = languageCodeList[langNdx]; + QStringList synthesizerList = m_langToSynthMap[languageCode]; + synthesizerList.append(synth); + m_langToSynthMap[languageCode] = synthesizerList; + } + } + // Fill language to language code map. + QStringList languageCodeList = m_langToSynthMap.keys(); + const int languageCodeListCount = languageCodeList.count(); + for (int ndx = 0; ndx < languageCodeListCount; ++ndx) + { + QString languageCode = languageCodeList[ndx]; + QString language = languageCodeToLanguage(languageCode); + m_languageToLanguageCodeMap[language] = languageCode; + } +} + +// Converts a language code plus optional country code to language description. +QString AddTalker::languageCodeToLanguage(const QString &languageCode) +{ + QString twoAlpha; + QString countryCode; + QString charSet; + QString language; + if (languageCode == "other") + language = i18n("Other"); + else + { + KGlobal::locale()->splitLocale(languageCode, twoAlpha, countryCode, charSet); + language = KGlobal::locale()->twoAlphaToLanguageName(twoAlpha); + } + if (!countryCode.isEmpty()) + language += " (" + KGlobal::locale()->twoAlphaToCountryName(countryCode) + ")"; + return language; +} + +// Based on user's radio button selection, filters choices for language or synthesizer +// comboboxes based on what is selected in the other combobox. +void AddTalker::applyFilter() +{ + if (languageRadioButton->isChecked()) + { + // Get current language. + QString language = languageSelection->currentText(); + // Fill language combobox will all possible languages. + languageSelection->clear(); + QStringList languageCodeList = m_langToSynthMap.keys(); + const int languageCodeListCount = languageCodeList.count(); + QStringList languageList; + for (int ndx=0; ndx < languageCodeListCount; ++ndx) + { + languageList.append(languageCodeToLanguage(languageCodeList[ndx])); + } + languageList.sort(); + for (int ndx=0; ndx < languageCodeListCount; ++ndx) + { + languageSelection->insertItem(languageList[ndx]); + } + // Re-select user's selection. + languageSelection->setCurrentItem(language, false); + // Get current language selection. + language = languageSelection->currentText(); + // Map current language to language code. + QString languageCode = m_languageToLanguageCodeMap[language]; + // Get list of synths that support this language code. + QStringList synthList = m_langToSynthMap[languageCode]; + // Get current user's synth selection. + QString synth = synthesizerSelection->currentText(); + // Fill synthesizer combobox. + synthesizerSelection->clear(); + synthList.sort(); + const int synthListCount = synthList.count(); + for (int ndx=0; ndx < synthListCount; ++ndx) + { + synthesizerSelection->insertItem(synthList[ndx]); + } + // Re-select user's selection. + synthesizerSelection->setCurrentItem(synth, false); + } + else + { + // Get current synth selection. + QString synth = synthesizerSelection->currentText(); + // Fill synthesizer combobox with all possible synths. + synthesizerSelection->clear(); + QStringList synthList = m_synthToLangMap.keys(); + synthList.sort(); + const int synthListCount = synthList.count(); + for (int ndx=0; ndx < synthListCount; ++ndx) + { + synthesizerSelection->insertItem(synthList[ndx]); + } + // Re-select user's synthesizer. + synthesizerSelection->setCurrentItem(synth, false); + // Get current synth selection. + synth = synthesizerSelection->currentText(); + // Get list of supported language codes. + QStringList languageCodeList = m_synthToLangMap[synth]; + // Get current user's language selection. + QString language = languageSelection->currentText(); + // Fill language combobox with language descriptions. + languageSelection->clear(); + const int languageCodeListCount = languageCodeList.count(); + QStringList languageList; + for (int ndx=0; ndx < languageCodeListCount; ++ndx) + { + languageList.append(languageCodeToLanguage(languageCodeList[ndx])); + } + languageList.sort(); + for (int ndx=0; ndx < languageCodeListCount; ++ndx) + { + languageSelection->insertItem(languageList[ndx]); + } + // Re-select user's language selection. + languageSelection->setCurrentItem(language, false); + } +} + +#include "addtalker.moc" + diff --git a/kttsd/kcmkttsmgr/addtalker.h b/kttsd/kcmkttsmgr/addtalker.h new file mode 100644 index 0000000..29bbfb5 --- /dev/null +++ b/kttsd/kcmkttsmgr/addtalker.h @@ -0,0 +1,89 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Dialog to allow user to add a new Talker by selecting a language and synthesizer + (button). Uses addtalkerwidget.ui. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef ADDTALKER_H +#define ADDTALKER_H + +// Qt includes. +#include <qmap.h> + +#include "addtalkerwidget.h" + +typedef QMap<QString,QStringList> SynthToLangMap; +typedef QMap<QString,QStringList> LangToSynthMap; + +class AddTalker : public AddTalkerWidget +{ + Q_OBJECT + +public: + /** + * Constructor. + * @param synthToLangMap QMap of supported language codes indexed by synthesizer. + * @param parent Inherited KDialog parameter. + * @param name Inherited KDialog parameter. + */ + AddTalker(SynthToLangMap synthToLangMap, QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); + + /** + * Destructor. + */ + ~AddTalker(); + + /** + * Returns user's chosen language code. + */ + QString getLanguageCode(); + + /** + * Returns user's chosen synthesizer. + */ + QString getSynthesizer(); + + +private: + /** + * Set the synthesizer-to-languages map. + * @param synthToLang QMap of supported language codes indexed by synthesizer. + */ + void setSynthToLangMap(SynthToLangMap synthToLangMap); + + // Converts a language code plus optional country code to language description. + QString languageCodeToLanguage(const QString &languageCode); + + // QMap of language descriptions to language codes. + QMap<QString,QString> m_languageToLanguageCodeMap; + // QMap of supported languages indexed by synthesizer. + SynthToLangMap m_synthToLangMap; + // QMap of synthesizers indexed by language code they support. + LangToSynthMap m_langToSynthMap; + +private slots: + // Based on user's radio button selection, filters choices for language or synthesizer + // comboboxes based on what is selected in the other combobox. + void applyFilter(); +}; + +#endif + diff --git a/kttsd/kcmkttsmgr/addtalkerwidget.ui b/kttsd/kcmkttsmgr/addtalkerwidget.ui new file mode 100644 index 0000000..f493138 --- /dev/null +++ b/kttsd/kcmkttsmgr/addtalkerwidget.ui @@ -0,0 +1,260 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>AddTalkerWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>AddTalkerWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>469</width> + <height>153</height> + </rect> + </property> + <property name="caption"> + <string>Add Talker</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup" row="0" column="0"> + <property name="name"> + <cstring>buttonGroup2</cstring> + </property> + <property name="title"> + <string></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KComboBox" row="2" column="1"> + <property name="name"> + <cstring>synthesizerSelection</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="duplicatesEnabled"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the speech synthesizer to do the speaking.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="KComboBox" row="1" column="1"> + <property name="name"> + <cstring>languageSelection</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="duplicatesEnabled"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the language to be spoken. Note that after you configure a Talker, your chosen Language may be overridden by the synthesizer, depending upon the options you choose.</string> + <comment>What's this text</comment> + </property> + </widget> + <spacer row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>spacer9</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>351</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>synthesizerLabel</cstring> + </property> + <property name="text"> + <string>&Synthesizer:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>synthesizerSelection</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the speech synthesizer to do the speaking.</string> + <comment>What's this text</comment> + </property> + </widget> + <spacer row="1" column="2"> + <property name="name"> + <cstring>spacer10</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + <spacer row="1" column="4"> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <spacer row="2" column="4"> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <spacer row="2" column="2"> + <property name="name"> + <cstring>spacer10_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="0" column="2" rowspan="1" colspan="3"> + <property name="name"> + <cstring>showAllLabel</cstring> + </property> + <property name="font"> + <font> + <underline>1</underline> + </font> + </property> + <property name="text"> + <string>Show All</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignLeft</set> + </property> + <property name="whatsThis" stdset="0"> + <string>The radio buttons below determine which box shows all possibilities. The box to the left of the checked button shows all possibilities. The box to the left of the unchecked box only shows those possibilities that match the other box.</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>languageLabel</cstring> + </property> + <property name="text"> + <string>&Language:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>languageSelection</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the language to be spoken. Note that after you configure a Talker, your chosen Language may be overridden by the synthesizer, depending upon the options you choose.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QRadioButton" row="1" column="3"> + <property name="name"> + <cstring>languageRadioButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Check to list all the possible languages in the Language box at the left. When a language has been chosen, the Synthesizer box will show only those synthesizers that can speak in the chosen language.</string> + </property> + </widget> + <widget class="QRadioButton" row="2" column="3"> + <property name="name"> + <cstring>synthesizerRadioButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Check this box to display all the available synthesizers in the Synthesizer box to the left. When a synthesizer is chosen, only the languages that can be spoken by that synthesizer appear in the Language box.</string> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>languageSelection</tabstop> + <tabstop>synthesizerSelection</tabstop> + <tabstop>languageRadioButton</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> +</includehints> +</UI> diff --git a/kttsd/kcmkttsmgr/kcmkttsd.desktop b/kttsd/kcmkttsmgr/kcmkttsd.desktop new file mode 100644 index 0000000..b54e66e --- /dev/null +++ b/kttsd/kcmkttsmgr/kcmkttsd.desktop @@ -0,0 +1,142 @@ +[Desktop Entry] +Type=Application +Name=Text-to-Speech +Name[bg]=Синтез на глас +Name[bs]=Izgovaranje teksta +Name[ca]=Text a veu +Name[cs]=Hlasová syntéza +Name[cy]=Testun-i-Leferydd +Name[da]=Tekst-til-tale +Name[de]=Sprachausgabe +Name[el]=Κείμενο-σε-ομιλία +Name[es]=Texto a voz +Name[et]=Tekst kõneks +Name[eu]=Testutik hizketara +Name[fa]=متن به گفتار +Name[fi]=Teksti puheeksi +Name[fr]=Synthèse vocale +Name[ga]=Téacs-go-Caint +Name[gl]=Texto-para-Fala +Name[he]=טקסט לדיבור +Name[hu]=Szövegfelolvasó +Name[is]=Texti-í-tal +Name[it]=Pronuncia +Name[ja]=テキスト読み上げ +Name[ka]=ტექსტი-გახმოვანება +Name[km]=អត្ថបទដែលត្រូវនិយាយ +Name[mk]=Текст-во-говор +Name[ms]=Teks-ke-Tutur +Name[nb]=Tekst-til-tale +Name[nds]=Vörleser +Name[ne]=पाठ वाचक +Name[nl]=Tekst-tot-spraak +Name[pa]=ਪਾਠ ਤੋਂ ਬੋਲੀ +Name[pl]=Odczytywanie tekstu +Name[pt]=Texto-para-Fala +Name[pt_BR]=Conversão de texto para fala +Name[ru]=Синтез речи +Name[sk]=Text-na-reč +Name[sl]=Besedilo v govor +Name[sr]=Текст-у-говор +Name[sr@Latn]=Tekst-u-govor +Name[sv]=Text-till-tal +Name[ta]=உரையில் இருந்து பேச்சுக்கு +Name[tg]=Таҳлили овоз +Name[tr]=Metinden Konuşmaya +Name[uk]=Синтез мовлення з тексту +Name[vi]=Văn bản sang Tiếng nói +Name[zh_TW]=文字轉語音 +Comment=Text-to-Speech Control Module +Comment[bg]=Модул за синтез на глас +Comment[bs]=Kontrolni modul za izgovaranje teksta +Comment[ca]=Mòdul de control de text a veu +Comment[cs]=Modul ovládání hlasové syntézy +Comment[da]=Kontrolmodul for tekst-til-tale +Comment[de]=Kontrollmodul für die Sprachausgabe +Comment[el]=Άρθρωμα ελέγχου κειμένου-σε-ομιλία +Comment[es]=Modulo de control del sintetizador texto a voz +Comment[et]=Juhtimiskeskuse teksti kõneks muutmise moodul +Comment[eu]=Testutik hizketarako modulua +Comment[fa]=پیمانۀ کنترل متن به گفتار +Comment[fi]=Teksti puheeksi -hallintamoduuli +Comment[fr]=Module de configuration de la synthèse vocale +Comment[ga]=Modúl Rialaithe Téacs-go-Caint +Comment[gl]=Módulo de Control do Texto-para-Fala +Comment[he]=מודול מרכז בקרה עבור טקסט לדיבור +Comment[hu]=A szövegfelolvasó szolgáltatás beállításai +Comment[is]=Texti-í-tal stjórneining +Comment[it]=Modulo di controllo di pronuncia +Comment[ja]=テキスト読み上げ制御モジュール +Comment[ka]=ტექსტი-სიტყვის მართვის მოდული +Comment[km]=ម៉ូឌុលត្រួតពិនិត្យអត្ថបទដែលត្រូវនិយាយ +Comment[mk]=Контролен модул за текст-во-говор +Comment[ms]=Modul Kawalan Teks-ke-Tutur +Comment[nb]=Styremodul for tekst-til-tale +Comment[nds]=Kuntrullmoduul för den Vörleser +Comment[ne]=पाठ वाचक नियन्त्रण मोड्युल +Comment[nl]=Tekst-tot-spraak configuratiemodule +Comment[pa]=ਪਾਠ ਤੋਂ ਬੋਲੀ ਕੰਟਰੋਲ ਮੈਡੀਊਲ +Comment[pl]=Moduł konfiguracji odczytywania tekstu +Comment[pt]=Módulo de Controlo de Texto-para-Fala +Comment[pt_BR]=Módulo de Controle de conversão de texto para fala +Comment[ru]=Настройка синтеза речи +Comment[sk]=Riadiaci modul text-na-reč +Comment[sl]=Nadzorni modul besedila v govor +Comment[sr]=Контролни модул за текст-у-говор +Comment[sr@Latn]=Kontrolni modul za tekst-u-govor +Comment[sv]=Text-till-tal inställningsmodul +Comment[ta]=உரையில் இருந்து பேச்சு கட்டுப்பாட்டு பகுதி +Comment[tg]=Танзими таҳлили овоз +Comment[tr]=Metinden Konuşmaya Kontrol Modülü +Comment[uk]=Модуль керування синтезом мовлення з тексту +Comment[vi]=Mô đun Điều khiển Văn bản sang Tiếng nói +Comment[zh_TW]=文字轉語音控制模組 +ServiceTypes=Application,KCModule +DocPath=kttsd +NoDisplay=false +StartupNotify=false +StartupWMClass= +Exec=kcmshell kcmkttsd +Icon=kttsd +Categories=Qt;KDE;Utility;Accessibility; +Keywords=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice +Keywords[bg]=синтез, глас, говор, текст, kttsd, kcmkttsmgr, kttsmgr, tts, ttsd, ktts, text, to, speech, speak, synthesizer, synth, festival, command, freetts, proklam, via, voice +Keywords[br]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,skrid,da,prezegenn,komz,synthesizer,synth,festival,urzhiad,freetts,proklam,via,mouezh +Keywords[bs]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,čitanje,izgovaranje +Keywords[ca]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,a,veu,parla,sintetitzador,síntesi,festival,ordre,freetts,proklam,via,veu +Keywords[cs]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,mluvit,syntéza,syntetizér,synth,festival,příkaz,freetts,proklam,hlasová,předčítání,čtení +Keywords[da]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,tekst,til,tale,tal,synthesizer,synth,festival,kommando,freetts,proklam,via,stemme +Keywords[de]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,Sprachsynthese,Vorlesen,Sprache,Sprachausgabe +Keywords[el]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,κείμενο,σε,ομιλία,εκφώνηση,synthesizer,synth,festival,εντολή,freetts,proklam,μέσω,φωνή +Keywords[es]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,texto,a,voz,habla,sintetizador,festival,orden,freetts,proklam,via,voz +Keywords[et]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,tekst,kõneks,kõne,süntesaator,festival,käsk,freetts,proklam,hääl +Keywords[eu]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,testua, hizketa,ahoskatu,sintetizadorea,synth,festival,command, freetts,proklam,via,ahotsa +Keywords[fa]=kttsd،kcmkttsmgr،kttsmgr،tts،ttsd،ktts،متن، به، گفتار، صحبت کردن دهنده، synth، Festival، فرمان، freetts،proklam، از طریق، صدا +Keywords[fr]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,texte,to,prononcer,énoncer,synthétiseur,synth,festival,command,freetts,proklam,via,voix +Keywords[ga]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,téacs,go,caint,abair,sintéiseoir,sintéis,festival,ordú,freetts,proklam,via,guth,glór +Keywords[gl]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,texto,para,fala,falar,sintetizador,sintetizar,festival,comando,freetts,proklam,via,voz +Keywords[he]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,דיבור,טקסט,מקריא, קול,פסטיבל,מסנטז,סינטי,סינטיזייזר +Keywords[hu]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,szöveg,hang,beszéd,beszél,szintetizátor,szintetizálás,festival,parancs,freetts,proklam,via,voice +Keywords[is]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,texti,í,tal,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice +Keywords[it]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,pronuncia,parlare,sintetizzatore,synth,festival,comando,freetts,proklam,via,voce +Keywords[mk]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,текст,во,говор,синтисајзер,фестивал,наредба,команда,глас +Keywords[nb]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,tekst,til,tale,snakke,synthesizer,synth,festival,kommando,freetts,proklam,via,voice +Keywords[nds]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,Vörleser,vörlesen,Blicksnuut,synth,festival,freetts,proklam,Stimm,Spraakutgaav +Keywords[nl]=kttsd,tts,ttsd,ktts,text to speech,praten,synthesizer,synth,festival,command,freetts,proklam,via voice,spraakprogramma,geluid,stem,tekst tot spraak,spreken,computerstem +Keywords[pl]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,tekst,do,mowa,mówienie, synteza,festival,polecenie,głos,mówienie,odczytywanie tekstu +Keywords[pt]=kttsd,kcmkttsmgr,kttsmgr,tts,TTS,ttsd,ktts,texto,fala,falar,sintetizador,festival,comandar,freetts,proklam,via,voice +Keywords[pt_BR]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,texto,fala,conversa, sintentizador,ynth,festival,command,freetts,proklam,via,voz +Keywords[ru]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text-to-speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,голос,речь,синтез,синтез речи +Keywords[sl]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,besedilo,v,govor,govori,sintetizator,,festival,ukaz,freetts,proklam,preko,glas +Keywords[sr]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,текст,говор,наредба +Keywords[sr@Latn]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,tekst,govor,naredba +Keywords[sv]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,till,tal,läsa,syntes,synt,festival,kommando,freetts,proklam,via,röst +Keywords[tg]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,овоз,ибора,таҳлили овоз +Keywords[uk]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,text,to,speech,speak,synthesizer,synth,festival,command,freetts,proklam,via,voice,мовлення,синтез,голос,текст,команда,через +Keywords[vi]=kttsd,kcmkttsmgr,kttsmgr,tts,ttsd,ktts,văn bản,sang,tiếng nói,nói,tổng hợp,tạp,festival,lệnh,freets,proklam,qua,giọng nói +X-KDE-FactoryName=kttsd +X-KDE-ParentApp=kcontrol +X-KDE-SubstituteUID=false +X-KDE-ModuleType=Library +X-KDE-Library=kttsd +Terminal=false diff --git a/kttsd/kcmkttsmgr/kcmkttsd_testmessage.desktop b/kttsd/kcmkttsmgr/kcmkttsd_testmessage.desktop new file mode 100644 index 0000000..816bfd7 --- /dev/null +++ b/kttsd/kcmkttsmgr/kcmkttsd_testmessage.desktop @@ -0,0 +1,46 @@ +[Desktop Entry] +NoDisplay=true +Name=The text-to-speech system seems to be functioning properly. +Name[bg]=Системата за синтез на глас изглежда, че функционира нормално. +Name[ca]=El sistema de text a veu sembla que funciona correctament. +Name[cs]=Zdá se, že systém hlasové syntézy funguje správně. +Name[da]=Tekst-til-tale systemet synes at virke rigtigt. +Name[de]=Die Sprachausgabe scheint ordnungsgemäß zu funktionieren. +Name[el]=Το σύστημα κειμένου-σε-ομιλία φαίνεται να λειτουργεί κανονικά. +Name[es]=El sintetizador de texto a voz parece estar funcionando correctamente. +Name[et]=Teksti kõneks muutmise süsteem paistab korralikult töötavat. +Name[eu]=Testutik-hizketarako sistemak ongi funtzionatzen duela dirudi. +Name[fa]=به نظر میرسد که سیستم متن به گفتار درست عمل میکند. +Name[fi]=Teksti puheeksi -järjestelmä näyttää toimivan hyvin. +Name[fr]=Le système de synthèse vocale semble fonctionner correctement. +Name[ga]=Dealraíonn sé go bhfuil an córas téacs-go-caint ag obair mar is ceart. +Name[gl]=O sistema de texto-para-fala parece estar a funcionar correctamente. +Name[he]=נראה כי מערכת הטקסט לדיבור עובדת כהלכה. +Name[hu]=A szövegfelolvasó rendszer megfelelően működik. +Name[is]=Texti-í-tal kerfið virðist virka eðlilega. +Name[it]=Il sistema di pronuncia pare funzionare correttamente. +Name[ja]=テキスト読み上げシステムは正しく動作しているようです。 +Name[ka]=ტექსტი-გახმოვანება სისტემა როგორც ჩანს სწორად მოქმედებს. +Name[km]=ប្រព័ន្ធអត្ថបទដែលត្រូវនិយាយហាក់ដូចជាមានមុខងារត្រឹមត្រូវ ។ +Name[mk]=Изгледа дека системот за текст-во-говор функционира како што треба. +Name[ms]=Sistem teks-ke-Tutur berfungsi dengan baik. +Name[nb]=Tekst-til-tale-systemet ser ut til å virke som det skal. +Name[nds]=As dat lett funkscheneert de Vörleser as he schall. +Name[ne]=पाठ वाचक प्रणालीले ठीक रुपमा कार्य गर्ने देखिन्छ +Name[nl]=Het tekst-tot-spraaksysteem blijkt goed te functioneren. +Name[pa]=ਇੱਕ ਪਾਠ ਤੋਂ ਬੋਲੀ ਸੰਸਲੇਸ਼ਣ ਸਿਸਟਮ, ਜੋ ਕਿ ਠੀਕ ਤਰਾਂ ਕੰਮ ਕਰਦਾ ਜਾਪਦਾ ਹੈ। +Name[pl]=System odczytywania tekstu funkcjonuje prawidłowo. +Name[pt]=O sistema de texto-para-voz parece estar a funcionar correctamente. +Name[pt_BR]=O sistema de fala do KDE parece estar funcionando corretamente. +Name[ru]=Система синтеза речи работает нормально. +Name[sk]=Systém text-na-reč funguje správne. +Name[sl]=Sistem besedila v govor očitno deluje pravilno. +Name[sr]=Систем текст-у-говор изгледа да функционише исправно. +Name[sr@Latn]=Sistem tekst-u-govor izgleda da funkcioniše ispravno. +Name[sv]=Text till tal-systemet verkar fungera som det ska. +Name[ta]=உரையில் இருந்து பேச்சு அமைப்பு சரியாக இயங்குகிறது. +Name[tg]=Системаи таҳлили овоз мӯътадил кор мекунад. +Name[tr]=Metinden Konuşmaya sistemi düzgün çalışıyor. +Name[uk]=Система синтезу мовлення з тексту функціонує правильно. +Name[vi]=Hệ thống văn bản sang tiếng nói có vẻ hoạt động tốt. +Name[zh_TW]=文字轉語音的系統已正常運作。 diff --git a/kttsd/kcmkttsmgr/kcmkttsmgr.cpp b/kttsd/kcmkttsmgr/kcmkttsmgr.cpp new file mode 100644 index 0000000..d890446 --- /dev/null +++ b/kttsd/kcmkttsmgr/kcmkttsmgr.cpp @@ -0,0 +1,2713 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KControl module for KTTSD configuration and Job Management + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + Copyright : (C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +// Note to programmers. There is a subtle difference between a plugIn name and a +// synthesizer name. The latter is a translated name, for example, "Festival Interactivo", +// while the former is alway an English name, example "Festival Interactive". + +// C++ includes. +#include <math.h> + +// Qt includes. +#include <qwidget.h> +#include <qtabwidget.h> +#include <qcheckbox.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qradiobutton.h> +#include <qslider.h> +#include <qlabel.h> +#include <qpopupmenu.h> +#include <qbuttongroup.h> + +// KDE includes. +#include <dcopclient.h> +#include <klistview.h> +#include <kparts/componentfactory.h> +#include <klineedit.h> +#include <kurlrequester.h> +#include <kiconloader.h> +#include <kapplication.h> +#include <kgenericfactory.h> +#include <kstandarddirs.h> +#include <kaboutdata.h> +#include <kconfig.h> +#include <knuminput.h> +#include <kcombobox.h> +#include <kinputdialog.h> +#include <kmessagebox.h> +#include <kfiledialog.h> + +// KTTS includes. +#include "talkercode.h" +#include "pluginconf.h" +#include "filterconf.h" +#include "testplayer.h" +#include "player.h" +#include "selecttalkerdlg.h" +#include "selectevent.h" +#include "notify.h" +#include "utils.h" + +// KCMKttsMgr includes. +#include "kcmkttsmgr.h" +#include "kcmkttsmgr.moc" + +// Some constants. +// Defaults set when clicking Defaults button. +const bool embedInSysTrayCheckBoxValue = true; +const bool showMainWindowOnStartupCheckBoxValue = true; + +const bool autostartMgrCheckBoxValue = true; +const bool autoexitMgrCheckBoxValue = true; + +const bool notifyEnableCheckBoxValue = false; +const bool notifyExcludeEventsWithSoundCheckBoxValue = true; + +const bool textPreMsgCheckValue = true; +const QString textPreMsgValue = i18n("Text interrupted. Message."); + +const bool textPreSndCheckValue = false; +const QString textPreSndValue = ""; + +const bool textPostMsgCheckValue = true; +const QString textPostMsgValue = i18n("Resuming text."); + +const bool textPostSndCheckValue = false; +const QString textPostSndValue = ""; + +const int timeBoxValue = 100; + +const bool keepAudioCheckBoxValue = false; + +// Make this a plug in. +typedef KGenericFactory<KCMKttsMgr, QWidget> KCMKttsMgrFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kttsd, KCMKttsMgrFactory("kttsd") ) + +/** +* Constructor. +* Makes the list of plug ins. +* And the languages acording to the plug ins. +*/ +KCMKttsMgr::KCMKttsMgr(QWidget *parent, const char *name, const QStringList &) : + DCOPStub("kttsd", "KSpeech"), + DCOPObject("kcmkttsmgr_kspeechsink"), + KCModule(KCMKttsMgrFactory::instance(), parent, name) +{ + // kdDebug() << "KCMKttsMgr contructor running." << endl; + + // Initialize some variables. + m_config = 0; + m_jobMgrPart = 0; + m_configDlg = 0; + m_changed = false; + m_suppressConfigChanged = false; + + // Add the KTTS Manager widget + QGridLayout *layout = new QGridLayout(this, 0, 0); + m_kttsmgrw = new KCMKttsMgrWidget(this, "kttsmgrw"); + // m_kttsmgrw = new KCMKttsMgrWidget(this); + layout->addWidget(m_kttsmgrw, 0, 0); + + // Give buttons icons. + // Talkers tab. + m_kttsmgrw->higherTalkerPriorityButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("up", KIcon::Small)); + m_kttsmgrw->lowerTalkerPriorityButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("down", KIcon::Small)); + m_kttsmgrw->removeTalkerButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("edittrash", KIcon::Small)); + m_kttsmgrw->configureTalkerButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("configure", KIcon::Small)); + + // Filters tab. + m_kttsmgrw->higherFilterPriorityButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("up", KIcon::Small)); + m_kttsmgrw->lowerFilterPriorityButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("down", KIcon::Small)); + m_kttsmgrw->removeFilterButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("edittrash", KIcon::Small)); + m_kttsmgrw->configureFilterButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("configure", KIcon::Small)); + + // Notify tab. + m_kttsmgrw->notifyActionComboBox->clear(); + for (int ndx = 0; ndx < NotifyAction::count(); ++ndx) + m_kttsmgrw->notifyActionComboBox->insertItem( NotifyAction::actionDisplayName( ndx ) ); + m_kttsmgrw->notifyPresentComboBox->clear(); + for (int ndx = 0; ndx < NotifyPresent::count(); ++ndx) + m_kttsmgrw->notifyPresentComboBox->insertItem( NotifyPresent::presentDisplayName( ndx ) ); + + m_kttsmgrw->notifyRemoveButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("edittrash", KIcon::Small)); + m_kttsmgrw->notifyTestButton->setIconSet( + KGlobal::iconLoader()->loadIconSet("speak", KIcon::Small)); + + m_kttsmgrw->sinkComboBox->setEditable(false); + m_kttsmgrw->pcmComboBox->setEditable(false); + + // Construct a popup menu for the Sentence Boundary Detector buttons on Filter tab. + m_sbdPopmenu = new QPopupMenu( m_kttsmgrw, "SbdPopupMenu" ); + m_sbdPopmenu->insertItem( i18n("&Edit..."), this, SLOT(slot_configureSbdFilter()), 0, sbdBtnEdit ); + m_sbdPopmenu->insertItem( KGlobal::iconLoader()->loadIconSet("up", KIcon::Small), + i18n("U&p"), this, SLOT(slot_higherSbdFilterPriority()), 0, sbdBtnUp ); + m_sbdPopmenu->insertItem( KGlobal::iconLoader()->loadIconSet("down", KIcon::Small), + i18n("Do&wn"), this, SLOT(slot_lowerSbdFilterPriority()), 0, sbdBtnDown ); + m_sbdPopmenu->insertItem( i18n("&Add..."), this, SLOT(slot_addSbdFilter()), 0, sbdBtnAdd ); + m_sbdPopmenu->insertItem( i18n("&Remove"), this, SLOT(slot_removeSbdFilter()), 0, sbdBtnRemove ); + m_kttsmgrw->sbdButton->setPopup( m_sbdPopmenu ); + + // If aRts is available, enable its radio button. + // Determine if available by loading its plugin. If it fails, not available. + TestPlayer* testPlayer = new TestPlayer(); + Player* player = testPlayer->createPlayerObject(0); + if (player) + m_kttsmgrw->artsRadioButton->setEnabled(true); + else + m_kttsmgrw->artsRadioButton->setEnabled(false); + delete player; + delete testPlayer; + + // If GStreamer is available, enable its radio button. + // Determine if available by loading its plugin. If it fails, not available. + testPlayer = new TestPlayer(); + player = testPlayer->createPlayerObject(1); + if (player) + { + m_kttsmgrw->gstreamerRadioButton->setEnabled(true); + m_kttsmgrw->sinkLabel->setEnabled(true); + m_kttsmgrw->sinkComboBox->setEnabled(true); + QStringList sinkList = player->getPluginList("Sink/Audio"); + // kdDebug() << "KCMKttsMgr::KCMKttsMgr: GStreamer Sink List = " << sinkList << endl; + m_kttsmgrw->sinkComboBox->clear(); + m_kttsmgrw->sinkComboBox->insertStringList(sinkList); + } + delete player; + delete testPlayer; + + // If ALSA is available, enable its radio button. + // Determine if available by loading its plugin. If it fails, not available. + testPlayer = new TestPlayer(); + player = testPlayer->createPlayerObject(2); + if (player) + { + m_kttsmgrw->alsaRadioButton->setEnabled(true); + m_kttsmgrw->pcmLabel->setEnabled(true); + m_kttsmgrw->pcmComboBox->setEnabled(true); + QStringList pcmList = player->getPluginList(""); + pcmList.append("custom"); + kdDebug() << "KCMKttsMgr::KCMKttsMgr: ALSA pcmList = " << pcmList << endl; + m_kttsmgrw->pcmComboBox->clear(); + m_kttsmgrw->pcmComboBox->insertStringList(pcmList); + } + delete player; + delete testPlayer; + + // If aKode is available, enable its radio button. + // Determine if available by loading its plugin. If it fails, not available. + testPlayer = new TestPlayer(); + player = testPlayer->createPlayerObject(3); + if (player) + { + m_kttsmgrw->akodeRadioButton->setEnabled(true); + m_kttsmgrw->akodeSinkLabel->setEnabled(true); + m_kttsmgrw->akodeComboBox->setEnabled(true); + QStringList pcmList = player->getPluginList(""); + kdDebug() << "KCMKttsMgr::KCMKttsMgr: aKode Sink List = " << pcmList << endl; + m_kttsmgrw->akodeComboBox->clear(); + m_kttsmgrw->akodeComboBox->insertStringList(pcmList); + } + delete player; + delete testPlayer; + + // Set up Keep Audio Path KURLRequestor. + m_kttsmgrw->keepAudioPath->setMode(KFile::Directory); + m_kttsmgrw->keepAudioPath->setURL(locateLocal("data", "kttsd/audio/")); + + // Object for the KTTSD configuration. + m_config = new KConfig("kttsdrc"); + + // Load configuration. + load(); + + // Connect the signals from the KCMKtssMgrWidget to this class. + + // Talker tab. + connect(m_kttsmgrw->addTalkerButton, SIGNAL(clicked()), + this, SLOT(slot_addTalker())); + connect(m_kttsmgrw->higherTalkerPriorityButton, SIGNAL(clicked()), + this, SLOT(slot_higherTalkerPriority())); + connect(m_kttsmgrw->lowerTalkerPriorityButton, SIGNAL(clicked()), + this, SLOT(slot_lowerTalkerPriority())); + connect(m_kttsmgrw->removeTalkerButton, SIGNAL(clicked()), + this, SLOT(slot_removeTalker())); + connect(m_kttsmgrw->configureTalkerButton, SIGNAL(clicked()), + this, SLOT(slot_configureTalker())); + connect(m_kttsmgrw->talkersList, SIGNAL(selectionChanged()), + this, SLOT(updateTalkerButtons())); + + // Filter tab. + connect(m_kttsmgrw->addFilterButton, SIGNAL(clicked()), + this, SLOT(slot_addNormalFilter())); + connect(m_kttsmgrw->higherFilterPriorityButton, SIGNAL(clicked()), + this, SLOT(slot_higherNormalFilterPriority())); + connect(m_kttsmgrw->lowerFilterPriorityButton, SIGNAL(clicked()), + this, SLOT(slot_lowerNormalFilterPriority())); + connect(m_kttsmgrw->removeFilterButton, SIGNAL(clicked()), + this, SLOT(slot_removeNormalFilter())); + connect(m_kttsmgrw->configureFilterButton, SIGNAL(clicked()), + this, SLOT(slot_configureNormalFilter())); + connect(m_kttsmgrw->filtersList, SIGNAL(selectionChanged()), + this, SLOT(updateFilterButtons())); + //connect(m_kttsmgrw->filtersList, SIGNAL(stateChanged()), + // this, SLOT(configChanged())); + connect(m_kttsmgrw->sbdsList, SIGNAL(selectionChanged()), + this, SLOT(updateSbdButtons())); + + // Audio tab. + connect(m_kttsmgrw->gstreamerRadioButton, SIGNAL(toggled(bool)), + this, SLOT(slotGstreamerRadioButton_toggled(bool))); + connect(m_kttsmgrw->alsaRadioButton, SIGNAL(toggled(bool)), + this, SLOT(slotAlsaRadioButton_toggled(bool))); + connect(m_kttsmgrw->pcmComboBox, SIGNAL(activated(int)), + this, SLOT(slotPcmComboBox_activated())); + connect(m_kttsmgrw->akodeRadioButton, SIGNAL(toggled(bool)), + this, SLOT(slotAkodeRadioButton_toggled(bool))); + connect(m_kttsmgrw->timeBox, SIGNAL(valueChanged(int)), + this, SLOT(timeBox_valueChanged(int))); + connect(m_kttsmgrw->timeSlider, SIGNAL(valueChanged(int)), + this, SLOT(timeSlider_valueChanged(int))); + connect(m_kttsmgrw->timeBox, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_kttsmgrw->timeSlider, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_kttsmgrw->keepAudioCheckBox, SIGNAL(toggled(bool)), + this, SLOT(keepAudioCheckBox_toggled(bool))); + connect(m_kttsmgrw->keepAudioPath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + + // General tab. + connect(m_kttsmgrw->enableKttsdCheckBox, SIGNAL(toggled(bool)), + SLOT(enableKttsdToggled(bool))); + + // Notify tab. + connect(m_kttsmgrw->notifyEnableCheckBox, SIGNAL(toggled(bool)), + this, SLOT(slotNotifyEnableCheckBox_toggled(bool))); + connect(m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_kttsmgrw->notifyAddButton, SIGNAL(clicked()), + this, SLOT(slotNotifyAddButton_clicked())); + connect(m_kttsmgrw->notifyRemoveButton, SIGNAL(clicked()), + this, SLOT(slotNotifyRemoveButton_clicked())); + connect(m_kttsmgrw->notifyClearButton, SIGNAL(clicked()), + this, SLOT(slotNotifyClearButton_clicked())); + connect(m_kttsmgrw->notifyLoadButton, SIGNAL(clicked()), + this, SLOT(slotNotifyLoadButton_clicked())); + connect(m_kttsmgrw->notifySaveButton, SIGNAL(clicked()), + this, SLOT(slotNotifySaveButton_clicked())); + connect(m_kttsmgrw->notifyListView, SIGNAL(selectionChanged()), + this, SLOT(slotNotifyListView_selectionChanged())); + connect(m_kttsmgrw->notifyPresentComboBox, SIGNAL(activated(int)), + this, SLOT(slotNotifyPresentComboBox_activated(int))); + connect(m_kttsmgrw->notifyActionComboBox, SIGNAL(activated(int)), + this, SLOT(slotNotifyActionComboBox_activated(int))); + connect(m_kttsmgrw->notifyTestButton, SIGNAL(clicked()), + this, SLOT(slotNotifyTestButton_clicked())); + connect(m_kttsmgrw->notifyMsgLineEdit, SIGNAL(textChanged(const QString&)), + this, SLOT(slotNotifyMsgLineEdit_textChanged(const QString&))); + connect(m_kttsmgrw->notifyTalkerButton, SIGNAL(clicked()), + this, SLOT(slotNotifyTalkerButton_clicked())); + + // Others. + connect(m_kttsmgrw, SIGNAL( configChanged() ), + this, SLOT( configChanged() ) ); + connect(m_kttsmgrw->mainTab, SIGNAL(currentChanged(QWidget*)), + this, SLOT(slotTabChanged())); + + // Connect KTTSD DCOP signals to our slots. + if (!connectDCOPSignal("kttsd", "KSpeech", + "kttsdStarted()", + "kttsdStarted()", + false)) kdDebug() << "connectDCOPSignal failed" << endl; + connectDCOPSignal("kttsd", "KSpeech", + "kttsdExiting()", + "kttsdExiting()", + false); + + // See if KTTSD is already running, and if so, create jobs tab. + if (kapp->dcopClient()->isApplicationRegistered("kttsd")) + kttsdStarted(); + else + // Start KTTSD if check box is checked. + enableKttsdToggled(m_kttsmgrw->enableKttsdCheckBox->isChecked()); + + // Switch to Talkers tab if none configured, + // otherwise switch to Jobs tab if it is active. + if (m_kttsmgrw->talkersList->childCount() == 0) + m_kttsmgrw->mainTab->setCurrentPage(wpTalkers); + else if (m_kttsmgrw->enableKttsdCheckBox->isChecked()) + m_kttsmgrw->mainTab->setCurrentPage(wpJobs); +} + +/** +* Destructor. +*/ +KCMKttsMgr::~KCMKttsMgr(){ + // kdDebug() << "KCMKttsMgr::~KCMKttsMgr: Running" << endl; + delete m_config; +} + +/** +* 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 KCMKttsMgr::load() +{ + // kdDebug() << "KCMKttsMgr::load: Running" << endl; + + m_changed = false; + // Don't emit changed() signal while loading. + m_suppressConfigChanged = true; + + // Set the group general for the configuration of kttsd itself (no plug ins) + m_config->setGroup("General"); + + // Load the configuration of the text interruption messages and sound + m_kttsmgrw->textPreMsgCheck->setChecked(m_config->readBoolEntry("TextPreMsgEnabled", textPreMsgCheckValue)); + m_kttsmgrw->textPreMsg->setText(m_config->readEntry("TextPreMsg", textPreMsgValue)); + m_kttsmgrw->textPreMsg->setEnabled(m_kttsmgrw->textPreMsgCheck->isChecked()); + + m_kttsmgrw->textPreSndCheck->setChecked(m_config->readBoolEntry("TextPreSndEnabled", textPreSndCheckValue)); + m_kttsmgrw->textPreSnd->setURL(m_config->readEntry("TextPreSnd", textPreSndValue)); + m_kttsmgrw->textPreSnd->setEnabled(m_kttsmgrw->textPreSndCheck->isChecked()); + + m_kttsmgrw->textPostMsgCheck->setChecked(m_config->readBoolEntry("TextPostMsgEnabled", textPostMsgCheckValue)); + m_kttsmgrw->textPostMsg->setText(m_config->readEntry("TextPostMsg", textPostMsgValue)); + m_kttsmgrw->textPostMsg->setEnabled(m_kttsmgrw->textPostMsgCheck->isChecked()); + + m_kttsmgrw->textPostSndCheck->setChecked(m_config->readBoolEntry("TextPostSndEnabled", textPostSndCheckValue)); + m_kttsmgrw->textPostSnd->setURL(m_config->readEntry("TextPostSnd", textPostSndValue)); + m_kttsmgrw->textPostSnd->setEnabled(m_kttsmgrw->textPostSndCheck->isChecked()); + + // Overall settings. + m_kttsmgrw->embedInSysTrayCheckBox->setChecked(m_config->readBoolEntry("EmbedInSysTray", + m_kttsmgrw->embedInSysTrayCheckBox->isChecked())); + m_kttsmgrw->showMainWindowOnStartupCheckBox->setChecked(m_config->readBoolEntry( + "ShowMainWindowOnStartup", m_kttsmgrw->showMainWindowOnStartupCheckBox->isChecked())); + m_kttsmgrw->showMainWindowOnStartupCheckBox->setEnabled( + m_kttsmgrw->embedInSysTrayCheckBox->isChecked()); + + m_kttsmgrw->enableKttsdCheckBox->setChecked(m_config->readBoolEntry("EnableKttsd", + m_kttsmgrw->enableKttsdCheckBox->isChecked())); + + m_kttsmgrw->autostartMgrCheckBox->setChecked(m_config->readBoolEntry("AutoStartManager", true)); + m_kttsmgrw->autoexitMgrCheckBox->setChecked(m_config->readBoolEntry("AutoExitManager", true)); + + // Notification settings. + m_kttsmgrw->notifyEnableCheckBox->setChecked(m_config->readBoolEntry("Notify", + m_kttsmgrw->notifyEnableCheckBox->isChecked())); + m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox->setChecked( + m_config->readBoolEntry("ExcludeEventsWithSound", + m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox->isChecked())); + slotNotifyClearButton_clicked(); + loadNotifyEventsFromFile( locateLocal("config", "kttsd_notifyevents.xml"), true ); + slotNotifyEnableCheckBox_toggled( m_kttsmgrw->notifyEnableCheckBox->isChecked() ); + // Auto-expand and position on the Default item. + QListViewItem* item = m_kttsmgrw->notifyListView->findItem( "default", nlvcEventSrc ); + if ( item ) + if ( item->childCount() > 0 ) item = item->firstChild(); + if ( item ) m_kttsmgrw->notifyListView->ensureItemVisible( item ); + + // Audio Output. + int audioOutputMethod = 0; + if (m_kttsmgrw->gstreamerRadioButton->isChecked()) audioOutputMethod = 1; + if (m_kttsmgrw->alsaRadioButton->isChecked()) audioOutputMethod = 2; + if (m_kttsmgrw->akodeRadioButton->isChecked()) audioOutputMethod = 3; + audioOutputMethod = m_config->readNumEntry("AudioOutputMethod", audioOutputMethod); + switch (audioOutputMethod) + { + case 0: + m_kttsmgrw->artsRadioButton->setChecked(true); + break; + case 1: + m_kttsmgrw->gstreamerRadioButton->setChecked(true); + break; + case 2: + m_kttsmgrw->alsaRadioButton->setChecked(true); + break; + case 3: + m_kttsmgrw->akodeRadioButton->setChecked(true); + break; + } + m_kttsmgrw->timeBox->setValue(m_config->readNumEntry("AudioStretchFactor", timeBoxValue)); + timeBox_valueChanged(m_kttsmgrw->timeBox->value()); + m_kttsmgrw->keepAudioCheckBox->setChecked( + m_config->readBoolEntry("KeepAudio", m_kttsmgrw->keepAudioCheckBox->isChecked())); + m_kttsmgrw->keepAudioPath->setURL( + m_config->readEntry("KeepAudioPath", + m_kttsmgrw->keepAudioPath->url())); + m_kttsmgrw->keepAudioPath->setEnabled(m_kttsmgrw->keepAudioCheckBox->isChecked()); + + // Last plugin ID. Used to generate a new ID for an added talker. + m_lastTalkerID = 0; + + // Last filter ID. Used to generate a new ID for an added filter. + m_lastFilterID = 0; + + // Dictionary mapping languages to language codes. + m_languagesToCodes.clear(); + + // Load existing Talkers into the listview. + m_kttsmgrw->talkersList->clear(); + m_kttsmgrw->talkersList->setSortColumn(-1); + QStringList talkerIDsList = m_config->readListEntry("TalkerIDs", ','); + if (!talkerIDsList.isEmpty()) + { + QListViewItem* talkerItem = 0; + QStringList::ConstIterator itEnd = talkerIDsList.constEnd(); + for (QStringList::ConstIterator it = talkerIDsList.constBegin(); it != itEnd; ++it) + { + QString talkerID = *it; + // kdDebug() << "KCMKttsMgr::load: talkerID = " << talkerID << endl; + m_config->setGroup(QString("Talker_") + talkerID); + QString talkerCode = m_config->readEntry("TalkerCode"); + QString fullLanguageCode; + talkerCode = TalkerCode::normalizeTalkerCode(talkerCode, fullLanguageCode); + QString language = TalkerCode::languageCodeToLanguage(fullLanguageCode); + QString desktopEntryName = m_config->readEntry("DesktopEntryName", QString::null); + // If a DesktopEntryName is not in the config file, it was configured before + // we started using them, when we stored translated plugin names instead. + // Try to convert the translated plugin name to a DesktopEntryName. + // DesktopEntryNames are better because user can change their desktop language + // and DesktopEntryName won't change. + QString synthName; + if (desktopEntryName.isEmpty()) + { + synthName = m_config->readEntry("PlugIn", QString::null); + // See if the translated name will untranslate. If not, well, sorry. + desktopEntryName = TalkerCode::TalkerNameToDesktopEntryName(synthName); + // Record the DesktopEntryName from now on. + if (!desktopEntryName.isEmpty()) m_config->writeEntry("DesktopEntryName", desktopEntryName); + } + synthName = TalkerCode::TalkerDesktopEntryNameToName(desktopEntryName); + if (!synthName.isEmpty()) + { + // kdDebug() << "KCMKttsMgr::load: talkerCode = " << talkerCode << endl; + if (talkerItem) + talkerItem = new KListViewItem(m_kttsmgrw->talkersList, talkerItem, + talkerID, language, synthName); + else + talkerItem = new KListViewItem(m_kttsmgrw->talkersList, + talkerID, language, synthName); + updateTalkerItem(talkerItem, talkerCode); + m_languagesToCodes[language] = fullLanguageCode; + if (talkerID.toInt() > m_lastTalkerID) m_lastTalkerID = talkerID.toInt(); + } + } + } + + // Query for all the KCMKTTSD SynthPlugins and store the list in offers. + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin"); + + // Iterate thru the possible plug ins getting their language support codes. + for(unsigned int i=0; i < offers.count() ; ++i) + { + QString synthName = offers[i]->name(); + QStringList languageCodes = offers[i]->property("X-KDE-Languages").toStringList(); + // Add language codes to the language-to-language code map. + QStringList::ConstIterator endLanguages(languageCodes.constEnd()); + for( QStringList::ConstIterator it = languageCodes.constBegin(); it != endLanguages; ++it ) + { + QString language = TalkerCode::languageCodeToLanguage(*it); + m_languagesToCodes[language] = *it; + } + + // All plugins support "Other". + // TODO: Eventually, this should not be necessary, since all plugins will know + // the languages they support and report them in call to getSupportedLanguages(). + if (!languageCodes.contains("other")) languageCodes.append("other"); + + // Add supported language codes to synthesizer-to-language map. + m_synthToLangMap[synthName] = languageCodes; + } + + // Add "Other" language. + m_languagesToCodes[i18n("Other")] = "other"; + + // Load Filters. + QListViewItem* filterItem = 0; + m_kttsmgrw->filtersList->clear(); + m_kttsmgrw->sbdsList->clear(); + m_kttsmgrw->filtersList->setSortColumn(-1); + m_kttsmgrw->sbdsList->setSortColumn(-1); + m_config->setGroup("General"); + QStringList filterIDsList = m_config->readListEntry("FilterIDs", ','); + // kdDebug() << "KCMKttsMgr::load: FilterIDs = " << filterIDsList << endl; + if (!filterIDsList.isEmpty()) + { + QStringList::ConstIterator itEnd = filterIDsList.constEnd(); + for (QStringList::ConstIterator it = filterIDsList.constBegin(); it != itEnd; ++it) + { + QString filterID = *it; + // kdDebug() << "KCMKttsMgr::load: filterID = " << filterID << endl; + m_config->setGroup("Filter_" + filterID); + QString desktopEntryName = m_config->readEntry("DesktopEntryName", QString::null); + // If a DesktopEntryName is not in the config file, it was configured before + // we started using them, when we stored translated plugin names instead. + // Try to convert the translated plugin name to a DesktopEntryName. + // DesktopEntryNames are better because user can change their desktop language + // and DesktopEntryName won't change. + QString filterPlugInName; + if (desktopEntryName.isEmpty()) + { + filterPlugInName = m_config->readEntry("PlugInName", QString::null); + // See if the translated name will untranslate. If not, well, sorry. + desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); + // Record the DesktopEntryName from now on. + if (!desktopEntryName.isEmpty()) m_config->writeEntry("DesktopEntryName", desktopEntryName); + } + filterPlugInName = FilterDesktopEntryNameToName(desktopEntryName); + if (!filterPlugInName.isEmpty()) + { + QString userFilterName = m_config->readEntry("UserFilterName", filterPlugInName); + bool multiInstance = m_config->readBoolEntry("MultiInstance", false); + // Determine if this filter is a Sentence Boundary Detector (SBD). + bool isSbd = m_config->readBoolEntry("IsSBD", false); + bool checked = m_config->readBoolEntry("Enabled", false); + if (isSbd) + { + filterItem = m_kttsmgrw->sbdsList->lastChild(); + if (!filterItem) + filterItem = new KListViewItem(m_kttsmgrw->sbdsList, + userFilterName, filterID, filterPlugInName); + else + filterItem = new KListViewItem(m_kttsmgrw->sbdsList, filterItem, + userFilterName, filterID, filterPlugInName); + } else { + filterItem = m_kttsmgrw->filtersList->lastChild(); + if (!filterItem) + filterItem = new KttsCheckListItem(m_kttsmgrw->filtersList, + userFilterName, QCheckListItem::CheckBox, this); + else + filterItem = new KttsCheckListItem(m_kttsmgrw->filtersList, filterItem, + userFilterName, QCheckListItem::CheckBox, this); + dynamic_cast<QCheckListItem*>(filterItem)->setOn(checked); + } + filterItem->setText(flvcFilterID, filterID); + filterItem->setText(flvcPlugInName, filterPlugInName); + if (multiInstance) + filterItem->setText(flvcMultiInstance, "T"); + else + filterItem->setText(flvcMultiInstance, "F"); + if (filterID.toInt() > m_lastFilterID) m_lastFilterID = filterID.toInt(); + } + } + } + + // Add at least one unchecked instance of each available filter plugin if there is + // not already at least one instance and the filter can autoconfig itself. + offers = KTrader::self()->query("KTTSD/FilterPlugin"); + for (unsigned int i=0; i < offers.count() ; ++i) + { + QString filterPlugInName = offers[i]->name(); + QString desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); + if (!desktopEntryName.isEmpty() && (countFilterPlugins(filterPlugInName) == 0)) + { + // Must load plugin to determine if it supports multiple instances + // and to see if it can autoconfigure itself. + KttsFilterConf* filterPlugIn = loadFilterPlugin(desktopEntryName); + if (filterPlugIn) + { + ++m_lastFilterID; + QString filterID = QString::number(m_lastFilterID); + QString groupName = "Filter_" + filterID; + filterPlugIn->load(m_config, groupName); + QString userFilterName = filterPlugIn->userPlugInName(); + if (!userFilterName.isEmpty()) + { + kdDebug() << "KCMKttsMgr::load: auto-configuring filter " << userFilterName << endl; + // Determine if plugin is an SBD filter. + bool multiInstance = filterPlugIn->supportsMultiInstance(); + bool isSbd = filterPlugIn->isSBD(); + if (isSbd) + { + filterItem = m_kttsmgrw->sbdsList->lastChild(); + if (!filterItem) + filterItem = new KListViewItem(m_kttsmgrw->sbdsList, + userFilterName, filterID, filterPlugInName); + else + filterItem = new KListViewItem(m_kttsmgrw->sbdsList, filterItem, + userFilterName, filterID, filterPlugInName); + } else { + filterItem = m_kttsmgrw->filtersList->lastChild(); + if (!filterItem) + filterItem = new KttsCheckListItem(m_kttsmgrw->filtersList, + userFilterName, QCheckListItem::CheckBox, this); + else + filterItem = new KttsCheckListItem(m_kttsmgrw->filtersList, filterItem, + userFilterName, QCheckListItem::CheckBox, this); + dynamic_cast<QCheckListItem*>(filterItem)->setOn(false); + } + filterItem->setText(flvcFilterID, filterID); + filterItem->setText(flvcPlugInName, filterPlugInName); + if (multiInstance) + filterItem->setText(flvcMultiInstance, "T"); + else + filterItem->setText(flvcMultiInstance, "F"); + m_config->setGroup(groupName); + filterPlugIn->save(m_config, groupName); + m_config->setGroup(groupName); + m_config->writeEntry("DesktopEntryName", desktopEntryName); + m_config->writeEntry("UserFilterName", userFilterName); + m_config->writeEntry("Enabled", isSbd); + m_config->writeEntry("MultiInstance", multiInstance); + m_config->writeEntry("IsSBD", isSbd); + filterIDsList.append(filterID); + } else m_lastFilterID--; + } else + kdDebug() << "KCMKttsMgr::load: Unable to load filter plugin " << filterPlugInName + << " DesktopEntryName " << desktopEntryName << endl; + delete filterPlugIn; + } + } + // Rewrite list of FilterIDs in case we added any. + QString filterIDs = filterIDsList.join(","); + m_config->setGroup("General"); + m_config->writeEntry("FilterIDs", filterIDs); + m_config->sync(); + + // Uncheck and disable KTTSD checkbox if no Talkers are configured. + if (m_kttsmgrw->talkersList->childCount() == 0) + { + m_kttsmgrw->enableKttsdCheckBox->setChecked(false); + m_kttsmgrw->enableKttsdCheckBox->setEnabled(false); + enableKttsdToggled(false); + } + + // Enable ShowMainWindowOnStartup checkbox based on EmbedInSysTray checkbox. + m_kttsmgrw->showMainWindowOnStartupCheckBox->setEnabled( + m_kttsmgrw->embedInSysTrayCheckBox->isChecked()); + + // GStreamer settings. + m_config->setGroup("GStreamerPlayer"); + KttsUtils::setCbItemFromText(m_kttsmgrw->sinkComboBox, m_config->readEntry("SinkName", "osssink")); + + // ALSA settings. + m_config->setGroup("ALSAPlayer"); + KttsUtils::setCbItemFromText(m_kttsmgrw->pcmComboBox, m_config->readEntry("PcmName", "default")); + m_kttsmgrw->pcmCustom->setText(m_config->readEntry("CustomPcmName", "")); + + // aKode settings. + m_config->setGroup("aKodePlayer"); + KttsUtils::setCbItemFromText(m_kttsmgrw->akodeComboBox, m_config->readEntry("SinkName", "auto")); + + // Update controls based on new states. + slotNotifyListView_selectionChanged(); + updateTalkerButtons(); + updateFilterButtons(); + updateSbdButtons(); + slotGstreamerRadioButton_toggled(m_kttsmgrw->gstreamerRadioButton->isChecked()); + slotAlsaRadioButton_toggled(m_kttsmgrw->alsaRadioButton->isChecked()); + slotAkodeRadioButton_toggled(m_kttsmgrw->akodeRadioButton->isChecked()); + + m_changed = false; + m_suppressConfigChanged = false; +} + +/** +* 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 KCMKttsMgr::save() +{ + // kdDebug() << "KCMKttsMgr::save: Running" << endl; + m_changed = false; + + // Clean up config. + m_config->deleteGroup("General"); + + // Set the group general for the configuration of kttsd itself (no plug ins) + m_config->setGroup("General"); + + // Set text interrumption messages and paths + m_config->writeEntry("TextPreMsgEnabled", m_kttsmgrw->textPreMsgCheck->isChecked()); + m_config->writeEntry("TextPreMsg", m_kttsmgrw->textPreMsg->text()); + + m_config->writeEntry("TextPreSndEnabled", m_kttsmgrw->textPreSndCheck->isChecked()); + m_config->writeEntry("TextPreSnd", PlugInConf::realFilePath(m_kttsmgrw->textPreSnd->url())); + + m_config->writeEntry("TextPostMsgEnabled", m_kttsmgrw->textPostMsgCheck->isChecked()); + m_config->writeEntry("TextPostMsg", m_kttsmgrw->textPostMsg->text()); + + m_config->writeEntry("TextPostSndEnabled", m_kttsmgrw->textPostSndCheck->isChecked()); + m_config->writeEntry("TextPostSnd", PlugInConf::realFilePath(m_kttsmgrw->textPostSnd->url())); + + // Overall settings. + m_config->writeEntry("EmbedInSysTray", m_kttsmgrw->embedInSysTrayCheckBox->isChecked()); + m_config->writeEntry("ShowMainWindowOnStartup", + m_kttsmgrw->showMainWindowOnStartupCheckBox->isChecked()); + m_config->writeEntry("AutoStartManager", m_kttsmgrw->autostartMgrCheckBox->isChecked()); + m_config->writeEntry("AutoExitManager", m_kttsmgrw->autoexitMgrCheckBox->isChecked()); + + // Uncheck and disable KTTSD checkbox if no Talkers are configured. + // Enable checkbox if at least one Talker is configured. + bool enableKttsdWasToggled = false; + if (m_kttsmgrw->talkersList->childCount() == 0) + { + enableKttsdWasToggled = m_kttsmgrw->enableKttsdCheckBox->isChecked(); + m_kttsmgrw->enableKttsdCheckBox->setChecked(false); + m_kttsmgrw->enableKttsdCheckBox->setEnabled(false); + // Might as well zero LastTalkerID as well. + m_lastTalkerID = 0; + } + else + m_kttsmgrw->enableKttsdCheckBox->setEnabled(true); + + m_config->writeEntry("EnableKttsd", m_kttsmgrw->enableKttsdCheckBox->isChecked()); + + // Notification settings. + m_config->writeEntry("Notify", m_kttsmgrw->notifyEnableCheckBox->isChecked()); + m_config->writeEntry("ExcludeEventsWithSound", + m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox->isChecked()); + saveNotifyEventsToFile( locateLocal("config", "kttsd_notifyevents.xml") ); + + // Audio Output. + int audioOutputMethod = 0; + if (m_kttsmgrw->gstreamerRadioButton->isChecked()) audioOutputMethod = 1; + if (m_kttsmgrw->alsaRadioButton->isChecked()) audioOutputMethod = 2; + if (m_kttsmgrw->akodeRadioButton->isChecked()) audioOutputMethod = 3; + m_config->writeEntry("AudioOutputMethod", audioOutputMethod); + m_config->writeEntry("AudioStretchFactor", m_kttsmgrw->timeBox->value()); + m_config->writeEntry("KeepAudio", m_kttsmgrw->keepAudioCheckBox->isChecked()); + m_config->writeEntry("KeepAudioPath", m_kttsmgrw->keepAudioPath->url()); + + // Get ordered list of all talker IDs. + QStringList talkerIDsList; + QListViewItem* talkerItem = m_kttsmgrw->talkersList->firstChild(); + while (talkerItem) + { + QListViewItem* nextTalkerItem = talkerItem->itemBelow(); + QString talkerID = talkerItem->text(tlvcTalkerID); + talkerIDsList.append(talkerID); + talkerItem = nextTalkerItem; + } + QString talkerIDs = talkerIDsList.join(","); + m_config->writeEntry("TalkerIDs", talkerIDs); + + // Erase obsolete Talker_nn sections. + QStringList groupList = m_config->groupList(); + int groupListCount = groupList.count(); + for (int groupNdx = 0; groupNdx < groupListCount; ++groupNdx) + { + QString groupName = groupList[groupNdx]; + if (groupName.left(7) == "Talker_") + { + QString groupTalkerID = groupName.mid(7); + if (!talkerIDsList.contains(groupTalkerID)) m_config->deleteGroup(groupName); + } + } + + // Get ordered list of all filter IDs. Record enabled state of each filter. + QStringList filterIDsList; + QListViewItem* filterItem = m_kttsmgrw->filtersList->firstChild(); + while (filterItem) + { + QListViewItem* nextFilterItem = filterItem->itemBelow(); + QString filterID = filterItem->text(flvcFilterID); + filterIDsList.append(filterID); + bool checked = dynamic_cast<QCheckListItem*>(filterItem)->isOn(); + m_config->setGroup("Filter_" + filterID); + m_config->writeEntry("Enabled", checked); + m_config->writeEntry("IsSBD", false); + filterItem = nextFilterItem; + } + QListViewItem* sbdItem = m_kttsmgrw->sbdsList->firstChild(); + while (sbdItem) + { + QListViewItem* nextSbdItem = sbdItem->itemBelow(); + QString filterID = sbdItem->text(slvcFilterID); + filterIDsList.append(filterID); + m_config->setGroup("Filter_" + filterID); + m_config->writeEntry("Enabled", true); + m_config->writeEntry("IsSBD", true); + sbdItem = nextSbdItem; + } + QString filterIDs = filterIDsList.join(","); + m_config->setGroup("General"); + m_config->writeEntry("FilterIDs", filterIDs); + + // Erase obsolete Filter_nn sections. + for (int groupNdx = 0; groupNdx < groupListCount; ++groupNdx) + { + QString groupName = groupList[groupNdx]; + if (groupName.left(7) == "Filter_") + { + QString groupFilterID = groupName.mid(7); + if (!filterIDsList.contains(groupFilterID)) m_config->deleteGroup(groupName); + } + } + + // GStreamer settings. + m_config->setGroup("GStreamerPlayer"); + m_config->writeEntry("SinkName", m_kttsmgrw->sinkComboBox->currentText()); + + // ALSA settings. + m_config->setGroup("ALSAPlayer"); + m_config->writeEntry("PcmName", m_kttsmgrw->pcmComboBox->currentText()); + m_config->writeEntry("CustomPcmName", m_kttsmgrw->pcmCustom->text()); + + // aKode settings. + m_config->setGroup("aKodePlayer"); + m_config->writeEntry("SinkName", m_kttsmgrw->akodeComboBox->currentText()); + + m_config->sync(); + + // If we automatically unchecked the Enable KTTSD checkbox, stop KTTSD. + if (enableKttsdWasToggled) + enableKttsdToggled(false); + else + { + // If KTTSD is running, reinitialize it. + DCOPClient *client = kapp->dcopClient(); + bool kttsdRunning = (client->isApplicationRegistered("kttsd")); + if (kttsdRunning) + { + kdDebug() << "Restarting KTTSD" << endl; + QByteArray data; + client->send("kttsd", "KSpeech", "reinit()", data); + } + } +} + +void KCMKttsMgr::slotTabChanged() +{ + setButtons(buttons()); + int currentPageIndex = m_kttsmgrw->mainTab->currentPageIndex(); + if (currentPageIndex == wpJobs) + { + if (m_changed) + { + KMessageBox::information(m_kttsmgrw, + i18n("You have made changes to the configuration but have not saved them yet. " + "Click Apply to save the changes or Cancel to abandon the changes.")); + } + } +} + +/** +* 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 KCMKttsMgr::defaults() { + // kdDebug() << "Running: KCMKttsMgr::defaults: Running"<< endl; + + int currentPageIndex = m_kttsmgrw->mainTab->currentPageIndex(); + bool changed = false; + switch (currentPageIndex) + { + case wpGeneral: + if (m_kttsmgrw->embedInSysTrayCheckBox->isChecked() != embedInSysTrayCheckBoxValue) + { + changed = true; + m_kttsmgrw->embedInSysTrayCheckBox->setChecked(embedInSysTrayCheckBoxValue); + } + if (m_kttsmgrw->showMainWindowOnStartupCheckBox->isChecked() != + showMainWindowOnStartupCheckBoxValue) + { + changed = true; + m_kttsmgrw->showMainWindowOnStartupCheckBox->setChecked( + showMainWindowOnStartupCheckBoxValue); + } + if (m_kttsmgrw->autostartMgrCheckBox->isChecked() != autostartMgrCheckBoxValue) + { + changed = true; + m_kttsmgrw->autostartMgrCheckBox->setChecked( + autostartMgrCheckBoxValue); + } + if (m_kttsmgrw->autoexitMgrCheckBox->isChecked() != autoexitMgrCheckBoxValue) + { + changed = true; + m_kttsmgrw->autoexitMgrCheckBox->setChecked( + autoexitMgrCheckBoxValue); + } + break; + + case wpNotify: + if (m_kttsmgrw->notifyEnableCheckBox->isChecked() != notifyEnableCheckBoxValue) + { + changed = true; + m_kttsmgrw->notifyEnableCheckBox->setChecked(notifyEnableCheckBoxValue); + m_kttsmgrw->notifyGroup->setChecked( notifyEnableCheckBoxValue ); + } + if (m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox->isChecked() != + notifyExcludeEventsWithSoundCheckBoxValue ) + { + changed = true; + m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox->setChecked( + notifyExcludeEventsWithSoundCheckBoxValue ); + } + break; + + case wpInterruption: + if (m_kttsmgrw->textPreMsgCheck->isChecked() != textPreMsgCheckValue) + { + changed = true; + m_kttsmgrw->textPreMsgCheck->setChecked(textPreMsgCheckValue); + } + if (m_kttsmgrw->textPreMsg->text() != i18n(textPreMsgValue.utf8())) + { + changed = true; + m_kttsmgrw->textPreMsg->setText(i18n(textPreMsgValue.utf8())); + } + if (m_kttsmgrw->textPreSndCheck->isChecked() != textPreSndCheckValue) + { + changed = true; + m_kttsmgrw->textPreSndCheck->setChecked(textPreSndCheckValue); + } + if (m_kttsmgrw->textPreSnd->url() != textPreSndValue) + { + changed = true; + m_kttsmgrw->textPreSnd->setURL(textPreSndValue); + } + if (m_kttsmgrw->textPostMsgCheck->isChecked() != textPostMsgCheckValue) + { + changed = true; + m_kttsmgrw->textPostMsgCheck->setChecked(textPostMsgCheckValue); + } + if (m_kttsmgrw->textPostMsg->text() != i18n(textPostMsgValue.utf8())) + { + changed = true; + m_kttsmgrw->textPostMsg->setText(i18n(textPostMsgValue.utf8())); + } + if (m_kttsmgrw->textPostSndCheck->isChecked() != textPostSndCheckValue) + { + changed = true; + m_kttsmgrw->textPostSndCheck->setChecked(textPostSndCheckValue); + } + if (m_kttsmgrw->textPostSnd->url() != textPostSndValue) + { + changed = true; + m_kttsmgrw->textPostSnd->setURL(textPostSndValue); + } + break; + + case wpAudio: + if (!m_kttsmgrw->artsRadioButton->isChecked()) + { + changed = true; + m_kttsmgrw->artsRadioButton->setChecked(true); + } + if (m_kttsmgrw->timeBox->value() != timeBoxValue) + { + changed = true; + m_kttsmgrw->timeBox->setValue(timeBoxValue); + } + if (m_kttsmgrw->keepAudioCheckBox->isChecked() != + keepAudioCheckBoxValue) + { + changed = true; + m_kttsmgrw->keepAudioCheckBox->setChecked(keepAudioCheckBoxValue); + } + if (m_kttsmgrw->keepAudioPath->url() != locateLocal("data", "kttsd/audio/")) + { + changed = true; + m_kttsmgrw->keepAudioPath->setURL(locateLocal("data", "kttsd/audio/")); + } + m_kttsmgrw->keepAudioPath->setEnabled(m_kttsmgrw->keepAudioCheckBox->isEnabled()); + } + if (changed) configChanged(); +} + +/** +* This is a static method which gets called to realize the modules settings +* during the startup of KDE. NOTE that most modules do not implement this +* method, but modules like the keyboard and mouse modules, which directly +* interact with the X-server, need this method. As this method is static, +* it can avoid to create an instance of the user interface, which is often +* not needed in this case. +*/ +void KCMKttsMgr::init(){ + // kdDebug() << "KCMKttsMgr::init: Running" << endl; +} + +/** +* The control center calls this function to decide which buttons should +* be displayed. For example, it does not make sense to display an "Apply" +* button for one of the information modules. The value returned can be set by +* modules using setButtons. +*/ +int KCMKttsMgr::buttons() { + // kdDebug() << "KCMKttsMgr::buttons: Running"<< endl; + return KCModule::Ok|KCModule::Apply|KCModule::Help|KCModule::Default; +} + +/** +* This function returns the small quickhelp. +* That is displayed in the sidebar in the KControl +*/ +QString KCMKttsMgr::quickHelp() const{ + // kdDebug() << "KCMKttsMgr::quickHelp: Running"<< endl; + return i18n( + "<h1>Text-to-Speech</h1>" + "<p>This is the configuration for the text-to-speech dcop service</p>" + "<p>This allows other applications to access text-to-speech resources</p>" + "<p>Be sure to configure a default language for the language you are using as this will be the language used by most of the applications</p>"); +} + +const KAboutData* KCMKttsMgr::aboutData() const{ + KAboutData *about = + new KAboutData(I18N_NOOP("kttsd"), I18N_NOOP("KCMKttsMgr"), + 0, 0, KAboutData::License_GPL, + I18N_NOOP("(c) 2002, José Pablo Ezequiel Fernández")); + + about->addAuthor("José Pablo Ezequiel Fernández", I18N_NOOP("Author") , "pupeno@kde.org"); + about->addAuthor("Gary Cramblitt", I18N_NOOP("Maintainer") , "garycramblitt@comcast.net"); + about->addAuthor("Olaf Schmidt", I18N_NOOP("Contributor"), "ojschmidt@kde.org"); + about->addAuthor("Paul Giannaros", I18N_NOOP("Contributor"), "ceruleanblaze@gmail.com"); + + return about; +} + +/** +* Loads the configuration plug in for a named talker plug in and type. +* @param name DesktopEntryName of the Synthesizer. +* @return Pointer to the configuration plugin for the Talker. +*/ +PlugInConf* KCMKttsMgr::loadTalkerPlugin(const QString& name) +{ + // kdDebug() << "KCMKttsMgr::loadPlugin: Running"<< endl; + + // Find the plugin. + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin", + QString("DesktopEntryName == '%1'").arg(name)); + + if (offers.count() == 1) + { + // When the entry is found, load the plug in + // First create a factory for the library + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if(factory){ + // If the factory is created successfully, instantiate the PlugInConf class for the + // specific plug in to get the plug in configuration object. + PlugInConf *plugIn = KParts::ComponentFactory::createInstanceFromLibrary<PlugInConf>( + offers[0]->library().latin1(), NULL, offers[0]->library().latin1()); + if(plugIn){ + // If everything went ok, return the plug in pointer. + return plugIn; + } else { + // Something went wrong, returning null. + kdDebug() << "KCMKttsMgr::loadTalkerPlugin: Unable to instantiate PlugInConf class for plugin " << name << endl; + return NULL; + } + } else { + // Something went wrong, returning null. + kdDebug() << "KCMKttsMgr::loadTalkerPlugin: Unable to create Factory object for plugin " + << name << endl; + return NULL; + } + } + // The plug in was not found (unexpected behaviour, returns null). + kdDebug() << "KCMKttsMgr::loadTalkerPlugin: KTrader did not return an offer for plugin " + << name << endl; + return NULL; +} + +/** + * Loads the configuration plug in for a named filter plug in. + * @param plugInName DesktopEntryName of the plugin. + * @return Pointer to the configuration plugin for the Filter. + */ +KttsFilterConf* KCMKttsMgr::loadFilterPlugin(const QString& plugInName) +{ + // kdDebug() << "KCMKttsMgr::loadPlugin: Running"<< endl; + + // Find the plugin. + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin", + QString("DesktopEntryName == '%1'").arg(plugInName)); + + if (offers.count() == 1) + { + // When the entry is found, load the plug in + // First create a factory for the library + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if(factory){ + // If the factory is created successfully, instantiate the KttsFilterConf class for the + // specific plug in to get the plug in configuration object. + int errorNo = 0; + KttsFilterConf *plugIn = + KParts::ComponentFactory::createInstanceFromLibrary<KttsFilterConf>( + offers[0]->library().latin1(), NULL, offers[0]->library().latin1(), + QStringList(), &errorNo); + if(plugIn){ + // If everything went ok, return the plug in pointer. + return plugIn; + } else { + // Something went wrong, returning null. + kdDebug() << "KCMKttsMgr::loadFilterPlugin: Unable to instantiate KttsFilterConf class for plugin " << plugInName << " error: " << errorNo << endl; + return NULL; + } + } else { + // Something went wrong, returning null. + kdDebug() << "KCMKttsMgr::loadFilterPlugin: Unable to create Factory object for plugin " << plugInName << endl; + return NULL; + } + } + // The plug in was not found (unexpected behaviour, returns null). + kdDebug() << "KCMKttsMgr::loadFilterPlugin: KTrader did not return an offer for plugin " << plugInName << endl; + return NULL; +} + +/** +* Given an item in the talker listview and a talker code, sets the columns of the item. +* @param talkerItem QListViewItem. +* @param talkerCode Talker Code. +*/ +void KCMKttsMgr::updateTalkerItem(QListViewItem* talkerItem, const QString &talkerCode) +{ + TalkerCode parsedTalkerCode(talkerCode); + QString fullLanguageCode = parsedTalkerCode.fullLanguageCode(); + if (!fullLanguageCode.isEmpty()) + { + QString language = TalkerCode::languageCodeToLanguage(fullLanguageCode); + if (!language.isEmpty()) + { + m_languagesToCodes[language] = fullLanguageCode; + talkerItem->setText(tlvcLanguage, language); + } + } + // Don't update the Synthesizer name with plugInName. The former is a translated + // name; the latter an English name. + // if (!plugInName.isEmpty()) talkerItem->setText(tlvcSynthName, plugInName); + if (!parsedTalkerCode.voice().isEmpty()) + talkerItem->setText(tlvcVoice, parsedTalkerCode.voice()); + if (!parsedTalkerCode.gender().isEmpty()) + talkerItem->setText(tlvcGender, TalkerCode::translatedGender(parsedTalkerCode.gender())); + if (!parsedTalkerCode.volume().isEmpty()) + talkerItem->setText(tlvcVolume, TalkerCode::translatedVolume(parsedTalkerCode.volume())); + if (!parsedTalkerCode.rate().isEmpty()) + talkerItem->setText(tlvcRate, TalkerCode::translatedRate(parsedTalkerCode.rate())); +} + +/** + * Add a talker. + */ +void KCMKttsMgr::slot_addTalker() +{ + AddTalker* addTalkerWidget = new AddTalker(m_synthToLangMap, this, "AddTalker_widget"); + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Add Talker"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_kttsmgrw, + "AddTalker_dlg", + true, + true); + dlg->setMainWidget(addTalkerWidget); + dlg->setHelp("select-plugin", "kttsd"); + int dlgResult = dlg->exec(); + QString languageCode = addTalkerWidget->getLanguageCode(); + QString synthName = addTalkerWidget->getSynthesizer(); + delete dlg; + // TODO: Also delete addTalkerWidget? + if (dlgResult != QDialog::Accepted) return; + + // If user chose "Other", must now get a language from him. + if(languageCode == "other") + { + // Create a QHBox to host KListView. + QHBox* hBox = new QHBox(m_kttsmgrw, "SelectLanguage_hbox"); + // Create a KListView and fill with all known languages. + KListView* langLView = new KListView(hBox, "SelectLanguage_lview"); + langLView->addColumn(i18n("Language")); + langLView->addColumn(i18n("Code")); + QStringList allLocales = KGlobal::locale()->allLanguagesTwoAlpha(); + QString locale; + QString countryCode; + QString charSet; + QString language; + const int allLocalesCount = allLocales.count(); + for (int ndx=0; ndx < allLocalesCount; ++ndx) + { + locale = allLocales[ndx]; + language = TalkerCode::languageCodeToLanguage(locale); + new KListViewItem(langLView, language, locale); + } + // Sort by language. + langLView->setSorting(0); + langLView->sort(); + // Display the box in a dialog. + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Select Language"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_kttsmgrw, + "SelectLanguage_dlg", + true, + true); + dlg->setMainWidget(hBox); + dlg->setHelp("select-plugin", "kttsd"); + dlg->setInitialSize(QSize(200, 500), false); + dlgResult = dlg->exec(); + languageCode = QString::null; + if (langLView->selectedItem()) languageCode = langLView->selectedItem()->text(1); + delete dlg; + // TODO: Also delete KListView and QHBox? + if (dlgResult != QDialog::Accepted) return; + } + + if (languageCode.isEmpty()) return; + QString language = TalkerCode::languageCodeToLanguage(languageCode); + if (language.isEmpty()) return; + + m_languagesToCodes[language] = languageCode; + + // Assign a new Talker ID for the talker. Wraps around to 1. + QString talkerID = QString::number(m_lastTalkerID + 1); + + // Erase extraneous Talker configuration entries that might be there. + m_config->deleteGroup(QString("Talker_")+talkerID); + m_config->sync(); + + // Convert translated plugin name to DesktopEntryName. + QString desktopEntryName = TalkerCode::TalkerNameToDesktopEntryName(synthName); + // This shouldn't happen, but just in case. + if (desktopEntryName.isEmpty()) return; + + // Load the plugin. + m_loadedTalkerPlugIn = loadTalkerPlugin(desktopEntryName); + if (!m_loadedTalkerPlugIn) return; + + // Give plugin the user's language code and permit plugin to autoconfigure itself. + m_loadedTalkerPlugIn->setDesiredLanguage(languageCode); + m_loadedTalkerPlugIn->load(m_config, QString("Talker_")+talkerID); + + // If plugin was able to configure itself, it returns a full talker code. + // If not, display configuration dialog for user to configure the plugin. + QString talkerCode = m_loadedTalkerPlugIn->getTalkerCode(); + if (talkerCode.isEmpty()) + { + // Display configuration dialog. + configureTalker(); + // Did user Cancel? + if (!m_loadedTalkerPlugIn) + { + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; + return; + } + talkerCode = m_loadedTalkerPlugIn->getTalkerCode(); + } + + // If still no Talker Code, abandon. + if (!talkerCode.isEmpty()) + { + // Let plugin save its configuration. + m_config->setGroup(QString("Talker_")+talkerID); + m_loadedTalkerPlugIn->save(m_config, QString("Talker_"+talkerID)); + + // Record last Talker ID used for next add. + m_lastTalkerID = talkerID.toInt(); + + // Record configuration data. Note, might as well do this now. + m_config->setGroup(QString("Talker_")+talkerID); + m_config->writeEntry("DesktopEntryName", desktopEntryName); + talkerCode = TalkerCode::normalizeTalkerCode(talkerCode, languageCode); + m_config->writeEntry("TalkerCode", talkerCode); + m_config->sync(); + + // Add listview item. + QListViewItem* talkerItem = m_kttsmgrw->talkersList->lastChild(); + if (talkerItem) + talkerItem = new KListViewItem(m_kttsmgrw->talkersList, talkerItem, + QString::number(m_lastTalkerID), language, synthName); + else + talkerItem = new KListViewItem(m_kttsmgrw->talkersList, + QString::number(m_lastTalkerID), language, synthName); + + // Set additional columns of the listview item. + updateTalkerItem(talkerItem, talkerCode); + + // Make sure visible. + m_kttsmgrw->talkersList->ensureItemVisible(talkerItem); + + // Select the new item, update buttons. + m_kttsmgrw->talkersList->setSelected(talkerItem, true); + updateTalkerButtons(); + + // Inform Control Center that change has been made. + configChanged(); + } + + // Don't need plugin in memory anymore. + delete m_loadedTalkerPlugIn; + m_loadedTalkerPlugIn = 0; + if (m_configDlg) + { + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; + } + + // kdDebug() << "KCMKttsMgr::addTalker: done." << endl; +} + +void KCMKttsMgr::slot_addNormalFilter() +{ + addFilter( false ); +} + +void KCMKttsMgr:: slot_addSbdFilter() +{ + addFilter( true ); +} + +/** +* Add a filter. +*/ +void KCMKttsMgr::addFilter( bool sbd) +{ + // Build a list of filters that support multiple instances and let user choose. + KListView* lView = m_kttsmgrw->filtersList; + if (sbd) lView = m_kttsmgrw->sbdsList; + + QStringList filterPlugInNames; + QListViewItem* item = lView->firstChild(); + while (item) + { + if (item->text(flvcMultiInstance) == "T") + { + if (!filterPlugInNames.contains(item->text(flvcPlugInName))) + filterPlugInNames.append(item->text(flvcPlugInName)); + } + item = item->nextSibling(); + } + // Append those available plugins not yet in the list at all. + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin"); + for (unsigned int i=0; i < offers.count() ; ++i) + { + QString filterPlugInName = offers[i]->name(); + if (countFilterPlugins(filterPlugInName) == 0) + { + QString desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); + KttsFilterConf* filterConf = loadFilterPlugin(desktopEntryName); + if (filterConf) + { + if (filterConf->isSBD() == sbd) + filterPlugInNames.append(filterPlugInName); + delete filterConf; + } + } + } + + // If no choice (shouldn't happen), bail out. + // kdDebug() << "KCMKttsMgr::addFilter: filterPluginNames = " << filterPlugInNames << endl; + if (filterPlugInNames.count() == 0) return; + + // If exactly one choice, skip selection dialog, otherwise display list to user to select from. + bool okChosen = false; + QString filterPlugInName; + if (filterPlugInNames.count() > 1) + { + filterPlugInName = KInputDialog::getItem( + i18n("Select Filter"), + i18n("Filter"), + filterPlugInNames, + 0, + false, + &okChosen, + m_kttsmgrw, + "selectfilter_kttsd"); + if (!okChosen) return; + } else + filterPlugInName = filterPlugInNames[0]; + + // Assign a new Filter ID for the filter. Wraps around to 1. + QString filterID = QString::number(m_lastFilterID + 1); + + // Erase extraneous Filter configuration entries that might be there. + m_config->deleteGroup(QString("Filter_")+filterID); + m_config->sync(); + + // Get DesktopEntryName from the translated name. + QString desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); + // This shouldn't happen, but just in case. + if (desktopEntryName.isEmpty()) return; + + // Load the plugin. + m_loadedFilterPlugIn = loadFilterPlugin(desktopEntryName); + if (!m_loadedFilterPlugIn) return; + + // Permit plugin to autoconfigure itself. + m_loadedFilterPlugIn->load(m_config, QString("Filter_")+filterID); + + // Display configuration dialog for user to configure the plugin. + configureFilter(); + + // Did user Cancel? + if (!m_loadedFilterPlugIn) + { + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; + return; + } + + // Get user's name for Filter. + QString userFilterName = m_loadedFilterPlugIn->userPlugInName(); + + // If user properly configured the plugin, save its configuration. + if ( !userFilterName.isEmpty() ) + { + // Let plugin save its configuration. + m_config->setGroup(QString("Filter_")+filterID); + m_loadedFilterPlugIn->save(m_config, QString("Filter_"+filterID)); + + // Record last Filter ID used for next add. + m_lastFilterID = filterID.toInt(); + + // Determine if filter supports multiple instances. + bool multiInstance = m_loadedFilterPlugIn->supportsMultiInstance(); + + // Record configuration data. Note, might as well do this now. + m_config->setGroup(QString("Filter_")+filterID); + m_config->writeEntry("DesktopEntryName", desktopEntryName); + m_config->writeEntry("UserFilterName", userFilterName); + m_config->writeEntry("MultiInstance", multiInstance); + m_config->writeEntry("Enabled", true); + m_config->writeEntry("IsSBD", sbd); + m_config->sync(); + + // Add listview item. + QListViewItem* filterItem = lView->lastChild(); + if (sbd) + { + if (filterItem) + filterItem = new KListViewItem( lView, filterItem, userFilterName ); + else + filterItem = new KListViewItem( lView, userFilterName ); + } + else + { + if (filterItem) + filterItem = new KttsCheckListItem(lView, filterItem, + userFilterName, QCheckListItem::CheckBox, this); + else + filterItem = new KttsCheckListItem(lView, + userFilterName, QCheckListItem::CheckBox, this); + dynamic_cast<QCheckListItem*>(filterItem)->setOn(true); + } + filterItem->setText(flvcFilterID, QString::number(m_lastFilterID)); + filterItem->setText(flvcPlugInName, filterPlugInName); + if (multiInstance) + filterItem->setText(flvcMultiInstance, "T"); + else + filterItem->setText(flvcMultiInstance, "F"); + + // Make sure visible. + lView->ensureItemVisible(filterItem); + + // Select the new item, update buttons. + lView->setSelected(filterItem, true); + if (sbd) + updateSbdButtons(); + else + updateFilterButtons(); + + // Inform Control Center that change has been made. + configChanged(); + } + + // Don't need plugin in memory anymore. + delete m_loadedFilterPlugIn; + m_loadedFilterPlugIn = 0; + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; + + // kdDebug() << "KCMKttsMgr::addFilter: done." << endl; +} + +/** +* Remove talker. +*/ +void KCMKttsMgr::slot_removeTalker(){ + // kdDebug() << "KCMKttsMgr::removeTalker: Running"<< endl; + + // Get the selected talker. + QListViewItem *itemToRemove = m_kttsmgrw->talkersList->selectedItem(); + if (!itemToRemove) return; + + // Delete the talker from configuration file. +// QString talkerID = itemToRemove->text(tlvcTalkerID); +// m_config->deleteGroup("Talker_"+talkerID, true, false); + + // Delete the talker from list view. + delete itemToRemove; + + updateTalkerButtons(); + + // Emit configuraton changed. + configChanged(); +} + +void KCMKttsMgr::slot_removeNormalFilter() +{ + removeFilter( false ); +} + +void KCMKttsMgr::slot_removeSbdFilter() +{ + removeFilter( true ); +} + +/** +* Remove filter. +*/ +void KCMKttsMgr::removeFilter( bool sbd ) +{ + // kdDebug() << "KCMKttsMgr::removeFilter: Running"<< endl; + + KListView* lView = m_kttsmgrw->filtersList; + if (sbd) lView = m_kttsmgrw->sbdsList; + // Get the selected filter. + QListViewItem *itemToRemove = lView->selectedItem(); + if (!itemToRemove) return; + + // Delete the filter from configuration file. +// QString filterID = itemToRemove->text(flvcFilterID); +// m_config->deleteGroup("Filter_"+filterID, true, false); + + // Delete the filter from list view. + delete itemToRemove; + + if (sbd) + updateSbdButtons(); + else + updateFilterButtons(); + + // Emit configuraton changed. + configChanged(); +} + +void KCMKttsMgr::slot_higherTalkerPriority() +{ + higherItemPriority( m_kttsmgrw->talkersList ); + updateTalkerButtons(); +} + +void KCMKttsMgr::slot_higherNormalFilterPriority() +{ + higherItemPriority( m_kttsmgrw->filtersList ); + updateFilterButtons(); +} + +void KCMKttsMgr::slot_higherSbdFilterPriority() +{ + higherItemPriority( m_kttsmgrw->sbdsList ); + updateSbdButtons(); +} + +/** +* This is called whenever user clicks the Up button. +*/ +void KCMKttsMgr::higherItemPriority( KListView* lView ) +{ + QListViewItem* item = lView->selectedItem(); + if (!item) return; + QListViewItem* prevItem = item->itemAbove(); + if (!prevItem) return; + prevItem->moveItem(item); + lView->setSelected(item, true); + lView->ensureItemVisible( item ); + configChanged(); +} + +void KCMKttsMgr::slot_lowerTalkerPriority() +{ + lowerItemPriority( m_kttsmgrw->talkersList ); + updateTalkerButtons(); +} + +void KCMKttsMgr::slot_lowerNormalFilterPriority() +{ + lowerItemPriority( m_kttsmgrw->filtersList ); + updateFilterButtons(); +} + +void KCMKttsMgr::slot_lowerSbdFilterPriority() +{ + lowerItemPriority( m_kttsmgrw->sbdsList ); + updateSbdButtons(); +} + +/** +* This is called whenever user clicks the Down button. +*/ +void KCMKttsMgr::lowerItemPriority( KListView* lView ) +{ + QListViewItem* item = lView->selectedItem(); + if (!item) return; + QListViewItem* nextItem = item->itemBelow(); + if (!nextItem) return; + item->moveItem(nextItem); + lView->setSelected(item, true); + lView->ensureItemVisible( item ); + configChanged(); +} + +/** +* Update the status of the Talker buttons. +*/ +void KCMKttsMgr::updateTalkerButtons(){ + // kdDebug() << "KCMKttsMgr::updateTalkerButtons: Running"<< endl; + if(m_kttsmgrw->talkersList->selectedItem()){ + m_kttsmgrw->removeTalkerButton->setEnabled(true); + m_kttsmgrw->configureTalkerButton->setEnabled(true); + m_kttsmgrw->higherTalkerPriorityButton->setEnabled( + m_kttsmgrw->talkersList->selectedItem()->itemAbove() != 0); + m_kttsmgrw->lowerTalkerPriorityButton->setEnabled( + m_kttsmgrw->talkersList->selectedItem()->itemBelow() != 0); + } else { + m_kttsmgrw->removeTalkerButton->setEnabled(false); + m_kttsmgrw->configureTalkerButton->setEnabled(false); + m_kttsmgrw->higherTalkerPriorityButton->setEnabled(false); + m_kttsmgrw->lowerTalkerPriorityButton->setEnabled(false); + } + // kdDebug() << "KCMKttsMgr::updateTalkerButtons: Exiting"<< endl; +} + +/** +* Update the status of the normal Filter buttons. +*/ +void KCMKttsMgr::updateFilterButtons(){ + // kdDebug() << "KCMKttsMgr::updateFilterButtons: Running"<< endl; + QListViewItem* item = m_kttsmgrw->filtersList->selectedItem(); + if (item) { + m_kttsmgrw->removeFilterButton->setEnabled(true); + m_kttsmgrw->configureFilterButton->setEnabled(true); + m_kttsmgrw->higherFilterPriorityButton->setEnabled( + m_kttsmgrw->filtersList->selectedItem()->itemAbove() != 0); + m_kttsmgrw->lowerFilterPriorityButton->setEnabled( + m_kttsmgrw->filtersList->selectedItem()->itemBelow() != 0); + } else { + m_kttsmgrw->removeFilterButton->setEnabled(false); + m_kttsmgrw->configureFilterButton->setEnabled(false); + m_kttsmgrw->higherFilterPriorityButton->setEnabled(false); + m_kttsmgrw->lowerFilterPriorityButton->setEnabled(false); + } + // kdDebug() << "KCMKttsMgr::updateFilterButtons: Exiting"<< endl; +} + +/** + * Update the status of the SBD buttons. + */ +void KCMKttsMgr::updateSbdButtons(){ + // kdDebug() << "KCMKttsMgr::updateSbdButtons: Running"<< endl; + QListViewItem* item = m_kttsmgrw->sbdsList->selectedItem(); + if (item) { + m_sbdPopmenu->setItemEnabled( sbdBtnEdit, true ); + m_sbdPopmenu->setItemEnabled( sbdBtnUp, + m_kttsmgrw->sbdsList->selectedItem()->itemAbove() != 0 ); + m_sbdPopmenu->setItemEnabled( sbdBtnDown, + m_kttsmgrw->sbdsList->selectedItem()->itemBelow() != 0 ); + m_sbdPopmenu->setItemEnabled( sbdBtnRemove, true ); + } else { + m_sbdPopmenu->setItemEnabled( sbdBtnEdit, false ); + m_sbdPopmenu->setItemEnabled( sbdBtnUp, false ); + m_sbdPopmenu->setItemEnabled( sbdBtnDown, false ); + m_sbdPopmenu->setItemEnabled( sbdBtnRemove, false ); + } + // kdDebug() << "KCMKttsMgr::updateSbdButtons: Exiting"<< endl; +} + +/** +* This signal is emitted whenever user checks/unchecks the Enable TTS System check box. +*/ +void KCMKttsMgr::enableKttsdToggled(bool) +{ + // Prevent re-entrancy. + static bool reenter; + if (reenter) return; + reenter = true; + // See if KTTSD is running. + DCOPClient *client = kapp->dcopClient(); + bool kttsdRunning = (client->isApplicationRegistered("kttsd")); + // kdDebug() << "KCMKttsMgr::enableKttsdToggled: kttsdRunning = " << kttsdRunning << endl; + // If Enable KTTSD check box is checked and it is not running, then start KTTSD. + if (m_kttsmgrw->enableKttsdCheckBox->isChecked()) + { + if (!kttsdRunning) + { + // kdDebug() << "KCMKttsMgr::enableKttsdToggled:: Starting KTTSD" << endl; + QString error; + if (KApplication::startServiceByDesktopName("kttsd", QStringList(), &error)) + { + kdDebug() << "Starting KTTSD failed with message " << error << endl; + m_kttsmgrw->enableKttsdCheckBox->setChecked(false); + m_kttsmgrw->notifyTestButton->setEnabled(false); + } + } + } + else + // If check box is not checked and it is running, then stop KTTSD. + { + if (kttsdRunning) + { + // kdDebug() << "KCMKttsMgr::enableKttsdToggled:: Stopping KTTSD" << endl; + QByteArray data; + client->send("kttsd", "KSpeech", "kttsdExit()", data); + } + } + reenter = false; +} + +/** +* This signal is emitted whenever user checks/unchecks the GStreamer radio button. +*/ +void KCMKttsMgr::slotGstreamerRadioButton_toggled(bool state) +{ + m_kttsmgrw->sinkLabel->setEnabled(state); + m_kttsmgrw->sinkComboBox->setEnabled(state); +} + +/** +* This signal is emitted whenever user checks/unchecks the ALSA radio button. +*/ +void KCMKttsMgr::slotAlsaRadioButton_toggled(bool state) +{ + m_kttsmgrw->pcmLabel->setEnabled(state); + m_kttsmgrw->pcmComboBox->setEnabled(state); + m_kttsmgrw->pcmCustom->setEnabled(state && m_kttsmgrw->pcmComboBox->currentText() == "custom"); +} + +/** +* This is emitted whenever user activates the ALSA pcm combobox. +*/ +void KCMKttsMgr::slotPcmComboBox_activated() +{ + m_kttsmgrw->pcmCustom->setEnabled(m_kttsmgrw->pcmComboBox->currentText() == "custom"); +} + +/** +* This signal is emitted whenever user checks/unchecks the aKode radio button. +*/ +void KCMKttsMgr::slotAkodeRadioButton_toggled(bool state) +{ + m_kttsmgrw->akodeSinkLabel->setEnabled(state); + m_kttsmgrw->akodeComboBox->setEnabled(state); +} + +/** +* This slot is called whenever KTTSD starts or restarts. +*/ +void KCMKttsMgr::kttsdStarted() +{ + // kdDebug() << "KCMKttsMgr::kttsdStarted: Running" << endl; + bool kttsdLoaded = (m_jobMgrPart != 0); + // Load Job Manager Part library. + if (!kttsdLoaded) + { + KLibFactory *factory = KLibLoader::self()->factory( "libkttsjobmgrpart" ); + if (factory) + { + // Create the Job Manager part + m_jobMgrPart = (KParts::ReadOnlyPart *)factory->create( m_kttsmgrw->mainTab, "kttsjobmgr", + "KParts::ReadOnlyPart" ); + if (m_jobMgrPart) + { + // Add the Job Manager part as a new tab. + m_kttsmgrw->mainTab->addTab(m_jobMgrPart->widget(), i18n("&Jobs")); + kttsdLoaded = true; + } + else + kdDebug() << "Could not create kttsjobmgr part." << endl; + } + else kdDebug() << "Could not load libkttsjobmgrpart. Is libkttsjobmgrpart installed?" << endl; + } + // Check/Uncheck the Enable KTTSD check box. + if (kttsdLoaded) + { + m_kttsmgrw->enableKttsdCheckBox->setChecked(true); + // Enable/disable notify Test button. + slotNotifyListView_selectionChanged(); + } else { + m_kttsmgrw->enableKttsdCheckBox->setChecked(false); + m_kttsmgrw->notifyTestButton->setEnabled(false); + } +} + +/** +* This slot is called whenever KTTSD is about to exit. +*/ +void KCMKttsMgr::kttsdExiting() +{ + // kdDebug() << "KCMKttsMgr::kttsdExiting: Running" << endl; + if (m_jobMgrPart) + { + m_kttsmgrw->mainTab->removePage(m_jobMgrPart->widget()); + delete m_jobMgrPart; + m_jobMgrPart = 0; + } + m_kttsmgrw->enableKttsdCheckBox->setChecked(false); + m_kttsmgrw->notifyTestButton->setEnabled(false); +} + +/** +* User has requested display of talker configuration dialog. +*/ +void KCMKttsMgr::slot_configureTalker() +{ + // Get highlighted plugin from Talker ListView and load into memory. + QListViewItem* talkerItem = m_kttsmgrw->talkersList->selectedItem(); + if (!talkerItem) return; + QString talkerID = talkerItem->text(tlvcTalkerID); + QString synthName = talkerItem->text(tlvcSynthName); + QString language = talkerItem->text(tlvcLanguage); + QString languageCode = m_languagesToCodes[language]; + QString desktopEntryName = TalkerCode::TalkerNameToDesktopEntryName(synthName); + m_loadedTalkerPlugIn = loadTalkerPlugin(desktopEntryName); + if (!m_loadedTalkerPlugIn) return; + // kdDebug() << "KCMKttsMgr::slot_configureTalker: plugin for " << synthName << " loaded successfully." << endl; + + // Tell plugin to load its configuration. + m_config->setGroup(QString("Talker_")+talkerID); + m_loadedTalkerPlugIn->setDesiredLanguage(languageCode); + // kdDebug() << "KCMKttsMgr::slot_configureTalker: about to call plugin load() method with Talker ID = " << talkerID << endl; + m_loadedTalkerPlugIn->load(m_config, QString("Talker_")+talkerID); + + // Display configuration dialog. + configureTalker(); + + // Did user Cancel? + if (!m_loadedTalkerPlugIn) + { + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; + return; + } + + // Get Talker Code. Note that plugin may return a code different from before. + QString talkerCode = m_loadedTalkerPlugIn->getTalkerCode(); + + // If plugin was successfully configured, save its configuration. + if (!talkerCode.isEmpty()) + { + m_config->setGroup(QString("Talker_")+talkerID); + m_loadedTalkerPlugIn->save(m_config, QString("Talker_")+talkerID); + m_config->setGroup(QString("Talker_")+talkerID); + talkerCode = TalkerCode::normalizeTalkerCode(talkerCode, languageCode); + m_config->writeEntry("TalkerCode", talkerCode); + m_config->sync(); + + // Update display. + updateTalkerItem(talkerItem, talkerCode); + + // Inform Control Center that configuration has changed. + configChanged(); + } + + delete m_loadedTalkerPlugIn; + m_loadedTalkerPlugIn = 0; + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; +} + +void KCMKttsMgr::slot_configureNormalFilter() +{ + configureFilterItem( false ); +} + +void KCMKttsMgr::slot_configureSbdFilter() +{ + configureFilterItem( true ); +} + +/** + * User has requested display of filter configuration dialog. + */ +void KCMKttsMgr::configureFilterItem( bool sbd ) +{ + // Get highlighted plugin from Filter ListView and load into memory. + KListView* lView = m_kttsmgrw->filtersList; + if (sbd) lView = m_kttsmgrw->sbdsList; + QListViewItem* filterItem = lView->selectedItem(); + if (!filterItem) return; + QString filterID = filterItem->text(flvcFilterID); + QString filterPlugInName = filterItem->text(flvcPlugInName); + QString desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); + if (desktopEntryName.isEmpty()) return; + m_loadedFilterPlugIn = loadFilterPlugin(desktopEntryName); + if (!m_loadedFilterPlugIn) return; + // kdDebug() << "KCMKttsMgr::slot_configureFilter: plugin for " << filterPlugInName << " loaded successfully." << endl; + + // Tell plugin to load its configuration. + m_config->setGroup(QString("Filter_")+filterID); + // kdDebug() << "KCMKttsMgr::slot_configureFilter: about to call plugin load() method with Filter ID = " << filterID << endl; + m_loadedFilterPlugIn->load(m_config, QString("Filter_")+filterID); + + // Display configuration dialog. + configureFilter(); + + // Did user Cancel? + if (!m_loadedFilterPlugIn) + { + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; + return; + } + + // Get user's name for the plugin. + QString userFilterName = m_loadedFilterPlugIn->userPlugInName(); + + // If user properly configured the plugin, save the configuration. + if ( !userFilterName.isEmpty() ) + { + + // Let plugin save its configuration. + m_config->setGroup(QString("Filter_")+filterID); + m_loadedFilterPlugIn->save(m_config, QString("Filter_")+filterID); + + // Save configuration. + m_config->setGroup("Filter_"+filterID); + m_config->writeEntry("DesktopEntryName", desktopEntryName); + m_config->writeEntry("UserFilterName", userFilterName); + m_config->writeEntry("Enabled", true); + m_config->writeEntry("MultiInstance", m_loadedFilterPlugIn->supportsMultiInstance()); + m_config->writeEntry("IsSBD", sbd); + + m_config->sync(); + + // Update display. + filterItem->setText(flvcUserName, userFilterName); + if (!sbd) + dynamic_cast<QCheckListItem*>(filterItem)->setOn(true); + + // Inform Control Center that configuration has changed. + configChanged(); + } + + delete m_loadedFilterPlugIn; + m_loadedFilterPlugIn = 0; + m_configDlg->setMainWidget(0); + delete m_configDlg; + m_configDlg = 0; +} + +/** +* Display talker configuration dialog. The plugin is assumed already loaded into +* memory referenced by m_loadedTalkerPlugIn. +*/ +void KCMKttsMgr::configureTalker() +{ + if (!m_loadedTalkerPlugIn) return; + m_configDlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Talker Configuration"), + KDialogBase::Help|KDialogBase::Default|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_kttsmgrw, + "configureTalker_dlg", + true, + true); + m_configDlg->setInitialSize(QSize(700, 300), false); + m_configDlg->setMainWidget(m_loadedTalkerPlugIn); + m_configDlg->setHelp("configure-plugin", "kttsd"); + m_configDlg->enableButtonOK(false); + connect(m_loadedTalkerPlugIn, SIGNAL( changed(bool) ), this, SLOT( slotConfigTalkerDlg_ConfigChanged() )); + connect(m_configDlg, SIGNAL( defaultClicked() ), this, SLOT( slotConfigTalkerDlg_DefaultClicked() )); + connect(m_configDlg, SIGNAL( cancelClicked() ), this, SLOT (slotConfigTalkerDlg_CancelClicked() )); + // Create a Player object for the plugin to use for testing. + int playerOption = 0; + QString sinkName; + if (m_kttsmgrw->gstreamerRadioButton->isChecked()) { + playerOption = 1; + sinkName = m_kttsmgrw->sinkComboBox->currentText(); + } + if (m_kttsmgrw->alsaRadioButton->isChecked()) { + playerOption = 2; + if (m_kttsmgrw->pcmComboBox->currentText() == "custom") + sinkName = m_kttsmgrw->pcmCustom->text(); + else + sinkName = m_kttsmgrw->pcmComboBox->currentText(); + } + if (m_kttsmgrw->akodeRadioButton->isChecked()) { + playerOption = 3; + sinkName = m_kttsmgrw->akodeComboBox->currentText(); + } + float audioStretchFactor = 1.0/(float(m_kttsmgrw->timeBox->value())/100.0); + // kdDebug() << "KCMKttsMgr::configureTalker: playerOption = " << playerOption << " audioStretchFactor = " << audioStretchFactor << " sink name = " << sinkName << endl; + TestPlayer* testPlayer = new TestPlayer(this, "ktts_testplayer", + playerOption, audioStretchFactor, sinkName); + m_loadedTalkerPlugIn->setPlayer(testPlayer); + // Display the dialog. + m_configDlg->exec(); + // Done with Player object. + if (m_loadedTalkerPlugIn) + { + delete testPlayer; + m_loadedTalkerPlugIn->setPlayer(0); + } +} + +/** +* Display filter configuration dialog. The plugin is assumed already loaded into +* memory referenced by m_loadedFilterPlugIn. +*/ +void KCMKttsMgr::configureFilter() +{ + if (!m_loadedFilterPlugIn) return; + m_configDlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Filter Configuration"), + KDialogBase::Help|KDialogBase::Default|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_kttsmgrw, + "configureFilter_dlg", + true, + true); + m_configDlg->setInitialSize(QSize(600, 450), false); + m_loadedFilterPlugIn->setMinimumSize(m_loadedFilterPlugIn->minimumSizeHint()); + m_loadedFilterPlugIn->show(); + m_configDlg->setMainWidget(m_loadedFilterPlugIn); + m_configDlg->setHelp("configure-filter", "kttsd"); + m_configDlg->enableButtonOK(false); + connect(m_loadedFilterPlugIn, SIGNAL( changed(bool) ), this, SLOT( slotConfigFilterDlg_ConfigChanged() )); + connect(m_configDlg, SIGNAL( defaultClicked() ), this, SLOT( slotConfigFilterDlg_DefaultClicked() )); + connect(m_configDlg, SIGNAL( cancelClicked() ), this, SLOT (slotConfigFilterDlg_CancelClicked() )); + // Display the dialog. + m_configDlg->exec(); +} + +/** +* Count number of configured Filters with the specified plugin name. +*/ +int KCMKttsMgr::countFilterPlugins(const QString& filterPlugInName) +{ + int cnt = 0; + QListViewItem* item = m_kttsmgrw->filtersList->firstChild(); + while (item) + { + if (item->text(flvcPlugInName) == filterPlugInName) ++cnt; + item = item->nextSibling(); + } + item = m_kttsmgrw->sbdsList->firstChild(); + while (item) + { + if (item->text(slvcPlugInName) == filterPlugInName) ++cnt; + item = item->nextSibling(); + } + return cnt; +} + +void KCMKttsMgr::keepAudioCheckBox_toggled(bool checked) +{ + m_kttsmgrw->keepAudioPath->setEnabled(checked); + configChanged(); +} + +// 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 KCMKttsMgr::percentToSlider(int percentValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor (0.5 + alpha * (log(percentValue)-log(50))); +} + +int KCMKttsMgr::sliderToPercent(int sliderValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor(0.5 + exp (sliderValue/alpha + log(50))); +} + +void KCMKttsMgr::timeBox_valueChanged(int percentValue) { + m_kttsmgrw->timeSlider->setValue (percentToSlider (percentValue)); +} + +void KCMKttsMgr::timeSlider_valueChanged(int sliderValue) { + m_kttsmgrw->timeBox->setValue (sliderToPercent (sliderValue)); +} + +void KCMKttsMgr::slotConfigTalkerDlg_ConfigChanged() +{ + m_configDlg->enableButtonOK(!m_loadedTalkerPlugIn->getTalkerCode().isEmpty()); +} + +void KCMKttsMgr::slotConfigFilterDlg_ConfigChanged() +{ + m_configDlg->enableButtonOK( !m_loadedFilterPlugIn->userPlugInName().isEmpty() ); +} + +void KCMKttsMgr::slotConfigTalkerDlg_DefaultClicked() +{ + m_loadedTalkerPlugIn->defaults(); +} + +void KCMKttsMgr::slotConfigFilterDlg_DefaultClicked() +{ + m_loadedFilterPlugIn->defaults(); +} + +void KCMKttsMgr::slotConfigTalkerDlg_CancelClicked() +{ + delete m_loadedTalkerPlugIn; + m_loadedTalkerPlugIn = 0; +} + +void KCMKttsMgr::slotConfigFilterDlg_CancelClicked() +{ + delete m_loadedFilterPlugIn; + m_loadedFilterPlugIn = 0; +} + +/** +* This slot is called whenever user checks/unchecks item in Filters list. +*/ +void KCMKttsMgr::slotFiltersList_stateChanged() +{ + // kdDebug() << "KCMKttsMgr::slotFiltersList_stateChanged: calling configChanged" << endl; + configChanged(); +} + +/** + * Uses KTrader to convert a translated Filter Plugin Name to DesktopEntryName. + * @param name The translated plugin name. From Name= line in .desktop file. + * @return DesktopEntryName. The name of the .desktop file (less .desktop). + * QString::null if not found. + */ +QString KCMKttsMgr::FilterNameToDesktopEntryName(const QString& name) +{ + if (name.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin"); + for (uint ndx = 0; ndx < offers.count(); ++ndx) + if (offers[ndx]->name() == name) return offers[ndx]->desktopEntryName(); + return QString::null; +} + +/** + * Uses KTrader to convert a DesktopEntryName into a translated Filter Plugin Name. + * @param desktopEntryName The DesktopEntryName. + * @return The translated Name of the plugin, from Name= line in .desktop file. + */ +QString KCMKttsMgr::FilterDesktopEntryNameToName(const QString& desktopEntryName) +{ + if (desktopEntryName.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin", + QString("DesktopEntryName == '%1'").arg(desktopEntryName)); + + if (offers.count() == 1) + return offers[0]->name(); + else + return QString::null; +} + +/** + * Loads notify events from a file. Clearing listview if clear is True. + */ +QString KCMKttsMgr::loadNotifyEventsFromFile( const QString& filename, bool clear) +{ + // Open existing event list. + QFile file( filename ); + if ( !file.open( IO_ReadOnly ) ) + { + return i18n("Unable to open file.") + filename; + } + // QDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" ); + QDomDocument doc( "" ); + if ( !doc.setContent( &file ) ) { + file.close(); + return i18n("File not in proper XML format."); + } + // kdDebug() << "StringReplacerConf::load: document successfully parsed." << endl; + file.close(); + + // Clear list view. + if ( clear ) m_kttsmgrw->notifyListView->clear(); + + // Event list. + QDomNodeList eventList = doc.elementsByTagName("notifyEvent"); + const int eventListCount = eventList.count(); + for (int eventIndex = 0; eventIndex < eventListCount; ++eventIndex) + { + QDomNode eventNode = eventList.item(eventIndex); + QDomNodeList propList = eventNode.childNodes(); + QString eventSrc; + QString event; + QString actionName; + QString message; + TalkerCode talkerCode; + const int propListCount = propList.count(); + for (int propIndex = 0; propIndex < propListCount; ++propIndex) + { + QDomNode propNode = propList.item(propIndex); + QDomElement prop = propNode.toElement(); + if (prop.tagName() == "eventSrc") eventSrc = prop.text(); + if (prop.tagName() == "event") event = prop.text(); + if (prop.tagName() == "action") actionName = prop.text(); + if (prop.tagName() == "message") message = prop.text(); + if (prop.tagName() == "talker") talkerCode = TalkerCode(prop.text(), false); + } + addNotifyItem(eventSrc, event, NotifyAction::action( actionName ), message, talkerCode); + } + + return QString::null; +} + +/** + * Saves notify events to a file. + */ +QString KCMKttsMgr::saveNotifyEventsToFile(const QString& filename) +{ + QFile file( filename ); + if ( !file.open( IO_WriteOnly ) ) + return i18n("Unable to open file ") + filename; + + QDomDocument doc( "" ); + + QDomElement root = doc.createElement( "notifyEventList" ); + doc.appendChild( root ); + + // Events. + KListView* lv = m_kttsmgrw->notifyListView; + QListViewItemIterator it(lv); + while ( it.current() ) + { + QListViewItem* item = *it; + if ( item->depth() > 0 ) + { + QDomElement wordTag = doc.createElement( "notifyEvent" ); + root.appendChild( wordTag ); + + QDomElement propTag = doc.createElement( "eventSrc" ); + wordTag.appendChild( propTag); + QDomText t = doc.createTextNode( item->text(nlvcEventSrc) ); + propTag.appendChild( t ); + + propTag = doc.createElement( "event" ); + wordTag.appendChild( propTag); + t = doc.createTextNode( item->text(nlvcEvent) ); + propTag.appendChild( t ); + + propTag = doc.createElement( "action" ); + wordTag.appendChild( propTag); + t = doc.createTextNode( item->text(nlvcAction) ); + propTag.appendChild( t ); + + if ( item->text(nlvcAction) == NotifyAction::actionName( NotifyAction::SpeakCustom ) ) + { + propTag = doc.createElement( "message" ); + wordTag.appendChild( propTag); + QString msg = item->text(nlvcActionName); + int msglen = msg.length(); + msg = msg.mid( 1, msglen-2 ); + t = doc.createCDATASection( msg ); + propTag.appendChild( t ); + } + + propTag = doc.createElement( "talker" ); + wordTag.appendChild( propTag); + t = doc.createCDATASection( item->text(nlvcTalker) ); + propTag.appendChild( t ); + } + ++it; + } + + // Write it all out. + QTextStream ts( &file ); + ts.setEncoding( QTextStream::UnicodeUTF8 ); + ts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + ts << doc.toString(); + file.close(); + + return QString::null; +} + +void KCMKttsMgr::slotNotifyEnableCheckBox_toggled(bool checked) +{ + m_kttsmgrw->notifyExcludeEventsWithSoundCheckBox->setEnabled( checked ); + m_kttsmgrw->notifyGroup->setEnabled( checked ); + configChanged(); +} + +void KCMKttsMgr::slotNotifyPresentComboBox_activated(int index) +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if ( !item ) return; // should not happen + item->setText( nlvcEvent, NotifyPresent::presentName( index ) ); + item->setText( nlvcEventName, NotifyPresent::presentDisplayName( index ) ); + bool enableIt = ( index != NotifyPresent::None); + m_kttsmgrw->notifyActionComboBox->setEnabled( enableIt ); + m_kttsmgrw->notifyTalkerButton->setEnabled( enableIt ); + if (!enableIt) + { + m_kttsmgrw->notifyTalkerLineEdit->clear(); + } else { + if ( m_kttsmgrw->notifyTalkerLineEdit->text().isEmpty() ) + { + m_kttsmgrw->notifyTalkerLineEdit->setText( i18n("default") ); + } + } + configChanged(); +} + +void KCMKttsMgr::slotNotifyListView_selectionChanged() +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if ( item ) + { + bool topLevel = ( item->depth() == 0 ); + if ( topLevel ) + { + m_kttsmgrw->notifyPresentComboBox->setEnabled( false ); + m_kttsmgrw->notifyActionComboBox->setEnabled( false ); + m_kttsmgrw->notifyTestButton->setEnabled( false ); + m_kttsmgrw->notifyMsgLineEdit->setEnabled( false ); + m_kttsmgrw->notifyMsgLineEdit->clear(); + m_kttsmgrw->notifyTalkerButton->setEnabled( false ); + m_kttsmgrw->notifyTalkerLineEdit->clear(); + bool defaultItem = ( item->text(nlvcEventSrc) == "default" ); + m_kttsmgrw->notifyRemoveButton->setEnabled( !defaultItem ); + } else { + bool defaultItem = ( item->parent()->text(nlvcEventSrc) == "default" ); + m_kttsmgrw->notifyPresentComboBox->setEnabled( defaultItem ); + if ( defaultItem ) + m_kttsmgrw->notifyPresentComboBox->setCurrentItem( NotifyPresent::present( item->text( nlvcEvent ) ) ); + m_kttsmgrw->notifyActionComboBox->setEnabled( true ); + int action = NotifyAction::action( item->text( nlvcAction ) ); + m_kttsmgrw->notifyActionComboBox->setCurrentItem( action ); + m_kttsmgrw->notifyTalkerButton->setEnabled( true ); + TalkerCode talkerCode( item->text( nlvcTalker ) ); + m_kttsmgrw->notifyTalkerLineEdit->setText( talkerCode.getTranslatedDescription() ); + if ( action == NotifyAction::SpeakCustom ) + { + m_kttsmgrw->notifyMsgLineEdit->setEnabled( true ); + QString msg = item->text( nlvcActionName ); + int msglen = msg.length(); + msg = msg.mid( 1, msglen-2 ); + m_kttsmgrw->notifyMsgLineEdit->setText( msg ); + } else { + m_kttsmgrw->notifyMsgLineEdit->setEnabled( false ); + m_kttsmgrw->notifyMsgLineEdit->clear(); + } + m_kttsmgrw->notifyRemoveButton->setEnabled( !defaultItem ); + m_kttsmgrw->notifyTestButton->setEnabled( + action != NotifyAction::DoNotSpeak && + m_kttsmgrw->enableKttsdCheckBox->isChecked()); + } + } else { + m_kttsmgrw->notifyPresentComboBox->setEnabled( false ); + m_kttsmgrw->notifyActionComboBox->setEnabled( false ); + m_kttsmgrw->notifyTestButton->setEnabled( false ); + m_kttsmgrw->notifyMsgLineEdit->setEnabled( false ); + m_kttsmgrw->notifyMsgLineEdit->clear(); + m_kttsmgrw->notifyTalkerButton->setEnabled( false ); + m_kttsmgrw->notifyTalkerLineEdit->clear(); + m_kttsmgrw->notifyRemoveButton->setEnabled( false ); + } +} + +void KCMKttsMgr::slotNotifyActionComboBox_activated(int index) +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if ( item ) + if ( item->depth() == 0 ) item = 0; + if ( !item ) return; // This shouldn't happen. + item->setText( nlvcAction, NotifyAction::actionName( index ) ); + item->setText( nlvcActionName, NotifyAction::actionDisplayName( index ) ); + if ( index == NotifyAction::SpeakCustom ) + item->setText( nlvcActionName, "\"" + m_kttsmgrw->notifyMsgLineEdit->text() + "\"" ); + if ( index == NotifyAction::DoNotSpeak ) + item->setPixmap( nlvcActionName, SmallIcon( "nospeak" ) ); + else + item->setPixmap( nlvcActionName, SmallIcon( "speak" ) ); + slotNotifyListView_selectionChanged(); + configChanged(); +} + +void KCMKttsMgr::slotNotifyMsgLineEdit_textChanged(const QString& text) +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if ( item ) + if ( item->depth() == 0 ) item = 0; + if ( !item ) return; // This shouldn't happen. + if ( m_kttsmgrw->notifyActionComboBox->currentItem() != NotifyAction::SpeakCustom) return; + item->setText( nlvcActionName, "\"" + text + "\"" ); + m_kttsmgrw->notifyTestButton->setEnabled( + !text.isEmpty() && m_kttsmgrw->enableKttsdCheckBox->isChecked()); + configChanged(); +} + +void KCMKttsMgr::slotNotifyTestButton_clicked() +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if (item) + { + QString msg; + int action = NotifyAction::action(item->text(nlvcAction)); + switch (action) + { + case NotifyAction::SpeakEventName: + msg = item->text(nlvcEventName); + break; + case NotifyAction::SpeakMsg: + msg = i18n("sample notification message"); + break; + case NotifyAction::SpeakCustom: + msg = m_kttsmgrw->notifyMsgLineEdit->text(); + msg.replace("%a", i18n("sample application")); + msg.replace("%e", i18n("sample event")); + msg.replace("%m", i18n("sample notification message")); + break; + } + if (!msg.isEmpty()) sayMessage(msg, item->text(nlvcTalker)); + } +} + +void KCMKttsMgr::slotNotifyTalkerButton_clicked() +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if ( item ) + if ( item->depth() == 0 ) item = 0; + if ( !item ) return; // This shouldn't happen. + QString talkerCode = item->text( nlvcTalker ); + SelectTalkerDlg dlg( m_kttsmgrw, "selecttalkerdialog", i18n("Select Talker"), talkerCode, true ); + int dlgResult = dlg.exec(); + if ( dlgResult != KDialogBase::Accepted ) return; + item->setText( nlvcTalker, dlg.getSelectedTalkerCode() ); + QString talkerName = dlg.getSelectedTranslatedDescription(); + item->setText( nlvcTalkerName, talkerName ); + m_kttsmgrw->notifyTalkerLineEdit->setText( talkerName ); + configChanged(); +} + +/** + * Adds an item to the notify listview. + * message is only needed if action = naSpeakCustom. + */ +QListViewItem* KCMKttsMgr::addNotifyItem( + const QString& eventSrc, + const QString& event, + int action, + const QString& message, + TalkerCode& talkerCode) +{ + KListView* lv = m_kttsmgrw->notifyListView; + QListViewItem* item = 0; + QString iconName; + QString eventSrcName; + if (eventSrc == "default") + eventSrcName = i18n("Default (all other events)"); + else + eventSrcName = NotifyEvent::getEventSrcName(eventSrc, iconName); + QString eventName; + if (eventSrc == "default") + eventName = NotifyPresent::presentDisplayName( event ); + else + { + if (event == "default") + eventName = i18n("All other %1 events").arg(eventSrcName); + else + eventName = NotifyEvent::getEventName(eventSrc, event); + } + QString actionName = NotifyAction::actionName( action ); + QString actionDisplayName = NotifyAction::actionDisplayName( action ); + if (action == NotifyAction::SpeakCustom) actionDisplayName = "\"" + message + "\""; + QString talkerName = talkerCode.getTranslatedDescription(); + if (!eventSrcName.isEmpty() && !eventName.isEmpty() && !actionName.isEmpty() && !talkerName.isEmpty()) + { + QListViewItem* parentItem = lv->findItem(eventSrcName, nlvcEventSrcName); + if (!parentItem) + { + item = lv->lastItem(); + if (!item) + parentItem = new KListViewItem(lv, eventSrcName, QString::null, QString::null, + eventSrc); + else + parentItem = new KListViewItem(lv, item, eventSrcName, QString::null, QString::null, + eventSrc); + if ( !iconName.isEmpty() ) + parentItem->setPixmap( nlvcEventSrcName, SmallIcon( iconName ) ); + } + // No duplicates. + item = lv->findItem( event, nlvcEvent ); + if ( !item || item->parent() != parentItem ) + item = new KListViewItem(parentItem, eventName, actionDisplayName, talkerName, + eventSrc, event, actionName, talkerCode.getTalkerCode()); + if ( action == NotifyAction::DoNotSpeak ) + item->setPixmap( nlvcActionName, SmallIcon( "nospeak" ) ); + else + item->setPixmap( nlvcActionName, SmallIcon( "speak" ) ); + } + return item; +} + +void KCMKttsMgr::slotNotifyAddButton_clicked() +{ + QListView* lv = m_kttsmgrw->notifyListView; + QListViewItem* item = lv->selectedItem(); + QString eventSrc; + if ( item ) eventSrc = item->text( nlvcEventSrc ); + SelectEvent* selectEventWidget = new SelectEvent( this, "SelectEvent_widget", 0, eventSrc ); + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Select Event"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_kttsmgrw, + "SelectEvent_dlg", + true, + true); + dlg->setMainWidget( selectEventWidget ); + dlg->setInitialSize( QSize(500, 400) ); + // dlg->setHelp("select-plugin", "kttsd"); + int dlgResult = dlg->exec(); + eventSrc = selectEventWidget->getEventSrc(); + QString event = selectEventWidget->getEvent(); + delete dlg; + if ( dlgResult != QDialog::Accepted ) return; + if ( eventSrc.isEmpty() || event.isEmpty() ) return; + // Use Default action, message, and talker. + QString actionName; + int action = NotifyAction::DoNotSpeak; + QString msg; + TalkerCode talkerCode; + item = lv->findItem( "default", nlvcEventSrc ); + if ( item ) + { + if ( item->childCount() > 0 ) item = item->firstChild(); + if ( item ) + { + actionName = item->text( nlvcAction ); + action = NotifyAction::action( actionName ); + talkerCode = TalkerCode( item->text( nlvcTalker ) ); + if (action == NotifyAction::SpeakCustom ) + { + msg = item->text(nlvcActionName); + int msglen = msg.length(); + msg = msg.mid( 1, msglen-2 ); + } + } + } + item = addNotifyItem( eventSrc, event, action, msg, talkerCode ); + lv->ensureItemVisible( item ); + lv->setSelected( item, true ); + slotNotifyListView_selectionChanged(); + configChanged(); +} + +void KCMKttsMgr::slotNotifyClearButton_clicked() +{ + m_kttsmgrw->notifyListView->clear(); + TalkerCode talkerCode( QString::null ); + QListViewItem* item = addNotifyItem( + QString("default"), + NotifyPresent::presentName(NotifyPresent::Passive), + NotifyAction::SpeakEventName, + QString::null, + talkerCode ); + QListView* lv = m_kttsmgrw->notifyListView; + lv->ensureItemVisible( item ); + lv->setSelected( item, true ); + slotNotifyListView_selectionChanged(); + configChanged(); +} + +void KCMKttsMgr::slotNotifyRemoveButton_clicked() +{ + QListViewItem* item = m_kttsmgrw->notifyListView->selectedItem(); + if (!item) return; + QListViewItem* parentItem = item->parent(); + delete item; + if (parentItem) + { + if (parentItem->childCount() == 0) delete parentItem; + } + slotNotifyListView_selectionChanged(); + configChanged(); +} + +void KCMKttsMgr::slotNotifyLoadButton_clicked() +{ + // QString dataDir = KGlobal::dirs()->resourceDirs("data").last() + "/kttsd/stringreplacer/"; + QString dataDir = KGlobal::dirs()->findAllResources("data", "kttsd/notify/").last(); + QString filename = KFileDialog::getOpenFileName( + dataDir, + "*.xml|" + i18n("file type", "Notification Event List") + " (*.xml)", + m_kttsmgrw, + "event_loadfile"); + if ( filename.isEmpty() ) return; + QString errMsg = loadNotifyEventsFromFile( filename, true ); + slotNotifyListView_selectionChanged(); + if ( !errMsg.isEmpty() ) + KMessageBox::sorry( m_kttsmgrw, errMsg, i18n("Error Opening File") ); + else + configChanged(); +} + +void KCMKttsMgr::slotNotifySaveButton_clicked() +{ + QString filename = KFileDialog::getSaveFileName( + KGlobal::dirs()->saveLocation( "data" ,"kttsd/notify/", false ), + "*.xml|" + i18n("file type", "Notification Event List") + " (*.xml)", + m_kttsmgrw, + "event_savefile"); + if ( filename.isEmpty() ) return; + QString errMsg = saveNotifyEventsToFile( filename ); + slotNotifyListView_selectionChanged(); + if ( !errMsg.isEmpty() ) + KMessageBox::sorry( m_kttsmgrw, errMsg, i18n("Error Opening File") ); +} + +// ---------------------------------------------------------------------------- + +KttsCheckListItem::KttsCheckListItem( QListView *parent, QListViewItem *after, + const QString &text, Type tt, + KCMKttsMgr* kcmkttsmgr ) : + QCheckListItem(parent, after, text, tt), + m_kcmkttsmgr(kcmkttsmgr) { } + +KttsCheckListItem::KttsCheckListItem( QListView *parent, + const QString &text, Type tt, + KCMKttsMgr* kcmkttsmgr ) : + QCheckListItem(parent, text, tt), + m_kcmkttsmgr(kcmkttsmgr) { } + +/*virtual*/ void KttsCheckListItem::stateChange(bool) +{ + if (m_kcmkttsmgr) m_kcmkttsmgr->slotFiltersList_stateChanged(); +} + +/*virtual*/ /*void resizeEvent( QResizeEvent ev ) +{ + dynamic_cast<KCModule>(resizeEvent(ev)); + updateGeometry(); +} +*/ diff --git a/kttsd/kcmkttsmgr/kcmkttsmgr.h b/kttsd/kcmkttsmgr/kcmkttsmgr.h new file mode 100644 index 0000000..39481b9 --- /dev/null +++ b/kttsd/kcmkttsmgr/kcmkttsmgr.h @@ -0,0 +1,530 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KControl module for KTTSD configuration and job management + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + Copyright : (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef KCMKTTSMGR_H +#define KCMKTTSMGR_H + +#include "config.h" + +// Qt includes. +#include <qmap.h> +#include <qlistview.h> + +// KDE includes. +#include <kcmodule.h> +#include <ktrader.h> +#include <kdebug.h> +#include <kparts/part.h> + +// KTTS includes. +#include "addtalker.h" +#include "kcmkttsmgrwidget.h" +#include "kspeech_stub.h" +#include "kspeechsink.h" + +class PlugInConf; +class KttsFilterConf; +class KListViewItem; +class KAboutData; +class KConfig; +class QPopupMenu; + +/** +* @author José Pablo Ezequiel "Pupeno" Fernández +* @author Gary Cramblitt +*/ + +class KCMKttsMgr : + public KCModule, + public KSpeech_stub, + virtual public KSpeechSink +{ + Q_OBJECT + + public: + KCMKttsMgr(QWidget *parent, const char *name, const QStringList &); + + ~KCMKttsMgr(); + + /** + * 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(); + + /** + * 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(); + + /** + * 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 is a static method which gets called to realize the modules settings + * durign the startup of KDE. NOTE that most modules do not implement this + * method, but modules like the keyboard and mouse modules, which directly + * interact with the X-server, need this method. As this method is static, + * it can avoid to create an instance of the user interface, which is often + * not needed in this case. + */ + static void init(); + + /** + * The control center calls this function to decide which buttons should + * be displayed. For example, it does not make sense to display an "Apply" + * button for one of the information modules. The value returned can be set by + * modules using setButtons. + */ + int buttons(); + + /** + * This function returns the small quickhelp. + * That is displayed in the sidebar in the KControl + */ + QString quickHelp() const; + + /** + * Return the about information for this module + */ + const KAboutData* aboutData() const; + + public slots: + /** + * This slot is used to emit the signal changed when any widget changes the configuration + */ + void configChanged() + { + if (!m_suppressConfigChanged) + { + // kdDebug() << "KCMKttsMgr::configChanged: Running"<< endl; + m_changed = true; + emit changed(true); + } + }; + /** + * This slot is called whenever user checks/unchecks item in Filters list. + */ + void slotFiltersList_stateChanged(); + + protected: + /** DCOP Methods connected to DCOP Signals emitted by KTTSD. */ + /** Most of these are not used */ + + /** + * This signal is emitted when KTTSD starts or restarts after a call to reinit. + */ + virtual void kttsdStarted(); + /** + * This signal is emitted just before KTTSD exits. + */ + virtual void kttsdExiting(); + + // virtual void resizeEvent( QResizeEvent ev ); + + private: + enum widgetPages + { + wpGeneral = 0, // General tab. + wpTalkers = 1, // Talkers tab. + wpNotify = 2, // Notify tab. + wpFilters = 3, // Filters tab. + wpInterruption = 4, // Interruption tab. + wpAudio = 5, // Audio tab. + wpJobs = 6 // Jobs tab. + }; + + enum NotifyListViewColumn + { + nlvcEventSrcName = 0, + nlvcEventName = 0, + nlvcActionName = 1, + nlvcTalkerName = 2, + nlvcEventSrc = 3, // hidden + nlvcEvent = 4, // hidden + nlvcAction = 5, // hidden + nlvcTalker = 6 // hidden + }; + + enum TalkerListViewColumn + { + tlvcTalkerID, + tlvcLanguage, + tlvcSynthName, + tlvcVoice, + tlvcGender, + tlvcVolume, + tlvcRate + }; + + enum FilterListViewColumn + { + flvcUserName, // Name of filter as set by user and displayed. + flvcFilterID, // Internal ID assigned to the filter (hidden). + flvcPlugInName, // Name of the filter plugin (from .desktop file, hidden). + flvcMultiInstance // True if multiple instances of this plugin are possible. (hidden) + }; + + enum SbdListViewColumn + { + slvcUserName, // Name of filter as set by user and displayed. + slvcFilterID, // Internal ID assigned to the filter (hidden). + slvcPlugInName, // Name of the filter plugin (from .desktop file, hidden). + slvcMultiInstance // True if multiple instances of this plugin are possible. (hidden) + }; + + enum SbdButtonIDs + { + sbdBtnEdit = 1, + sbdBtnUp = 2, + sbdBtnDown = 3, + sbdBtnAdd = 4, + sbdBtnRemove = 5 + }; + + /** + * Conversion functions for percent boxes to/from sliders. + */ + int percentToSlider(int percentValue); + int sliderToPercent(int sliderValue); + + /** + * Given a language code and plugin name, returns a normalized default talker code. + * @param languageCode Language code. + * @param plugInName Name of the plugin. + * @return Full normalized talker code. + * + * Example returned from defaultTalkerCode("en", "Festival") + * <voice lang="en" name="fixed" gender="neutral"/> + * <prosody volume="medium" rate="medium"/> + * <kttsd synthesizer="Festival" /> + */ + QString defaultTalkerCode(const QString &languageCode, const QString &plugInName); + + /** + * Given an item in the talker listview and a talker code, sets the columns of the item. + * @param talkerItem QListViewItem. + * @param talkerCode Talker Code. + */ + void updateTalkerItem(QListViewItem* talkerItem, const QString &talkerCode); + + /** + * Loads the configuration plugin for a named Talker plugin. + * @param name DesktopEntryName of the Synthesizer. + * @return Pointer to the configuration plugin for the Talker. + */ + PlugInConf* loadTalkerPlugin(const QString& name); + + /** + * Loads the configuration plugin for a named Filter plugin. + * @param plugInName DesktopEntryName of the plugin. + * @return Pointer to the configuration plugin for the Filter. + */ + KttsFilterConf* loadFilterPlugin(const QString& plugInName); + + /** + * Display the Talker Configuration Dialog. + */ + void configureTalker(); + + /** + * Display the Filter Configuration Dialog. + */ + void configureFilterItem( bool sbd ); + void configureFilter(); + + /** + * Add a filter. + */ + void addFilter( bool sbd ); + + /** + * Remove a filter. + */ + void removeFilter( bool sbd ); + + /** + * Move an item in a KListView up or down. + */ + void lowerItemPriority( KListView* lView ); + void higherItemPriority( KListView* lView ); + + /** + * Count number of configured Filters with the specified plugin name. + */ + int countFilterPlugins(const QString& filterPlugInName); + + /** + * Uses KTrader to convert a translated Filter Plugin Name to DesktopEntryName. + * @param name The translated plugin name. From Name= line in .desktop file. + * @return DesktopEntryName. The name of the .desktop file (less .desktop). + * QString::null if not found. + */ + QString FilterNameToDesktopEntryName(const QString& name); + + /** + * Uses KTrader to convert a DesktopEntryName into a translated Filter Plugin Name. + * @param desktopEntryName The DesktopEntryName. + * @return The translated Name of the plugin, from Name= line in .desktop file. + */ + QString FilterDesktopEntryNameToName(const QString& desktopEntryName); + + /** + * Loads notify events from a file. Clearing listview if clear is True. + */ + QString loadNotifyEventsFromFile( const QString& filename, bool clear); + + /** + * Saves notify events to a file. + */ + QString saveNotifyEventsToFile(const QString& filename); + + /** + * Adds an item to the notify listview. + * message is only needed if action = nactSpeakCustom. + */ + QListViewItem* addNotifyItem( + const QString& eventSrc, + const QString& event, + int action, + const QString& message, + TalkerCode& talkerCode); + + /** + * Main widget + */ + KCMKttsMgrWidget *m_kttsmgrw; + + /** + * Object holding all the configuration + */ + KConfig *m_config; + + /** + * KTTS Job Manager. + */ + KParts::ReadOnlyPart *m_jobMgrPart; + + /** + * Plugin configuration dialog. + */ + KDialogBase* m_configDlg; + + /** + * Sentence Boundary Detector button popup menu. + */ + QPopupMenu* m_sbdPopmenu; + + /** + * Talker(synth) Plugin currently loaded into configuration dialog. + */ + PlugInConf *m_loadedTalkerPlugIn; + + /** + * Filter Plugin currently loaded into configuration dialog. + */ + KttsFilterConf *m_loadedFilterPlugIn; + + /** + * Last talker ID. Used to generate a new ID. + */ + int m_lastTalkerID; + + /** + * Last filter ID. Used to generate a new ID. + */ + int m_lastFilterID; + + /** + * Last SBD filter ID. Used to generate to new ID. + */ + int m_lastSbdID; + + /** + * True if the configuration has been changed. + */ + bool m_changed; + + /** + * When True, suppresses emission of changed() signal. Used to suppress this + * signal while loading configuration. + */ + bool m_suppressConfigChanged; + + /** + * Dictionary mapping language names to codes. + */ + QMap<QString, QString> m_languagesToCodes; + + /** + * A QMap of languages codes indexed by synthesizer that supports them. + */ + SynthToLangMap m_synthToLangMap; + + /** + * Default Talker Code for notifications. + */ + QString m_defaultNotifyTalkerCode; + + private slots: + /** + * Add a talker/filter. + * This is a wrapper function that takes the parameters for the real talker from the + * widgets to later call it. + */ + void slot_addTalker(); + void slot_addNormalFilter(); + void slot_addSbdFilter(); + + /** + * Remove talker/filter. + * This is a wrapper function that takes the parameters for the real removeTalker from the + * widgets to later call it. + */ + void slot_removeTalker(); + void slot_removeNormalFilter(); + void slot_removeSbdFilter(); + + /** + * This slot is called whenever user clicks the higher*Priority button (up). + */ + void slot_higherTalkerPriority(); + void slot_higherNormalFilterPriority(); + void slot_higherSbdFilterPriority(); + + /** + * This slot is called whenever user clicks the lower*Priority button (down). + */ + void slot_lowerTalkerPriority(); + void slot_lowerNormalFilterPriority(); + void slot_lowerSbdFilterPriority(); + + /** + * Update the status of the Talker/Filter buttons. + */ + void updateTalkerButtons(); + void updateFilterButtons(); + void updateSbdButtons(); + + /** + * This signal is emitted whenever user checks/unchecks the Enable TTS System check box. + */ + void enableKttsdToggled(bool checked); + + /** + * This signal is emitted whenever user checks/unchecks the GStreamer radio button. + */ + void slotGstreamerRadioButton_toggled(bool state); + + /** + * This signal is emitted whenever user checks/unchecks the ALSA radio button. + */ + void slotAlsaRadioButton_toggled(bool state); + + /** + * This is emitted whenever user activates the ALSA pcm combobox. + */ + void slotPcmComboBox_activated(); + + /** + * This signal is emitted whenever user checks/unchecks the aKode radio button. + */ + void slotAkodeRadioButton_toggled(bool state); + + /** + * User has requested to display the Talker/Filter Configuration Dialog. + */ + void slot_configureTalker(); + void slot_configureNormalFilter(); + void slot_configureSbdFilter(); + + /** + * Slots for the Talker/Filter Configuration dialogs. + */ + void slotConfigTalkerDlg_ConfigChanged(); + void slotConfigFilterDlg_ConfigChanged(); + void slotConfigTalkerDlg_DefaultClicked(); + void slotConfigFilterDlg_DefaultClicked(); + void slotConfigTalkerDlg_CancelClicked(); + void slotConfigFilterDlg_CancelClicked(); + + /** + * Slots for Speed setting. + */ + void timeBox_valueChanged(int percentValue); + void timeSlider_valueChanged(int sliderValue); + + /** + * Keep Audio CheckBox slot. + */ + void keepAudioCheckBox_toggled(bool checked); + + /** + * Notify tab slots. + */ + void slotNotifyEnableCheckBox_toggled(bool checked); + void slotNotifyAddButton_clicked(); + void slotNotifyRemoveButton_clicked(); + void slotNotifyClearButton_clicked(); + void slotNotifyLoadButton_clicked(); + void slotNotifySaveButton_clicked(); + void slotNotifyListView_selectionChanged(); + void slotNotifyPresentComboBox_activated(int index); + void slotNotifyActionComboBox_activated(int index); + void slotNotifyTestButton_clicked(); + void slotNotifyMsgLineEdit_textChanged(const QString& text); + void slotNotifyTalkerButton_clicked(); + + /** + * Other slots. + */ + void slotTabChanged(); +}; + +/// This is a small helper class to detect when user checks/unchecks a Filter in Filters tab +/// and emit changed() signal. +class KttsCheckListItem : public QCheckListItem +{ + public: + KttsCheckListItem( QListView *parent, + const QString &text, Type tt = RadioButtonController, + KCMKttsMgr* kcmkttsmgr = 0); + KttsCheckListItem( QListView *parent, QListViewItem *after, + const QString &text, Type tt = RadioButtonController, + KCMKttsMgr* kcmkttsmgr = 0); + + protected: + virtual void stateChange(bool); + + private: + KCMKttsMgr* m_kcmkttsmgr; +}; + +#endif diff --git a/kttsd/kcmkttsmgr/kcmkttsmgrwidget.ui b/kttsd/kcmkttsmgr/kcmkttsmgrwidget.ui new file mode 100644 index 0000000..36942a3 --- /dev/null +++ b/kttsd/kcmkttsmgr/kcmkttsmgrwidget.ui @@ -0,0 +1,1928 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>KCMKttsMgrWidget</class> +<author>Gary Cramblitt</author> +<widget class="KCModule"> + <property name="name"> + <cstring>KCMKttsMgrWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>638</width> + <height>382</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QTabWidget" row="0" column="0"> + <property name="name"> + <cstring>mainTab</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>tab</cstring> + </property> + <attribute name="title"> + <string>&General</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>enableKttsdCheckBox</cstring> + </property> + <property name="text"> + <string>&Enable Text-to-Speech System (KTTSD)</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Check to start the KTTS Deamon and enable Text-to-Speech.</string> + </property> + </widget> + <widget class="QCheckBox" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>embedInSysTrayCheckBox</cstring> + </property> + <property name="text"> + <string>Always em&bed Text-to-Speech Manager in system tray</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, KTTSMgr displays an icon in the system tray, and clicking OK or Cancel buttons does not stop KTTSMgr. Use system tray context menu to quit KTTSMgr. This setting takes effect when KTTSMgr is next started. This setting has no effect when running in the KDE Control Center.</string> + </property> + </widget> + <spacer row="6" column="1"> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLayoutWidget" row="3" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer5_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>showMainWindowOnStartupCheckBox</cstring> + </property> + <property name="text"> + <string>Show &main window on startup</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, the KTTSMgr window is displayed when KTTSMgr starts. When unchecked, click on the icon in the system tray to display the KTTSMgr window.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="5" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout5_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer5_2_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>autoexitMgrCheckBox</cstring> + </property> + <property name="text"> + <string>E&xit when speaking is finished</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, and KTTSMgr was automatically started when speech began, automatically exits when speech has finished. Does not automatically exit if KTTSMgr was started manually or started from the Control Center.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox" row="4" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>autostartMgrCheckBox</cstring> + </property> + <property name="text"> + <string>Sta&rt minimized in system tray when speaking</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, if KTTSMgr is not already running and speech begins, starts KTTSMgr and displays an icon in the system tray. <em>Note<em>: KTTSMgr only automatically starts for text jobs having 5 sentences or more.</string> + </property> + </widget> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>&Talkers</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KListView" row="0" column="0"> + <column> + <property name="text"> + <string>ID</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Language</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Speech Synthesizer</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Voice</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Gender</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Volume</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Rate</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>talkersList</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This is a list of all the configured Talkers. A Talker is a speech synthesizer that has been configured with a language, voice, gender, speaking rate, and volume. Talkers higher in the list have higher priority. The topmost Talker will be used when no talker attributes have been specified by an application.</string> + </property> + </widget> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>addTalkerButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add...</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to add and configure a new Talker (speech synthesizer).</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>higherTalkerPriorityButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>U&p</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>lowerTalkerPriorityButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Do&wn</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>configureTalkerButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Edit...</string> + </property> + <property name="toggleButton"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to configure options for the highlighted Talker.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>removeTalkerButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Remove</string> + </property> + <property name="toggleButton"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to remove the highlighted Talker.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer11</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>&Notifications</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox" row="1" column="0"> + <property name="name"> + <cstring>notifyGroup</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="KListView" row="1" column="0"> + <column> + <property name="text"> + <string>Application/Event</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Action</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Talker</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>notifyListView</cstring> + </property> + <property name="rootIsDecorated"> + <bool>true</bool> + </property> + <property name="toolTip" stdset="0"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is a list of configured application events and actions to be taken when received. The "default" event governs all events not specifically configured.</string> + </property> + </widget> + <widget class="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout21</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>notifyPresentLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Notifications to speak:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>notifyWhatComboBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Applies only to the default event. Does not affect application-specific events. Only events that display in the manner which you select will be spoken.</string> + </property> + </widget> + <widget class="QComboBox"> + <property name="name"> + <cstring>notifyPresentComboBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Applies only to the default event. Does not affect application-specific events. Only events that display in the manner which you select will be spoken.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout28</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>notifyAddButton</cstring> + </property> + <property name="text"> + <string>Add...</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to configure notification for a specific application event.</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>notifyRemoveButton</cstring> + </property> + <property name="text"> + <string>Re&move</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to remove a specific notification event from the list. You cannot remove the default event.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer16</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>152</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>notifyClearButton</cstring> + </property> + <property name="text"> + <string>Cl&ear</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Removes all the application specific events. The default event remains.</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>notifyLoadButton</cstring> + </property> + <property name="text"> + <string>&Load...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to read configured notification events from a file.</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>notifySaveButton</cstring> + </property> + <property name="text"> + <string>&Save...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to write all the configured application events to a file.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="3" column="0"> + <property name="name"> + <cstring>notifyExceptionActionLayout</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton" row="0" column="2"> + <property name="name"> + <cstring>notifyTestButton</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>Click to test notification</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click this button to test the notification. A sample message will be spoken. Note: The Text-to-Speech system must be enabled.</string> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>notifyActionLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Ac&tion:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>notifyActionComboBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Specifies how KTTS should speak the event when received. If you select "Speak custom text", enter the text in the box. You may use the following substitution strings in the text:<dl><dt>%e</dt><dd>Name of the event</dd><dt>%a</dt><dd>Application that sent the event</dd><dt>%m</dt><dd>The message sent by the application</dd></dl></qt></string> + </property> + </widget> + <widget class="QLineEdit" row="0" column="3"> + <property name="name"> + <cstring>notifyMsgLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Specifies how KTTS should speak the event when received. If you select "Speak custom text", enter the text in the box. You may use the following substitution strings in the text:<dl><dt>%e</dt><dd>Name of the event</dd><dt>%a</dt><dd>Application that sent the event</dd><dt>%m</dt><dd>The message sent by the application</dd></dl></qt></string> + </property> + </widget> + <widget class="QComboBox" row="0" column="1"> + <property name="name"> + <cstring>notifyActionComboBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Specifies how KTTS should speak the event when received. If you select "Speak custom text", enter the text in the box. You may use the following substitution strings in the text:<dl><dt>%e</dt><dd>Name of the event</dd><dt>%a</dt><dd>Application that sent the event</dd><dt>%m</dt><dd>The message sent by the application</dd></dl></qt></string> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="4" column="0"> + <property name="name"> + <cstring>layout19_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>notifyTalkerLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Talke&r:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>notifyTalkerLineEdit</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The Talker that will speak the notification. The "default" Talker is the topmost talker listed on the Talkers tab.</string> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>notifyTalkerLineEdit</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="paletteForegroundColor"> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="whatsThis" stdset="0"> + <string>The Talker that will speak the notification. The "default" Talker is the topmost talker listed on the Talkers tab.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>notifyTalkerButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select the Talker to speak the notification.</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout17</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>notifyEnableCheckBox</cstring> + </property> + <property name="text"> + <string>Speak notifications (&KNotify)</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked and KTTS is enabled, notification events from applications sent via KNotify will be spoken according to the options you set on this tab.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer12</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>notifyExcludeEventsWithSoundCheckBox</cstring> + </property> + <property name="text"> + <string>E&xclude notifications with a sound</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, notification events that have a sound will not be spoken.</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>&Filters</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KListView"> + <column> + <property name="text"> + <string>Filter</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>filtersList</cstring> + </property> + <property name="fullWidth"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>This is a list of all the configured Filters. Filters higher in the list are applied first. Filters modify text before it is spoken. They can be used to substitute for mispronounced words, transform XML from one form to another, or change the default Talker to be used for speech output.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout9_2</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>addFilterButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add...</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to add and configure a new Filter.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>higherFilterPriorityButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>U&p</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to move selected filter up in the list. Filters higher in the list are applied first.</string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>lowerFilterPriorityButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Do&wn</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to move a filter down in the list. Filters lower in the list are applied last.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>configureFilterButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Edit...</string> + </property> + <property name="toggleButton"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to configure options for the highlighted Filter.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>removeFilterButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Remove</string> + </property> + <property name="toggleButton"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to remove the highlighted Filter.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer11_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout18</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KListView"> + <column> + <property name="text"> + <string>Sentence Boundary Detector</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>sbdsList</cstring> + </property> + <property name="fullWidth"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>This is a list of all the configured Sentence Boundary Detector (SBD) Filters. SBDs break long text jobs up into sentences, which reduces the time before a job begins speaking, and permits you to advance or rewind through a job. SBDs are applied in the order listed (top to bottom) after all the normal filters at the top of this screen have been applied. Filtering stops when the first SBD modifies the text.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout16</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>sbdButton</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Co&nfigure</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click this button to edit the Sentence Boundary Detector (SBD) configuration or add additional SBD filters.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer11_2_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>11</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> + </widget> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>&Interruption</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout15</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KURLRequester" row="1" column="1"> + <property name="name"> + <cstring>textPreSnd</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Pre-sound box and choose a Pre-sound audio file, which will sound when a text job is interrupted by another message.</string> + </property> + </widget> + <widget class="KLineEdit" row="2" column="1"> + <property name="name"> + <cstring>textPostMsg</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Post-message box and enter a Post-message, which will be spoken when a text job resumes after being interrupted by another message.</string> + <comment>What's this text</comment> + </property> + </widget> + <widget class="QCheckBox" row="2" column="0"> + <property name="name"> + <cstring>textPostMsgCheck</cstring> + </property> + <property name="text"> + <string>Post-&message:</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Post-message box and enter a Post-message, which will be spoken when a text job resumes after being interrupted by another message.</string> + </property> + </widget> + <widget class="QCheckBox" row="1" column="0"> + <property name="name"> + <cstring>textPreSndCheck</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Pre-sou&nd:</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Pre-sound box and choose a Pre-sound audio file, which will sound when a text job is interrupted by another message.</string> + </property> + </widget> + <widget class="QCheckBox" row="0" column="0"> + <property name="name"> + <cstring>textPreMsgCheck</cstring> + </property> + <property name="text"> + <string>&Pre-message:</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="tristate"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Pre-message box and enter a Pre-message, which will be spoken whenever a text job is interrupted by another message.</string> + </property> + </widget> + <widget class="KURLRequester" row="3" column="1"> + <property name="name"> + <cstring>textPostSnd</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Post-sound and choose a Post-sound audio file, which will sound before a text job resumes after being interrupted by another message.</string> + </property> + </widget> + <widget class="QCheckBox" row="3" column="0"> + <property name="name"> + <cstring>textPostSndCheck</cstring> + </property> + <property name="text"> + <string>Post-s&ound:</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Post-sound and choose a Post-sound audio file, which will sound before a text job resumes after being interrupted by another message.</string> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1"> + <property name="name"> + <cstring>textPreMsg</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Check the Pre-message box and enter a Pre-message, which will be spoken whenever a text job is interrupted by another message.</string> + <comment>What's this text</comment> + </property> + </widget> + </grid> + </widget> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer4</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>180</height> + </size> + </property> + </spacer> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>A&udio</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>keepAudioCheckBox</cstring> + </property> + <property name="text"> + <string>&Keep audio files:</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Check this if you want to keep the generated audio (wav) files. You will find them in the indicated directory.</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>keepAudioPath</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>Specify the directory in which the audio files will be copied.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast". You cannot change the speed of MultiSyn voices.</string> + </property> + </widget> + <widget class="KIntSpinBox"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast". You cannot change the speed of MultiSyn voices.</string> + </property> + </widget> + <widget class="QSlider"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast". You cannot change the speed of MultiSyn voices.</string> + </property> + </widget> + </hbox> + </widget> + <spacer row="3" column="0"> + <property name="name"> + <cstring>spacer8</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + <spacer row="0" column="1"> + <property name="name"> + <cstring>spacer9</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>90</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QButtonGroup" row="0" column="0"> + <property name="name"> + <cstring>audioButtonGroup</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Out&put Using</string> + </property> + <property name="whatsThis" stdset="0"> + <string><p>Select the audio output method desired. If you select <b>GStreamer</b>, you must also select a <b>Sink</b>.</p><p><em>Note</em>: You must have GStreamer >= 0.87 to use GStreamer.</p></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="1" column="1"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>sinkLabel</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Sink:</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the sound sink to be used for GStreamer output.</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>sinkComboBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </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>Select the sound sink to be used for GStreamer output.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="3" column="1"> + <property name="name"> + <cstring>layout8_2_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>akodeSinkLabel</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Sink:</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the sound sink to be used for aKode output.</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>akodeComboBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </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>Select the sink to be used for aKode output. Select "auto" to let aKode pick the best output method.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QRadioButton" row="1" column="0"> + <property name="name"> + <cstring>gstreamerRadioButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>GStrea&mer</string> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + <property name="whatsThis" stdset="0"> + <string><p>Check to use the GStreamer audio output system. You must also select a <b>Sink</b> plugin.</p></string> + </property> + </widget> + <widget class="QRadioButton" row="0" column="0"> + <property name="name"> + <cstring>artsRadioButton</cstring> + </property> + <property name="text"> + <string>a&Rts</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Check to use the KDE aRts system for audio output.</string> + </property> + </widget> + <widget class="QRadioButton" row="3" column="0"> + <property name="name"> + <cstring>akodeRadioButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>aKode</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="buttonGroupId"> + <number>0</number> + </property> + <property name="whatsThis" stdset="0"> + <string><p>Check to use aKode for audio output. You must also select a <b>Sink</b>.</p></string> + </property> + </widget> + <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout21</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>alsaRadioButton</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>ALSA</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="buttonGroupId"> + <number>-1</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Check to use Advanced Linux Sound Architecture (ALSA) for audio output.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout8_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>pcmLabel</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Device:</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Select the sound sink to be used for GStreamer output.</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>pcmComboBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </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>Select the PCM device to be used for ALSA output. Select "default" to use the default ALSA device.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>pcmCustom</cstring> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + </grid> + </widget> + </widget> + </grid> +</widget> +<connections> + <connection> + <sender>enableKttsdCheckBox</sender> + <signal>clicked()</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>embedInSysTrayCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>showMainWindowOnStartupCheckBox</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>embedInSysTrayCheckBox</sender> + <signal>clicked()</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>showMainWindowOnStartupCheckBox</sender> + <signal>clicked()</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>artsRadioButton</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>gstreamerRadioButton</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>sinkComboBox</sender> + <signal>activated(int)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>autostartMgrCheckBox</sender> + <signal>toggled(bool)</signal> + <receiver>autoexitMgrCheckBox</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>autostartMgrCheckBox</sender> + <signal>clicked()</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>autoexitMgrCheckBox</sender> + <signal>clicked()</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>pcmComboBox</sender> + <signal>activated(int)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>alsaRadioButton</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>akodeRadioButton</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>akodeComboBox</sender> + <signal>activated(int)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPreMsg</sender> + <signal>textChanged(const QString&)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPreMsgCheck</sender> + <signal>toggled(bool)</signal> + <receiver>textPreMsg</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>textPreMsgCheck</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPreSndCheck</sender> + <signal>toggled(bool)</signal> + <receiver>textPreSnd</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>textPreSndCheck</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPreSnd</sender> + <signal>textChanged(const QString&)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPostMsgCheck</sender> + <signal>toggled(bool)</signal> + <receiver>textPostMsg</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>textPostMsgCheck</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPostMsg</sender> + <signal>textChanged(const QString&)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPostSndCheck</sender> + <signal>toggled(bool)</signal> + <receiver>textPostSnd</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>textPostSndCheck</sender> + <signal>toggled(bool)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>textPostSnd</sender> + <signal>textChanged(const QString&)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> + <connection> + <sender>pcmCustom</sender> + <signal>textChanged(const QString&)</signal> + <receiver>KCMKttsMgrWidget</receiver> + <slot>slotConfigChanged()</slot> + </connection> +</connections> +<tabstops> + <tabstop>mainTab</tabstop> + <tabstop>enableKttsdCheckBox</tabstop> + <tabstop>embedInSysTrayCheckBox</tabstop> + <tabstop>showMainWindowOnStartupCheckBox</tabstop> + <tabstop>autostartMgrCheckBox</tabstop> + <tabstop>autoexitMgrCheckBox</tabstop> + <tabstop>talkersList</tabstop> + <tabstop>addTalkerButton</tabstop> + <tabstop>higherTalkerPriorityButton</tabstop> + <tabstop>lowerTalkerPriorityButton</tabstop> + <tabstop>configureTalkerButton</tabstop> + <tabstop>removeTalkerButton</tabstop> + <tabstop>notifyEnableCheckBox</tabstop> + <tabstop>notifyExcludeEventsWithSoundCheckBox</tabstop> + <tabstop>notifyAddButton</tabstop> + <tabstop>notifyRemoveButton</tabstop> + <tabstop>notifyClearButton</tabstop> + <tabstop>notifyLoadButton</tabstop> + <tabstop>notifySaveButton</tabstop> + <tabstop>notifyListView</tabstop> + <tabstop>notifyPresentComboBox</tabstop> + <tabstop>notifyActionComboBox</tabstop> + <tabstop>notifyTestButton</tabstop> + <tabstop>notifyMsgLineEdit</tabstop> + <tabstop>notifyTalkerLineEdit</tabstop> + <tabstop>notifyTalkerButton</tabstop> + <tabstop>filtersList</tabstop> + <tabstop>addFilterButton</tabstop> + <tabstop>higherFilterPriorityButton</tabstop> + <tabstop>lowerFilterPriorityButton</tabstop> + <tabstop>configureFilterButton</tabstop> + <tabstop>removeFilterButton</tabstop> + <tabstop>sbdsList</tabstop> + <tabstop>sbdButton</tabstop> + <tabstop>textPreMsgCheck</tabstop> + <tabstop>textPreMsg</tabstop> + <tabstop>textPreSndCheck</tabstop> + <tabstop>textPreSnd</tabstop> + <tabstop>textPostMsgCheck</tabstop> + <tabstop>textPostMsg</tabstop> + <tabstop>textPostSndCheck</tabstop> + <tabstop>textPostSnd</tabstop> + <tabstop>artsRadioButton</tabstop> + <tabstop>sinkComboBox</tabstop> + <tabstop>pcmComboBox</tabstop> + <tabstop>akodeComboBox</tabstop> + <tabstop>timeBox</tabstop> + <tabstop>keepAudioCheckBox</tabstop> + <tabstop>keepAudioPath</tabstop> +</tabstops> +<includes> + <include location="global" impldecl="in declaration">klocale.h</include> + <include location="global" impldecl="in implementation">klocale.h</include> + <include location="global" impldecl="in implementation">kdebug.h</include> +</includes> +<signals> + <signal>configChanged()</signal> +</signals> +<slots> + <slot access="private">slotConfigChanged()</slot> +</slots> +<layoutdefaults spacing="6" margin="6"/> +<includehints> + <includehint>kcmodule.h</includehint> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistview.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistview.h</includehint> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> +</includehints> +</UI> diff --git a/kttsd/kcmkttsmgr/kcmkttsmgrwidget.ui.h b/kttsd/kcmkttsmgr/kcmkttsmgrwidget.ui.h new file mode 100644 index 0000000..206524a --- /dev/null +++ b/kttsd/kcmkttsmgr/kcmkttsmgrwidget.ui.h @@ -0,0 +1,16 @@ +/**************************************************************************** +** 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 KCMKttsMgrWidget::slotConfigChanged(){ + // kdDebug() << "Running: KCMKttsMgrWidget:slotConfigChanged():" << endl; + emit configChanged(); +} + + + diff --git a/kttsd/kcmkttsmgr/selectevent.cpp b/kttsd/kcmkttsmgr/selectevent.cpp new file mode 100644 index 0000000..dc65fa4 --- /dev/null +++ b/kttsd/kcmkttsmgr/selectevent.cpp @@ -0,0 +1,149 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Dialog to allow user to select a KNotify application and event. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qcombobox.h> + +// KDE includes +#include <kstandarddirs.h> +#include <kconfig.h> +#include <klocale.h> +#include <kdebug.h> +#include <klistview.h> +#include <kiconloader.h> + +// KTTS includes +#include "utils.h" +#include "selectevent.h" + +SelectEvent::SelectEvent(QWidget* parent, const char* name, WFlags fl, const QString& initEventSrc) + : SelectEventWidget(parent,name,fl) +{ + // Load list of event sources (applications). + QStringList fullpaths = + KGlobal::dirs()->findAllResources("data", "*/eventsrc", false, true ); + QStringList::ConstIterator it = fullpaths.begin(); + QStringList relativePaths; + for ( ; it != fullpaths.end(); ++it) + { + QString relativePath = *it; + if ( relativePath.at(0) == '/' && KStandardDirs::exists( relativePath ) ) + { + relativePath = makeRelative( relativePath ); + relativePaths.append(relativePath); + } + } + relativePaths.sort(); + it = relativePaths.begin(); + for ( ; it != relativePaths.end(); ++it) + { + QString relativePath = *it; + if ( !relativePath.isEmpty() ) + { + KConfig* config = new KConfig(relativePath, true, false, "data"); + config->setGroup( QString::fromLatin1("!Global!") ); + QString icon = config->readEntry(QString::fromLatin1("IconName"), + QString::fromLatin1("misc")); + QString description = config->readEntry( QString::fromLatin1("Comment"), + i18n("No description available") ); + delete config; + int index = relativePath.find( '/' ); + QString appname; + if ( index >= 0 ) + appname = relativePath.left( index ); + else + kdDebug() << "Cannot determine application name from path: " << relativePath << endl; + eventSrcComboBox->insertItem( SmallIcon( icon ), description ); + m_eventSrcNames.append( appname ); + if ( appname == initEventSrc ) KttsUtils::setCbItemFromText(eventSrcComboBox, description); + } + } + slotEventSrcComboBox_activated(eventSrcComboBox->currentItem()); + connect (eventSrcComboBox, SIGNAL(activated(int)), this, SLOT(slotEventSrcComboBox_activated(int))); +} + +SelectEvent::~SelectEvent() { } + +void SelectEvent::slotEventSrcComboBox_activated(int index) +{ + eventsListView->clear(); + QListViewItem* item = 0; + QString eventSrc = m_eventSrcNames[index]; + QString configFilename = eventSrc + QString::fromLatin1( "/eventsrc" ); + KConfig* config = new KConfig( configFilename, true, false, "data" ); + QStringList eventNames = config->groupList(); + uint eventNamesCount = eventNames.count(); + for (uint ndx = 0; ndx < eventNamesCount; ++ndx) + { + QString eventName = eventNames[ndx]; + if ( eventName != "!Global!" ) + { + config->setGroup( eventName ); + QString eventDesc = config->readEntry( QString::fromLatin1( "Comment" ), + config->readEntry( QString::fromLatin1( "Name" ))); + if ( !item ) + item = new KListViewItem( eventsListView, eventDesc, eventName ); + else + item = new KListViewItem( eventsListView, item, eventDesc, eventName ); + } + } + delete config; + eventsListView->sort(); + item = eventsListView->lastChild(); + QString eventDesc = i18n("All other %1 events").arg(eventSrcComboBox->currentText()); + if ( !item ) + item = new KListViewItem( eventsListView, eventDesc, "default" ); + else + item = new KListViewItem( eventsListView, item, eventDesc, "default" ); + +} + +QString SelectEvent::getEventSrc() +{ + return m_eventSrcNames[eventSrcComboBox->currentItem()]; +} + +QString SelectEvent::getEvent() +{ + QListViewItem* item = eventsListView->currentItem(); + if ( item ) + return item->text(1); + else + return QString::null; +} + +// returns e.g. "kwin/eventsrc" from a given path +// "/opt/kde3/share/apps/kwin/eventsrc" +QString SelectEvent::makeRelative( const QString& fullPath ) +{ + int slash = fullPath.findRev( '/' ) - 1; + slash = fullPath.findRev( '/', slash ); + + if ( slash < 0 ) + return QString::null; + + return fullPath.mid( slash+1 ); +} + + +#include "selectevent.moc" diff --git a/kttsd/kcmkttsmgr/selectevent.h b/kttsd/kcmkttsmgr/selectevent.h new file mode 100644 index 0000000..1ca4e21 --- /dev/null +++ b/kttsd/kcmkttsmgr/selectevent.h @@ -0,0 +1,69 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Dialog to allow user to select a KNotify application and event. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _SELECTEVENT_H_ +#define _SELECTEVENT_H_ + +#include "selecteventwidget.h" + +class SelectEvent : public SelectEventWidget +{ + Q_OBJECT + +public: + /** + * Constructor. + * @param parent Inherited KDialog parameter. + * @param name Inherited KDialog parameter. + * @param initEventSrc Event source to start with. + */ + SelectEvent(QWidget* parent = 0, const char* name = 0, WFlags fl = 0, + const QString& initEventSrc = QString::null ); + + /** + * Destructor. + */ + ~SelectEvent(); + + /** + * Returns the chosen event source (app name). + */ + QString getEventSrc(); + + /** + * Returns the chosen event. + */ + QString getEvent(); + +private slots: + void slotEventSrcComboBox_activated(int index); + +private: + // returns e.g. "kwin/eventsrc" from a given path + // "/opt/kde3/share/apps/kwin/eventsrc" + QString makeRelative( const QString& fullPath ); + + QStringList m_eventSrcNames; +}; + +#endif // _SELECTEVENT_H_ diff --git a/kttsd/kcmkttsmgr/selecteventwidget.ui b/kttsd/kcmkttsmgr/selecteventwidget.ui new file mode 100644 index 0000000..13b91f1 --- /dev/null +++ b/kttsd/kcmkttsmgr/selecteventwidget.ui @@ -0,0 +1,62 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>SelectEventWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>SelectEventWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>472</width> + <height>326</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>eventSrcLabel</cstring> + </property> + <property name="text"> + <string>Event source:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>eventSrcComboBox</cstring> + </property> + </widget> + <widget class="QComboBox" row="0" column="1"> + <property name="name"> + <cstring>eventSrcComboBox</cstring> + </property> + </widget> + <widget class="KListView" row="1" column="0" rowspan="1" colspan="2"> + <column> + <property name="text"> + <string>Events</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>eventsListView</cstring> + </property> + <property name="fullWidth"> + <bool>true</bool> + </property> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klistview.h</includehint> +</includehints> +</UI> diff --git a/kttsd/kttsd.lsm b/kttsd/kttsd.lsm new file mode 100644 index 0000000..8c5679c --- /dev/null +++ b/kttsd/kttsd.lsm @@ -0,0 +1,27 @@ +Begin4 +Title: KTTS - the KDE Text-to-Speech system +Version: 0.3.0 +Entered-date: 2005/02/26 +Description: KTTS -- KDE Text-to-Speech -- is a subsystem within the KDE + desktop for conversion of text to audible speech. KTTS is + currently under development and aims to become the standard + subsystem for all KDE applications to provide speech output. +Keywords: KDE accessibility kdeaccessibility TTS Text-to-Speech KTTSD + kttsmgr festival flite hadifix MBROLA freetts epos SSML +Author: pupeno@pupeno.com (José Pablo Ezequiel Fernández) + garycramblitt@comcast.net (Gary Cramblitt) + gunnar@schmi-dt.de (Gunnar Schmi Dt) + ojschmidt@kde.org (Olaf Schmidt) + ceruleanblaze@gmail.com (Paul Giannaros) +Maintained-by: garycramblitt@comcast.net (Gary Cramblitt) +Home-page: http://accessibility.kde.org/developer/kttsd/ +Primary-site: ftp://ftp.kde.org/pub/kde/unstable/apps/KDE3.x/ + 1.7K kttsd.lsm + 3.5M kttsd-0.3.0.src.tar.bz2 + 4M kttsd_0.3.0-1_i386.deb + 28K kttsd-gstreamer_0.3.0-1_i386.deb +Alternate-site: +Original-site: +Platforms: KDE 3.2 or greater +Copying-policy: GPL +End diff --git a/kttsd/kttsd/Makefile.am b/kttsd/kttsd/Makefile.am new file mode 100644 index 0000000..a553636 --- /dev/null +++ b/kttsd/kttsd/Makefile.am @@ -0,0 +1,51 @@ +# Include paths. INCLUDES is maintained by KDevelop, AM_CPPFLAGS is the preferred variable, +# so keep them synchronized. +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd \ + -I$(kde_includes)/arts \ + $(KTTS_KSPEECH_INCLUDE) \ + $(all_includes) + +# Let am_edit/unsermake handle all of the metasource files (moc). +METASOURCES = AUTO + +######################################################################### +# APPLICATION SECTION +######################################################################### +# This is the program that gets installed. It's name is used for all +# of the other Makefile.am variables. +bin_PROGRAMS = kttsd + +kspeech_DIR = $(KTTS_KSPEECH_DIR) +kspeechsink_DIR = $(KTTS_KSPEECH_DIR) + +# The source, library search path, and link libraries. +# Note: .skel files cause DCOPIDL compiler to generate _skel.cpp file and compile it. +kttsd_SOURCES = \ + kspeech.skel kspeechsink.stub\ + main.cpp \ + kttsd.cpp \ + speaker.cpp \ + speechdata.cpp \ + kttsd.skel \ + threadedplugin.cpp \ + ssmlconvert.cpp \ + filtermgr.cpp \ + talkermgr.cpp + +kttsd_LDFLAGS = -avoid-version -module $(all_libraries) $(KDE_RPATH) +kttsd_LDADD = \ + $(top_builddir)/kttsd/libkttsd/libkttsd.la \ + $(LIB_KDECORE) \ + $(LIB_KIO) \ + $(LIB_KUTILS) + +# Install desktop file to standard services directory. +kde_services_DATA = kttsd.desktop + +# Install data. +kttsddatadir = $(kde_datadir)/kttsd/xslt/ +kttsddata_DATA = SSMLtoPlainText.xsl + +noinst_HEADERS = threadedplugin.h ssmlconvert.h + diff --git a/kttsd/kttsd/SSMLtoPlainText.xsl b/kttsd/kttsd/SSMLtoPlainText.xsl new file mode 100644 index 0000000..c1c087d --- /dev/null +++ b/kttsd/kttsd/SSMLtoPlainText.xsl @@ -0,0 +1,9 @@ +<?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"/> + +<xsl:template match="speak"> +<xsl:value-of select="."/> +</xsl:template> + +</xsl:stylesheet> diff --git a/kttsd/kttsd/filtermgr.cpp b/kttsd/kttsd/filtermgr.cpp new file mode 100644 index 0000000..3b0474b --- /dev/null +++ b/kttsd/kttsd/filtermgr.cpp @@ -0,0 +1,405 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + Filters text, applying each configured Filter in turn. + Runs asynchronously, emitting Finished() signal when all Filters have run. + + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> +#include <ktrader.h> +#include <kparts/componentfactory.h> +#include <klocale.h> + +// FilterMgr includes. +#include "filtermgr.h" +#include "filtermgr.moc" + +/** + * Constructor. + */ +FilterMgr::FilterMgr( QObject *parent, const char *name) : + KttsFilterProc(parent, name) +{ + // kdDebug() << "FilterMgr::FilterMgr: Running" << endl; + m_state = fsIdle; + m_noSBD = false; + m_supportsHTML = false; + m_talkerCode = 0; +} + +/** + * Destructor. + */ +FilterMgr::~FilterMgr() +{ + // kdDebug() << "FilterMgr::~FilterMgr: Running" << endl; + if ( m_state == fsFiltering ) + stopFiltering(); + m_filterList.setAutoDelete( TRUE ); + m_filterList.clear(); +} + +/** + * Loads and initializes the filters. + * @param config Settings object. + * @return False if FilterMgr is not ready to filter. + */ +bool FilterMgr::init(KConfig *config, const QString& /*configGroup*/) +{ + // Load each of the filters and initialize. + config->setGroup("General"); + QStringList filterIDsList = config->readListEntry("FilterIDs", ','); + // kdDebug() << "FilterMgr::init: FilterIDs = " << filterIDsList << endl; + // If no filters have been configured, automatically configure the standard SBD. + if (filterIDsList.isEmpty()) + { + config->setGroup("Filter_1"); + config->writeEntry("DesktopEntryName", "kttsd_sbdplugin"); + config->writeEntry("Enabled", true); + config->writeEntry("IsSBD", true); + config->writeEntry("MultiInstance", true); + config->writeEntry("SentenceBoundary", "\\1\\t"); + config->writeEntry("SentenceDelimiterRegExp", "([\\.\\?\\!\\:\\;])(\\s|$|(\\n *\\n))"); + config->writeEntry("UserFilterName", i18n("Standard Sentence Boundary Detector")); + config->setGroup("General"); + config->writeEntry("FilterIDs", "1"); + filterIDsList = config->readListEntry("FilterIDs", ','); + } + if ( !filterIDsList.isEmpty() ) + { + QStringList::ConstIterator itEnd = filterIDsList.constEnd(); + for (QStringList::ConstIterator it = filterIDsList.constBegin(); it != itEnd; ++it) + { + QString filterID = *it; + QString groupName = "Filter_" + filterID; + config->setGroup( groupName ); + QString desktopEntryName = config->readEntry( "DesktopEntryName" ); + // If a DesktopEntryName is not in the config file, it was configured before + // we started using them, when we stored translated plugin names instead. + // Try to convert the translated plugin name to a DesktopEntryName. + // DesktopEntryNames are better because user can change their desktop language + // and DesktopEntryName won't change. + if (desktopEntryName.isEmpty()) + { + QString filterPlugInName = config->readEntry("PlugInName", QString::null); + // See if the translated name will untranslate. If not, well, sorry. + desktopEntryName = FilterNameToDesktopEntryName(filterPlugInName); + // Record the DesktopEntryName from now on. + if (!desktopEntryName.isEmpty()) config->writeEntry("DesktopEntryName", desktopEntryName); + } + if (config->readBoolEntry("Enabled") || config->readBoolEntry("IsSBD")) + { + // kdDebug() << "FilterMgr::init: filterID = " << filterID << endl; + KttsFilterProc* filterProc = loadFilterPlugin( desktopEntryName ); + if ( filterProc ) + { + filterProc->init( config, groupName ); + m_filterList.append( filterProc ); + } + if (config->readEntry("DocType").contains("html") || + config->readEntry("RootElement").contains("html")) + m_supportsHTML = true; + } + } + } + return true; +} + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ +/*virtual*/ bool FilterMgr::isSBD() { return true; } + +/** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ +/*virtual*/ bool FilterMgr::supportsAsync() { return true; } + +/** + * Synchronously convert text. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return Converted text. + */ +QString FilterMgr::convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId) +{ + m_text = inputText; + m_talkerCode = talkerCode; + m_appId = appId; + m_filterIndex = -1; + m_filterProc = 0; + m_state = fsFiltering; + m_async = false; + while ( m_state == fsFiltering ) + nextFilter(); + return m_text; +} + +/** + * Aynchronously convert input. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * + * When the input text has been converted, filteringFinished signal will be emitted + * and caller can retrieve using getOutput(); +*/ +bool FilterMgr::asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId) +{ + m_text = inputText; + m_talkerCode = talkerCode; + m_appId = appId; + m_filterIndex = -1; + m_filterProc = 0; + m_state = fsFiltering; + m_async = true; + nextFilter(); + return true; +} + +// Finishes up with current filter (if any) and goes on to the next filter. +void FilterMgr::nextFilter() +{ + if ( m_filterProc ) + { + if ( m_filterProc->supportsAsync() ) + { + m_text = m_filterProc->getOutput(); + m_filterProc->ackFinished(); + disconnect( m_filterProc, SIGNAL(filteringFinished()), this, SLOT(slotFilteringFinished()) ); + } + // if ( m_filterProc->wasModified() ) + // kdDebug() << "FilterMgr::nextFilter: Filter# " << m_filterIndex << " modified the text." << endl; + if ( m_filterProc->wasModified() && m_filterProc->isSBD() ) + { + m_state = fsFinished; + // Post an event which will be later emitted as a signal. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 301); + QApplication::postEvent(this, ev); + return; + } + } + ++m_filterIndex; + if ( m_filterIndex == static_cast<int>(m_filterList.count()) ) + { + m_state = fsFinished; + // Post an event which will be later emitted as a signal. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 301); + QApplication::postEvent(this, ev); + return; + } + m_filterProc = m_filterList.at(m_filterIndex); + if ( m_noSBD && m_filterProc->isSBD() ) + { + m_state = fsFinished; + // Post an event which will be later emitted as a signal. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 301); + QApplication::postEvent(this, ev); + return; + } + m_filterProc->setSbRegExp( m_re ); + if ( m_async ) + { + if ( m_filterProc->supportsAsync() ) + { + // kdDebug() << "FilterMgr::nextFilter: calling asyncConvert on filter " << m_filterIndex << endl; + connect( m_filterProc, SIGNAL(filteringFinished()), this, SLOT(slotFilteringFinished()) ); + if ( !m_filterProc->asyncConvert( m_text, m_talkerCode, m_appId ) ) + { + disconnect( m_filterProc, SIGNAL(filteringFinished()), this, SLOT(slotFilteringFinished()) ); + m_filterProc = 0; + nextFilter(); + } + } else { + m_text = m_filterProc->convert( m_text, m_talkerCode, m_appId ); + nextFilter(); + } + } else + m_text = m_filterProc->convert( m_text, m_talkerCode, m_appId ); +} + +// Received when each filter finishes. +void FilterMgr::slotFilteringFinished() +{ + // kdDebug() << "FilterMgr::slotFilteringFinished: received signal from filter " << m_filterIndex << endl; + nextFilter(); +} + +bool FilterMgr::event ( QEvent * e ) +{ + if ( e->type() == (QEvent::User + 301) ) + { + // kdDebug() << "FilterMgr::event: emitting filteringFinished signal." << endl; + emit filteringFinished(); + return true; + } + if ( e->type() == (QEvent::User + 302) ) + { + // kdDebug() << "FilterMgr::event: emitting filteringStopped signal." << endl; + emit filteringStopped(); + return true; + } + else return false; +} + +/** + * Waits for filtering to finish. + */ +void FilterMgr::waitForFinished() +{ + if ( m_state != fsFiltering ) return; + disconnect(m_filterProc, SIGNAL(filteringFinished()), this, SLOT(slotFilteringFinished()) ); + m_async = false; + m_filterProc->waitForFinished(); + while ( m_state == fsFiltering ) + nextFilter(); +} + +/** + * Returns the state of the FilterMgr. + */ +int FilterMgr::getState() { return m_state; } + +/** + * Returns the filtered output. + */ +QString FilterMgr::getOutput() +{ + return m_text; +} + +/** + * Acknowledges the finished filtering. + */ +void FilterMgr::ackFinished() +{ + m_state = fsIdle; + m_text = QString::null; +} + +/** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped. + */ +void FilterMgr::stopFiltering() +{ + if ( m_state != fsFiltering ) return; + if ( m_async ) + disconnect( m_filterProc, SIGNAL(filteringFinished()), this, SLOT(slotFilteringFinished()) ); + m_filterProc->stopFiltering(); + m_state = fsIdle; + QCustomEvent* ev = new QCustomEvent(QEvent::User + 302); + QApplication::postEvent(this, ev); +} + +/** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ +/*virtual*/ void FilterMgr::setSbRegExp(const QString& re) +{ + m_re = re; +} + +/** + * Do not call SBD filters. + */ +void FilterMgr::setNoSBD(bool noSBD) { m_noSBD = noSBD; } +bool FilterMgr::noSBD() { return m_noSBD; } + +// Loads the processing plug in for a filter plug in given its DesktopEntryName. +KttsFilterProc* FilterMgr::loadFilterPlugin(const QString& desktopEntryName) +{ + // kdDebug() << "FilterMgr::loadFilterPlugin: Running"<< endl; + + // Find the plugin. + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin", + QString("DesktopEntryName == '%1'").arg(desktopEntryName)); + + if (offers.count() == 1) + { + // When the entry is found, load the plug in + // First create a factory for the library + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if(factory){ + // If the factory is created successfully, instantiate the KttsFilterConf class for the + // specific plug in to get the plug in configuration object. + int errorNo; + KttsFilterProc *plugIn = + KParts::ComponentFactory::createInstanceFromLibrary<KttsFilterProc>( + offers[0]->library().latin1(), NULL, offers[0]->library().latin1(), + QStringList(), &errorNo); + if(plugIn){ + // If everything went ok, return the plug in pointer. + return plugIn; + } else { + // Something went wrong, returning null. + kdDebug() << "FilterMgr::loadFilterPlugin: Unable to instantiate KttsFilterProc class for plugin " << desktopEntryName << " error: " << errorNo << endl; + return NULL; + } + } else { + // Something went wrong, returning null. + kdDebug() << "FilterMgr::loadFilterPlugin: Unable to create Factory object for plugin " + << desktopEntryName << endl; + return NULL; + } + } + // The plug in was not found (unexpected behaviour, returns null). + kdDebug() << "FilterMgr::loadFilterPlugin: KTrader did not return an offer for plugin " + << desktopEntryName << endl; + return NULL; +} + +/** + * Uses KTrader to convert a translated Filter Plugin Name to DesktopEntryName. + * @param name The translated plugin name. From Name= line in .desktop file. + * @return DesktopEntryName. The name of the .desktop file (less .desktop). + * QString::null if not found. + */ +QString FilterMgr::FilterNameToDesktopEntryName(const QString& name) +{ + if (name.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/FilterPlugin"); + for (uint ndx = 0; ndx < offers.count(); ++ndx) + if (offers[ndx]->name() == name) return offers[ndx]->desktopEntryName(); + return QString::null; +} + diff --git a/kttsd/kttsd/filtermgr.h b/kttsd/kttsd/filtermgr.h new file mode 100644 index 0000000..d909128 --- /dev/null +++ b/kttsd/kttsd/filtermgr.h @@ -0,0 +1,199 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + Filters text, applying each configured Filter in turn. + Runs asynchronously, emitting Finished() signal when all Filters have run. + + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FILTERMGR_H_ +#define _FILTERMGR_H_ + +// Qt includes. +#include <qptrlist.h> + +// KTTS includes. +#include "filterproc.h" + +class KConfig; +class TalkerCode; + +typedef QPtrList<KttsFilterProc> FilterList; + +class FilterMgr : public KttsFilterProc +{ + Q_OBJECT + + public: + /** + * Constructor. + */ + FilterMgr(QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + */ + ~FilterMgr(); + + /** + * Initialize the filters. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ + virtual bool supportsAsync(); + + /** + * Synchronously convert text. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return Converted text. + */ + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Asynchronously convert input. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * + * When the input text has been converted, filteringFinished signal will be emitted + * and caller can retrieve using getOutput(); + */ + virtual bool asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Waits for filtering to finish. + */ + virtual void waitForFinished(); + + /** + * Returns the state of the FilterMgr. + */ + virtual int getState(); + + /** + * Returns the filtered output. + */ + virtual QString getOutput(); + + /** + * Acknowledges the finished filtering. + */ + virtual void ackFinished(); + + /** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped. + */ + virtual void stopFiltering(); + + /** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ + virtual void setSbRegExp(const QString& re); + + /** + * Do not call SBD filters. + */ + void setNoSBD(bool noSBD); + bool noSBD(); + + /** + * True if there is at least one XML Transformer filter for html. + */ + bool supportsHTML() { return m_supportsHTML; } + + protected: + bool event ( QEvent * e ); + + private slots: + void slotFilteringFinished(); + + private: + // Loads the processing plug in for a named filter plug in. + KttsFilterProc* loadFilterPlugin(const QString& plugInName); + // Finishes up with current filter (if any) and goes on to the next filter. + void nextFilter(); + // Uses KTrader to convert a translated Filter Plugin Name to DesktopEntryName. + // @param name The translated plugin name. From Name= line in .desktop file. + // @return DesktopEntryName. The name of the .desktop file (less .desktop). + // QString::null if not found. + QString FilterNameToDesktopEntryName(const QString& name); + + // List of filters. + FilterList m_filterList; + // Text being filtered. + QString m_text; + // Index to list of filters. + int m_filterIndex; + // Current filter. + KttsFilterProc* m_filterProc; + // True if calling filters asynchronously. + bool m_async; + // Talker Code. + TalkerCode* m_talkerCode; + // AppId. + QCString m_appId; + // Sentence Boundary regular expression (if app overrode the default). + QString m_re; + // True if any of the filters modified the text. + bool m_wasModified; + // FilterMgr state. + int m_state; + // True if SBD Filters should not be called. + bool m_noSBD; + // True if at least one XML Transformer for html is enabled. + bool m_supportsHTML; +}; + +#endif // _FILTERMGR_H_ diff --git a/kttsd/kttsd/kttsd.cpp b/kttsd/kttsd/kttsd.cpp new file mode 100644 index 0000000..9ee841f --- /dev/null +++ b/kttsd/kttsd/kttsd.cpp @@ -0,0 +1,1183 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KTTSD main class + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + 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 <qcstring.h> +#include <qclipboard.h> +#include <qtextstream.h> +#include <qtextcodec.h> +#include <qfile.h> + +// KDE includes. +#include <kdebug.h> +#include <kapplication.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kfiledialog.h> +#include <dcopclient.h> +#include <knotifyclient.h> +#include <krun.h> +#include <kaboutdata.h> + +// KTTS includes. +#include "notify.h" +#include "kttsd.h" + +/** +* This is the "main" module of KTTSD. It performs the following functions: +* - Creates and destroys SpeechData and Speaker objects. +* - Receives DCOP calls and dispatches them to SpeechData and Speaker. +* - Receives signals from SpeechData and Speaker and converts them to DCOP signals. +* +* Note that most of the real tts work occurs in Speaker. +*/ + +KTTSD::KTTSD(const QCString& objId, QObject *parent, const char *name) : + DCOPObject(objId), + QObject(parent, name) +{ + // kdDebug() << "KTTSD::KTTSD Running" << endl; + m_speaker = 0; + m_talkerMgr = 0; + m_speechData = 0; + ready(); +} + +/* +* Create and initialize the SpeechData object. +*/ +bool KTTSD::initializeSpeechData() +{ + // Create speechData object. + if (!m_speechData) + { + m_speechData = new SpeechData(); + connect (m_speechData, SIGNAL(textSet(const QCString&, const uint)), + this, SLOT(slotTextSet(const QCString&, const uint))); + connect (m_speechData, SIGNAL(textAppended(const QCString&, const uint, const int)), + this, SLOT(slotTextAppended(const QCString&, const uint, const int))); + connect (m_speechData, SIGNAL(textRemoved(const QCString&, const uint)), + this, SLOT(slotTextRemoved(const QCString&, const uint))); + + // Hook KNotify signal. + if (!connectDCOPSignal(0, 0, + "notifySignal(QString,QString,QString,QString,QString,int,int,int,int)", + "notificationSignal(QString,QString,QString,QString,QString,int,int,int,int)", + false)) kdDebug() << "KTTSD:initializeSpeechData: connectDCOPSignal for knotify failed" << endl; + } + // Load configuration. + m_speechData->readConfig(); + + return true; +} + +/* +* Create and initialize the TalkerMgr object. +*/ +bool KTTSD::initializeTalkerMgr() +{ + if (!m_talkerMgr) + { + if (!m_speechData) initializeSpeechData(); + + m_talkerMgr = new TalkerMgr(this, "kttsdtalkermgr"); + int load = m_talkerMgr->loadPlugIns(m_speechData->config); + // If no Talkers configured, try to autoconfigure one, first in the user's + // desktop language, but if that fails, fallback to English. + if (load < 0) + { + QString languageCode = KGlobal::locale()->language(); + if (m_talkerMgr->autoconfigureTalker(languageCode, m_speechData->config)) + load = m_talkerMgr->loadPlugIns(m_speechData->config); + else + { + if (m_talkerMgr->autoconfigureTalker("en", m_speechData->config)) + load = m_talkerMgr->loadPlugIns(m_speechData->config); + } + } + if (load < 0) + { + // TODO: Would really like to eliminate ALL GUI stuff from kttsd. Find + // a better way to do this. + delete m_speaker; + m_speaker = 0; + delete m_talkerMgr; + m_talkerMgr = 0; + delete m_speechData; + m_speechData = 0; + kdDebug() << "KTTSD::initializeTalkerMgr: no Talkers have been configured." << endl; + // Ask if user would like to run configuration dialog, but don't bug user unnecessarily. + QString dontAskConfigureKTTS = "DontAskConfigureKTTS"; + KMessageBox::ButtonCode msgResult; + if (KMessageBox::shouldBeShownYesNo(dontAskConfigureKTTS, msgResult)) + { + if (KMessageBox::questionYesNo( + 0, + i18n("KTTS has not yet been configured. At least one Talker must be configured. " + "Would you like to configure it now?"), + i18n("KTTS Not Configured"), + i18n("Configure"), + i18n("Do Not Configure"), + dontAskConfigureKTTS) == KMessageBox::Yes) msgResult = KMessageBox::Yes; + } + if (msgResult == KMessageBox::Yes) showDialog(); + return false; + } + } + m_speechData->setTalkerMgr(m_talkerMgr); + return true; +} + +/* +* Create and initialize the Speaker object. +*/ +bool KTTSD::initializeSpeaker() +{ + // kdDebug() << "KTTSD::initializeSpeaker: Instantiating Speaker" << endl; + + if (!m_talkerMgr) initializeTalkerMgr(); + + // Create speaker object and load plug ins, checking for the return + m_speaker = new Speaker(m_speechData, m_talkerMgr); + connect (m_speaker, SIGNAL(textStarted(const QCString&, const uint)), + this, SLOT(slotTextStarted(const QCString&, const uint))); + connect (m_speaker, SIGNAL(textFinished(const QCString&, const uint)), + this, SLOT(slotTextFinished(const QCString&, const uint))); + connect (m_speaker, SIGNAL(textResumed(const QCString&, const uint)), + this, SLOT(slotTextResumed(const QCString&, const uint))); + connect (m_speaker, SIGNAL(sentenceStarted(QString, QString, const QCString&, const uint, const uint)), + this, SLOT(slotSentenceStarted(QString, QString, const QCString&, const uint, const uint))); + connect (m_speaker, SIGNAL(sentenceFinished(const QCString&, const uint, const uint)), this, + SLOT(slotSentenceFinished(const QCString&, const uint, const uint))); + connect (m_speaker, SIGNAL(textStopped(const QCString&, const uint)), + this, SLOT(slotTextStopped(const QCString&, const uint))); + connect (m_speaker, SIGNAL(textPaused(const QCString&, const uint)), + this, SLOT(slotTextPaused(const QCString&, const uint))); + + return true; +} + +/** + * Destructor + * Terminate speaker thread + */ +KTTSD::~KTTSD(){ + kdDebug() << "KTTSD::~KTTSD:: Stopping KTTSD service" << endl; + if (m_speaker) m_speaker->requestExit(); + delete m_speaker; + delete m_talkerMgr; + delete m_speechData; + kdDebug() << "KTTSD::~KTTSD: Emitting DCOP signal kttsdExiting()" << endl; + kttsdExiting(); +} + +/***** DCOP exported functions *****/ + +/** +* Determine whether the currently-configured speech plugin supports a speech markup language. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* @param markupType The kttsd code for the desired speech markup language. +* @return True if the plugin currently configured for the indicated +* talker supports the indicated speech markup language. +* @see kttsdMarkupType +*/ +bool KTTSD::supportsMarkup(const QString& talker /*=NULL*/, const uint markupType /*=0*/) const +{ + if (markupType == KSpeech::mtHtml) + { + if (!m_speechData) return false; + return m_speechData->supportsHTML; + } + if (markupType != KSpeech::mtSsml) return false; + if (!m_talkerMgr) return false; + return m_talkerMgr->supportsMarkup(fixNullString(talker), markupType); +} + +/** +* Determine whether the currently-configured speech plugin supports markers in speech markup. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* @return True if the plugin currently configured for the indicated +* talker supports markers. +* TODO: Waiting on plugin API. +*/ +bool KTTSD::supportsMarkers(const QString& /*talker=NULL*/) const { return false; } + +/** +* Say a message as soon as possible, interrupting any other speech in progress. +* IMPORTANT: This method is reserved for use by Screen Readers and should not be used +* by any other applications. +* @param msg The message to be spoken. +* @param talker Code for the to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +* +* If an existing Screen Reader output is in progress, it is stopped and discarded and +* replaced with this new message. +*/ +void KTTSD::sayScreenReaderOutput(const QString &msg, const QString &talker /*=NULL*/) +{ + if (!m_speaker) return; + m_speechData->setScreenReaderOutput(msg, fixNullString(talker), getAppId()); + m_speaker->doUtterances(); +} + +/** +* Say a warning. The warning will be spoken when the current sentence +* stops speaking and takes precedence over Messages and regular text. Warnings should only +* be used for high-priority messages requiring immediate user attention, such as +* "WARNING. CPU is overheating." +* @param warning The warning to be spoken. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +*/ +void KTTSD::sayWarning(const QString &warning, const QString &talker /*=NULL*/){ + // kdDebug() << "KTTSD::sayWarning: Running" << endl; + if (!m_speaker) return; + kdDebug() << "KTTSD::sayWarning: Adding '" << warning << "' to warning queue." << endl; + m_speechData->enqueueWarning(warning, fixNullString(talker), getAppId()); + m_speaker->doUtterances(); +} + +/** +* Say a message. The message will be spoken when the current sentence stops speaking +* but after any warnings have been spoken. +* Messages should be used for one-shot messages that can't wait for +* normal text messages to stop speaking, such as "You have mail.". +* @param message The message to be spoken. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no talker has been configured for the specified Talker code, +* defaults to the closest matching talker. +*/ +void KTTSD::sayMessage(const QString &message, const QString &talker /*=NULL*/) +{ + // kdDebug() << "KTTSD::sayMessage: Running" << endl; + if (!m_speaker) return; + kdDebug() << "KTTSD::sayMessage: Adding '" << message << "' to message queue." << endl; + m_speechData->enqueueMessage(message, fixNullString(talker), getAppId()); + m_speaker->doUtterances(); +} + +/** +* Sets the GREP pattern that will be used as the sentence delimiter. +* @param delimiter A valid GREP pattern. +* +* The default sentence delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])\\s + @endverbatim +* +* Note that backward slashes must be escaped. +* +* Changing the sentence delimiter does not affect other applications. +* @see sentenceparsing +*/ +void KTTSD::setSentenceDelimiter(const QString &delimiter) +{ + if (!m_speaker) return; + m_speechData->setSentenceDelimiter(fixNullString(delimiter), getAppId()); +} + +/** +* Queue a text job. Does not start speaking the text. +* @param text The message to be spoken. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default plugin. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +* @return Job number. +* +* Plain text is parsed into individual sentences using the current sentence delimiter. +* Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. +* Call @ref getTextCount to retrieve the sentence count after calling setText. +* +* The text may contain speech mark language, such as Sable, JSML, or SMML, +* provided that the speech plugin/engine support it. In this case, +* sentence parsing follows the semantics of the markup language. +* +* Call @ref startText to mark the job as speakable and if the +* job is the first speakable job in the queue, speaking will begin. +* @see getTextCount +* @see startText +*/ +uint KTTSD::setText(const QString &text, const QString &talker /*=NULL*/) +{ + // kdDebug() << "KTTSD::setText: Running" << endl; + if (!m_speaker) return 0; + // kdDebug() << "KTTSD::setText: Setting text: '" << text << "'" << endl; + uint jobNum = m_speechData->setText(text, fixNullString(talker), getAppId()); + return jobNum; +} + +/** +* Say a plain text job. This is a convenience method that +* combines @ref setText and @ref startText into a single call. +* @param text The message to be spoken. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default plugin. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +* @return Job number. +* +* Plain text is parsed into individual sentences using the current sentence delimiter. +* Call @ref setSentenceDelimiter to change the sentence delimiter prior to +* calling setText. +* Call @ref getTextCount to retrieve the sentence count after calling setText. +* +* The text may contain speech mark language, such as Sable, JSML, or SSML, +* provided that the speech plugin/engine support it. In this case, +* sentence parsing follows the semantics of the markup language. +* +* The job is marked speakable. +* If there are other speakable jobs preceeding this one in the queue, +* those jobs continue speaking and when finished, this job will begin speaking. +* If there are no other speakable jobs preceeding this one, it begins speaking. +* +* @see getTextCount +* +* @since KDE 3.5 +*/ +uint KTTSD::sayText(const QString &text, const QString &talker) +{ + uint jobNum = setText(text, talker); + if (jobNum) startText(jobNum); + return jobNum; +} + +/** +* Adds another part to a text job. Does not start speaking the text. +* (thread safe) +* @param text The message to be spoken. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application, +* but if no such job, applies to the last job queued by any application. +* @return Part number for the added part. Parts are numbered starting at 1. +* +* The text is parsed into individual sentences. Call getTextCount to retrieve +* the sentence count. Call startText to mark the job as speakable and if the +* job is the first speakable job in the queue, speaking will begin. +* @see setText. +* @see startText. +*/ +int KTTSD::appendText(const QString &text, const uint jobNum /*=0*/) +{ + if (!m_speaker) return 0; + return m_speechData->appendText(text, applyDefaultJobNum(jobNum), getAppId()); +} + +/** +* Queue a text job from the contents of a file. Does not start speaking the text. +* @param filename Full path to the file to be spoken. May be a URL. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +* @param encoding Name of the encoding to use when reading the file. If +* NULL or Empty, uses default stream encoding. +* @return Job number. 0 if an error occurs. +* +* Plain text is parsed into individual sentences using the current sentence delimiter. +* Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. +* Call @ref getTextCount to retrieve the sentence count after calling setText. +* +* The text may contain speech mark language, such as Sable, JSML, or SMML, +* provided that the speech plugin/engine support it. In this case, +* sentence parsing follows the semantics of the markup language. +* +* Call @ref startText to mark the job as speakable and if the +* job is the first speakable job in the queue, speaking will begin. +* @see getTextCount +* @see startText +*/ +uint KTTSD::setFile(const QString &filename, const QString &talker /*=NULL*/, + const QString &encoding /*=NULL*/) +{ + // kdDebug() << "KTTSD::setFile: Running" << endl; + if (!m_speaker) return 0; + QFile file(filename); + uint jobNum = 0; + if ( file.open(IO_ReadOnly) ) + { + QTextStream stream(&file); + QString enc = fixNullString(encoding); + if (!enc.isEmpty()) + { + QTextCodec* codec = QTextCodec::codecForName(enc.latin1()); + if (codec) stream.setCodec(codec); + } + jobNum = m_speechData->setText(stream.read(), fixNullString(talker), getAppId()); + file.close(); + } + return jobNum; +} + +/** +* Get the number of sentences in a text job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* @return The number of sentences in the job. -1 if no such job. +* +* The sentences of a job are given sequence numbers from 1 to the number returned by this +* method. The sequence numbers are emitted in the @ref sentenceStarted and +* @ref sentenceFinished signals. +*/ +int KTTSD::getTextCount(const uint jobNum /*=0*/) +{ + if (!m_speaker) return -1; + return m_speechData->getTextCount(applyDefaultJobNum(jobNum)); +} + +/** +* Get the job number of the current text job. +* @return Job number of the current text job. 0 if no jobs. +* +* Note that the current job may not be speaking. See @ref isSpeakingText. +* @see getTextJobState. +* @see isSpeakingText +*/ +uint KTTSD::getCurrentTextJob() +{ + if (!m_speaker) return 0; + return m_speaker->getCurrentTextJob(); +} + +/** +* Get the number of jobs in the text job queue. +* @return Number of text jobs in the queue. 0 if none. +*/ +uint KTTSD::getTextJobCount() +{ + if (!m_speaker) return 0; + return m_speechData->getTextJobCount(); +} + +/** +* Get a comma-separated list of text job numbers in the queue. +* @return Comma-separated list of text job numbers in the queue. +*/ +QString KTTSD::getTextJobNumbers() +{ + if (!m_speaker) return QString::null; + return m_speechData->getTextJobNumbers(); +} + +/** +* Get the state of a text job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* @return State of the job. -1 if invalid job number. +* +* @see kttsdJobState +*/ +int KTTSD::getTextJobState(const uint jobNum /*=0*/) +{ + if (!m_speaker) return -1; + return m_speechData->getTextJobState(applyDefaultJobNum(jobNum)); +} + +/** +* Get information about a text job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* @return A QDataStream containing information about the job. +* Blank if no such job. +* +* The stream contains the following elements: +* - int state Job state. +* - QCString appId DCOP senderId of the application that requested the speech job. +* - QString talker Language code in which to speak the text. +* - int seq Current sentence being spoken. Sentences are numbered starting at 1. +* - int sentenceCount Total number of sentences in the job. +* +* The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talker; + int seq; + int sentenceCount; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + @endverbatim +*/ +QByteArray KTTSD::getTextJobInfo(const uint jobNum /*=0*/) +{ + return m_speechData->getTextJobInfo(applyDefaultJobNum(jobNum)); +} + +/** +* Given a Talker Code, returns the Talker ID of the talker that would speak +* a text job with that Talker Code. +* @param talkerCode Talker Code. +* @return Talker ID of the talker that would speak the text job. +*/ +QString KTTSD::talkerCodeToTalkerId(const QString& talkerCode) +{ + if (!m_talkerMgr) return QString::null; + return m_talkerMgr->talkerCodeToTalkerId(fixNullString(talkerCode)); +} + +/** +* Return a sentence of a job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* @param seq Sequence number of the sentence. +* @return The specified sentence in the specified job. If no such +* job or sentence, returns "". +*/ +QString KTTSD::getTextJobSentence(const uint jobNum /*=0*/, const uint seq /*=1*/) +{ + return m_speechData->getTextJobSentence(applyDefaultJobNum(jobNum), seq); +} + +/** +* Determine if kttsd is currently speaking any text jobs. +* @return True if currently speaking any text jobs. +*/ +bool KTTSD::isSpeakingText() const +{ + if (!m_speaker) return false; + return m_speaker->isSpeakingText(); +} + +/** +* Remove a text job from the queue. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* +* The job is deleted from the queue and the @ref textRemoved signal is emitted. +* +* If there is another job in the text queue, and it is marked speakable, +* that job begins speaking. +*/ +void KTTSD::removeText(const uint jobNum /*=0*/) +{ + kdDebug() << "KTTSD::removeText: Running" << endl; + if (!m_speaker) return; + m_speaker->removeText(applyDefaultJobNum(jobNum)); +} + +/** +* Start a text job at the beginning. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* +* Rewinds the job to the beginning. +* +* The job is marked speakable. +* If there are other speakable jobs preceeding this one in the queue, +* those jobs continue speaking and when finished, this job will begin speaking. +* If there are no other speakable jobs preceeding this one, it begins speaking. +* +* The @ref textStarted signal is emitted when the text job begins speaking. +* When all the sentences of the job have been spoken, the job is marked for deletion from +* the text queue and the @ref textFinished signal is emitted. +*/ +void KTTSD::startText(const uint jobNum /*=0*/) +{ + kdDebug() << "KTTSD::startText: Running" << endl; + if (!m_speaker) return; + // Determine if we are starting speech. + bool startingSpeech = !isSpeakingText(); + uint jNum = applyDefaultJobNum(jobNum); + m_speaker->startText(jNum); + // If this has started speech output, determine whether to autostart KTTSMgr. + if (startingSpeech) + { + if (m_speechData->autoStartManager) + { + // Do not start KTTSMgr unless at least 5 sentences are queued. + if (getTextCount(jNum) > 4) + { + QString cmd = "kttsmgr --systray"; + if (m_speechData->autoExitManager) cmd.append(" --autoexit"); + // Notice this fails if KTTSMgr is already running, which is what we want. + KRun::runCommand(cmd); + } + } + } +} + +/** +* Stop a text job and rewind to the beginning. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* +* The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText +* is called. +* +* If there are speaking jobs preceeding this one in the queue, they continue speaking. +* If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. +* Depending upon the speech engine and plugin used, speeking may not stop immediately +* (it might finish the current sentence). +*/ +void KTTSD::stopText(const uint jobNum /*=0*/) +{ + kdDebug() << "KTTSD::stopText: Running" << endl; + if (!m_speaker) return; + m_speaker->stopText(applyDefaultJobNum(jobNum)); +} + +/** +* Pause a text job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* +* The job is marked as paused and will not be speakable until @ref resumeText or +* @ref startText is called. +* +* If there are speaking jobs preceeding this one in the queue, they continue speaking. +* If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. +* Depending upon the speech engine and plugin used, speeking may not stop immediately +* (it might finish the current sentence). +* @see resumeText +*/ +void KTTSD::pauseText(const uint jobNum /*=0*/) +{ + kdDebug() << "KTTSD::pauseText: Running" << endl; + if (!m_speaker) return; + m_speaker->pauseText(applyDefaultJobNum(jobNum)); +} + +/** +* Start or resume a text job where it was paused. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* +* The job is marked speakable. +* +* If the job is currently speaking, or is waiting to be spoken (speakable +* state), the resumeText() call is ignored. +* +* If the job is currently queued, or is finished, it is the same as calling +* @ref startText . +* +* If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, +* when finished this job will begin speaking where it left off. +* +* The @ref textResumed signal is emitted when the job resumes. +* @see pauseText +*/ +void KTTSD::resumeText(const uint jobNum /*=0*/) +{ + kdDebug() << "KTTSD::resumeText: Running" << endl; + if (!m_speaker) return; + m_speaker->resumeText(applyDefaultJobNum(jobNum)); +} + +/** +* Get a list of the talkers configured in KTTS. +* @return A QStringList of fully-specified talker codes, one +* for each talker user has configured. +* +* @see talkers +*/ +QStringList KTTSD::getTalkers() +{ + if (!m_talkerMgr) return QStringList(); + return m_talkerMgr->getTalkers(); +} + +/** +* Change the talker for a text job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* @param talker New code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +*/ +void KTTSD::changeTextTalker(const QString &talker, uint jobNum) +{ + m_speechData->changeTextTalker(fixNullString(talker), applyDefaultJobNum(jobNum)); +} + +/** +* Get the user's default talker. +* @return A fully-specified talker code. +* +* @see talkers +* @see getTalkers +*/ +QString KTTSD::userDefaultTalker() +{ + if (!m_talkerMgr) return QString::null; + return m_talkerMgr->userDefaultTalker(); +} + +/** +* Move a text job down in the queue so that it is spoken later. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application. +* +* If the job is currently speaking, it is paused. +* If the next job in the queue is speakable, it begins speaking. +*/ +void KTTSD::moveTextLater(const uint jobNum /*=0*/) +{ + if (!m_speaker) return; + m_speaker->moveTextLater(applyDefaultJobNum(jobNum)); +} + +/** +* Jump to the first sentence of a specified part of a text job. +* @param partNum Part number of the part to jump to. Parts are numbered starting at 1. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application, +* but if no such job, applies to the last job queued by any application. +* @return Part number of the part actually jumped to. +* +* If partNum is greater than the number of parts in the job, jumps to last part. +* If partNum is 0, does nothing and returns the current part number. +* If no such job, does nothing and returns 0. +* Does not affect the current speaking/not-speaking state of the job. +*/ +int KTTSD::jumpToTextPart(const int partNum, const uint jobNum /*=0*/) +{ + if (!m_speaker) return 0; + return m_speaker->jumpToTextPart(partNum, applyDefaultJobNum(jobNum)); +} + +/** +* Advance or rewind N sentences in a text job. +* @param n Number of sentences to advance (positive) or rewind (negative) in the job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application, +* but if no such job, applies to the last job queued by any application. +* @return Sequence number of the sentence actually moved to. Sequence numbers +* are numbered starting at 1. +* +* If no such job, does nothing and returns 0. +* If n is zero, returns the current sequence number of the job. +* Does not affect the current speaking/not-speaking state of the job. +*/ +uint KTTSD::moveRelTextSentence(const int n, const uint jobNum /*=0*/) +{ + if (!m_speaker) return 0; + return m_speaker->moveRelTextSentence(n, applyDefaultJobNum(jobNum)); +} + +/** +* Add the clipboard contents to the text queue and begin speaking it. +*/ +void KTTSD::speakClipboard() +{ + // Get the clipboard object. + QClipboard *cb = kapp->clipboard(); + + // Copy text from the clipboard. + QString text = cb->text(); + + // Speak it. + if ( !text.isNull() ) + { + setText(text); + startText(); + } +} + +/** +* Displays the %KTTS Manager dialog. In this dialog, the user may backup or skip forward in +* any text job by sentence or paragraph, rewind jobs, pause or resume jobs, or +* delete jobs. +*/ +void KTTSD::showDialog() +{ + KRun::runCommand("kttsmgr"); +} + +/** +* Stop the service. +*/ +void KTTSD::kttsdExit() +{ + stopText(); + kdDebug() << "KTTSD::kttsdExit: Emitting DCOP signal kttsdExiting()" << endl; + kttsdExiting(); + kapp->quit(); +} + +/** +* Re-start %KTTSD. +*/ +void KTTSD::reinit() +{ + // Restart ourself. + kdDebug() << "KTTSD::reinit: Running" << endl; + if (m_speaker) + { + kdDebug() << "KTTSD::reinit: Stopping KTTSD service" << endl; + if (m_speaker->isSpeakingText()) pauseText(); + m_speaker->requestExit(); + } + delete m_speaker; + m_speaker = 0; + delete m_talkerMgr; + m_talkerMgr = 0; + ready(); +} + +/** +* Return KTTSD daemon version number. +*/ +QString KTTSD::version() { return kapp->aboutData()->version(); } + +/* +* Checks if KTTSD is ready to speak and at least one talker is configured. +* If not, user is prompted to display the configuration dialog. +*/ +bool KTTSD::ready() +{ + if (m_speaker) return true; + kdDebug() << "KTTSD::ready: Starting KTTSD service" << endl; + if (!initializeSpeechData()) return false; + if (!initializeTalkerMgr()) return false; + if (!initializeSpeaker()) return false; + m_speaker->doUtterances(); + kdDebug() << "KTTSD::ready: Emitting DCOP signal kttsdStarted()" << endl; + kttsdStarted(); + return true; +} + +void KTTSD::configCommitted() { + if (m_speaker) reinit(); +} + +/** +* This signal is emitted by KNotify when a notification event occurs. +* ds << event << fromApp << text << sound << file << present << level +* << winId << eventId; +* default_presentation contains these ORed events: None=0, Sound=1, Messagebox=2, Logfile=4, Stderr=8, +* PassivePopup=16, Execute=32, Taskbar=64 +*/ +void KTTSD::notificationSignal( const QString& event, const QString& fromApp, + const QString &text, const QString& sound, const QString& /*file*/, + const int present, const int /*level*/, const int /*windId*/, const int /*eventId*/) +{ + if (!m_speaker) return; + // kdDebug() << "KTTSD:notificationSignal: event: " << event << " fromApp: " << fromApp << + // " text: " << text << " sound: " << sound << " file: " << file << " present: " << present << + // " level: " << level << " windId: " << windId << " eventId: " << eventId << endl; + if ( m_speechData->notify ) + if ( !m_speechData->notifyExcludeEventsWithSound || sound.isEmpty() ) + { + bool found = false; + NotifyOptions notifyOptions; + QString msg; + QString talker; + // Check for app-specific action. + if ( m_speechData->notifyAppMap.contains( fromApp ) ) + { + NotifyEventMap notifyEventMap = m_speechData->notifyAppMap[ fromApp ]; + if ( notifyEventMap.contains( event ) ) + { + found = true; + notifyOptions = notifyEventMap[ event ]; + } else { + // Check for app-specific default. + if ( notifyEventMap.contains( "default" ) ) + { + found = true; + notifyOptions = notifyEventMap[ "default" ]; + notifyOptions.eventName = QString::null; + } + } + } + // If no app-specific action, try default. + if ( !found ) + { + switch ( m_speechData->notifyDefaultPresent ) + { + case NotifyPresent::None: + found = false; + break; + case NotifyPresent::Dialog: + found = ( + (present & KNotifyClient::Messagebox) + && + !(present & KNotifyClient::PassivePopup) + ); + break; + case NotifyPresent::Passive: + found = ( + !(present & KNotifyClient::Messagebox) + && + (present & KNotifyClient::PassivePopup) + ); + break; + case NotifyPresent::DialogAndPassive: + found = ( + (present & KNotifyClient::Messagebox) + && + (present & KNotifyClient::PassivePopup) + ); + break; + case NotifyPresent::All: + found = true; + break; + } + if ( found ) + notifyOptions = m_speechData->notifyDefaultOptions; + } + if ( found ) + { + int action = notifyOptions.action; + talker = notifyOptions.talker; + switch ( action ) + { + case NotifyAction::DoNotSpeak: + break; + case NotifyAction::SpeakEventName: + if (notifyOptions.eventName.isEmpty()) + msg = NotifyEvent::getEventName( fromApp, event ); + else + msg = notifyOptions.eventName; + break; + case NotifyAction::SpeakMsg: + msg = text; + break; + case NotifyAction::SpeakCustom: + msg = notifyOptions.customMsg; + msg.replace( "%a", fromApp ); + msg.replace( "%m", text ); + if ( msg.contains( "%e" ) ) + { + if ( notifyOptions.eventName.isEmpty() ) + msg.replace( "%e", NotifyEvent::getEventName( fromApp, event ) ); + else + msg.replace( "%e", notifyOptions.eventName ); + } + break; + } + } + // Queue msg if we should speak something. + if ( !msg.isEmpty() ) + { + QString fromApps = fromApp + ",knotify"; + m_speechData->enqueueMessage( msg, talker, fromApps.utf8() ); + m_speaker->doUtterances(); + } + } +} + +// Slots for the speaker object +void KTTSD::slotSentenceStarted(QString, QString, const QCString& appId, + const uint jobNum, const uint seq) { + // Emit DCOP signal. + kdDebug() << "KTTSD::slotSentenceStarted: Emitting DCOP signal sentenceStarted with appId " << appId << " job number " << jobNum << " seq number " << seq << endl; + sentenceStarted(appId, jobNum, seq); +} + +void KTTSD::slotSentenceFinished(const QCString& appId, const uint jobNum, const uint seq){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotSentenceFinished: Emitting DCOP signal sentenceFinished with appId " << appId << " job number " << jobNum << " seq number " << seq << endl; + sentenceFinished(appId, jobNum, seq); +} + +// Slots for the speechData and speaker objects. +void KTTSD::slotTextStarted(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextStarted: Emitting DCOP signal textStarted with appId " << appId << " job number " << jobNum << endl; + textStarted(appId, jobNum); +} + +void KTTSD::slotTextFinished(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextFinished: Emitting DCOP signal textFinished with appId " << appId << " job number " << jobNum << endl; + textFinished(appId, jobNum); +} + +void KTTSD::slotTextStopped(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextStopped: Emitting DCOP signal textStopped with appId " << appId << " job number " << jobNum << endl; + textStopped(appId, jobNum); +} + +void KTTSD::slotTextPaused(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextPaused: Emitting DCOP signal textPaused with appId " << appId << " job number " << jobNum << endl; + textPaused(appId, jobNum); +} + +void KTTSD::slotTextResumed(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextResumed: Emitting DCOP signal textResumed with appId " << appId << " job number " << jobNum << endl; + textResumed(appId, jobNum); +} + +//void KTTSD::slotTextSet(const QCString& appId, const uint jobNum){ +void KTTSD::slotTextSet(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextSet: Emitting DCOP signal textSet with appId " << appId << " job number " << jobNum << endl; + textSet(appId, jobNum); +} + +void KTTSD::slotTextAppended(const QCString& appId, const uint jobNum, const int partNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextAppended: Emitting DCOP signal textAppended with appId " << + appId << " job number " << jobNum << " part number " << partNum << endl; + textAppended(appId, jobNum, partNum); +} + +void KTTSD::slotTextRemoved(const QCString& appId, const uint jobNum){ + // Emit DCOP signal. + kdDebug() << "KTTSD::slotTextRemoved: Emitting DCOP signal textRemoved with appId " << appId << " job number " << jobNum << endl; + textRemoved(appId, jobNum); +} + +/** + * Returns the senderId (appId) of the DCOP application that called us. + * @return The DCOP sendId of calling application. + */ +const QCString KTTSD::getAppId() +{ + DCOPClient* client = callingDcopClient(); + QCString appId; + if (client) appId = client->senderId(); + return appId; +} + +/** +* If a job number is 0, returns the default job number for a command. +* Returns the job number of the last job queued by the application, or if +* no such job, the current job number. +* @return Default job number. 0 if no such job. +*/ +uint KTTSD::applyDefaultJobNum(const uint jobNum) +{ + uint jNum = jobNum; + if (!jNum) + { + jNum = m_speechData->findAJobNumByAppId(getAppId()); + if (!jNum) jNum = getCurrentTextJob(); + if (!jNum) jNum = m_speechData->findAJobNumByAppId(0); + } + return jNum; +} + +/* +* Fixes a string argument passed in via dcop. +* If NULL or "0" return QString::null. +*/ +QString KTTSD::fixNullString(const QString &talker) const +{ + if (!talker) return QString::null; + if (talker == "0") return QString::null; + return talker; +} + +// kspeech is obsolete. Applications should use KSpeech instead. + +// Constructor. +kspeech::kspeech(const QCString& objId, QObject *parent, const char *name) : + DCOPObject(objId), + QObject(parent, name), + m_kttsd("KSpeech") +{ +} + +// Destructor. +kspeech::~kspeech() { } + +// Delegate all DCOP methods to KTTSD object. +/*virtual*/ bool kspeech::supportsMarkup(const QString &talker, uint markupType) const + { return m_kttsd.supportsMarkup(talker, markupType); } +/*virtual*/ bool kspeech::supportsMarkers(const QString &talker) const + { return m_kttsd.supportsMarkers(talker); } +/*virtual*/ ASYNC kspeech::sayScreenReaderOutput(const QString &msg, const QString &talker) + { m_kttsd.sayScreenReaderOutput(msg, talker); } +/*virtual*/ ASYNC kspeech::sayWarning(const QString &warning, const QString &talker) + { m_kttsd.sayWarning(warning, talker); } +/*virtual*/ ASYNC kspeech::sayMessage(const QString &message, const QString &talker) + { m_kttsd.sayMessage(message, talker); } +/*virtual*/ ASYNC kspeech::setSentenceDelimiter(const QString &delimiter) + { m_kttsd.setSentenceDelimiter(delimiter); } +/*virtual*/ uint kspeech::setText(const QString &text, const QString &talker) + { return m_kttsd.setText(text, talker); } +/*virtual*/ uint kspeech::sayText(const QString &text, const QString &talker) + { return m_kttsd.sayText(text, talker); } +/*virtual*/ int kspeech::appendText(const QString &text, uint jobNum) + { return m_kttsd.appendText(text, jobNum); } +/*virtual*/ uint kspeech::setFile(const QString &filename, const QString &talker, + const QString& encoding) + { return m_kttsd.setFile(filename, talker, encoding); } +/*virtual*/ int kspeech::getTextCount(uint jobNum) + { return m_kttsd.getTextCount(jobNum); } +/*virtual*/ uint kspeech::getCurrentTextJob() + { return m_kttsd.getCurrentTextJob(); } +/*virtual*/ uint kspeech::getTextJobCount() + { return m_kttsd.getTextJobCount(); } +/*virtual*/ QString kspeech::getTextJobNumbers() + { return m_kttsd.getTextJobNumbers(); } +/*virtual*/ int kspeech::getTextJobState(uint jobNum) + { return m_kttsd.getTextJobState(jobNum); } +/*virtual*/ QByteArray kspeech::getTextJobInfo(uint jobNum) + { return m_kttsd.getTextJobInfo(jobNum); } +/*virtual*/ QString kspeech::talkerCodeToTalkerId(const QString& talkerCode) + { return m_kttsd.talkerCodeToTalkerId(talkerCode); } +/*virtual*/ QString kspeech::getTextJobSentence(uint jobNum, uint seq) + { return m_kttsd.getTextJobSentence(jobNum, seq); } +/*virtual*/ bool kspeech::isSpeakingText() const + { return m_kttsd.isSpeakingText(); } +/*virtual*/ ASYNC kspeech::removeText(uint jobNum) + { m_kttsd.removeText(jobNum); } +/*virtual*/ ASYNC kspeech::startText(uint jobNum) + { m_kttsd.startText(jobNum); } +/*virtual*/ ASYNC kspeech::stopText(uint jobNum) + { m_kttsd.stopText(jobNum); } +/*virtual*/ ASYNC kspeech::pauseText(uint jobNum) + { m_kttsd.pauseText(jobNum); } +/*virtual*/ ASYNC kspeech::resumeText(uint jobNum) + { m_kttsd.resumeText(jobNum); } +/*virtual*/ QStringList kspeech::getTalkers() + { return m_kttsd.getTalkers(); } +/*virtual*/ ASYNC kspeech::changeTextTalker(const QString &talker, uint jobNum ) + { m_kttsd.changeTextTalker(talker, jobNum); } +/*virtual*/ QString kspeech::userDefaultTalker() + { return m_kttsd.userDefaultTalker(); } +/*virtual*/ ASYNC kspeech::moveTextLater(uint jobNum) + { m_kttsd.moveTextLater(jobNum); } +/*virtual*/ int kspeech::jumpToTextPart(int partNum, uint jobNum) + { return m_kttsd.jumpToTextPart(partNum, jobNum); } +/*virtual*/ uint kspeech::moveRelTextSentence(int n, uint jobNum) + { return m_kttsd.moveRelTextSentence(n, jobNum); } +/*virtual*/ ASYNC kspeech::speakClipboard() + { m_kttsd.speakClipboard(); } +/*virtual*/ void kspeech::showDialog() + { m_kttsd.showDialog(); } +/*virtual*/ void kspeech::kttsdExit() + { m_kttsd.kttsdExit(); } +/*virtual*/ void kspeech::reinit() + { m_kttsd.reinit(); } +/*virtual*/ QString kspeech::version() + { return m_kttsd.version(); } + +#include "kttsd.moc" + diff --git a/kttsd/kttsd/kttsd.desktop b/kttsd/kttsd/kttsd.desktop new file mode 100644 index 0000000..27c216e --- /dev/null +++ b/kttsd/kttsd/kttsd.desktop @@ -0,0 +1,56 @@ +[Desktop Entry] +Type=Service +Exec=kttsd +Icon=kttsd +ServiceTypes=DCOP/Text-to-Speech +X-DCOP-ServiceType=Unique +X-DCOP-ServiceName=kttsd +X-KDE-StartupNotify=false +Name=KTTSD +Name[zh_TW]=KTTSd +Comment=KDE Text To Speech Daemon +Comment[bg]=Демон за управление на модула за синтез на глас +Comment[bs]=KDE Demon za izgovaranje teksta +Comment[ca]=Dimoni de text a veu de KDE +Comment[cs]=Démon hlasové syntézy KDE +Comment[da]=KDE's Tekst til tale-dæmon +Comment[de]=KDE Sprachausgabedienst +Comment[el]=KDE δαίμονας κειμένου-σε-ομιλία +Comment[es]=Demonio de KDE para la síntesis de texto a voz +Comment[et]=KDE teksti kõneks muutmise deemon +Comment[eu]=KDE-ren testutik hizketarako deabrua +Comment[fa]=شبح متن به گفتار KDE +Comment[fi]=KDE Teksti puheeksi -palvelinohjelma +Comment[fr]=Démon de synthèse vocale pour KDE +Comment[ga]=Deamhan Téacs-Go-Caint KDE +Comment[gl]=Servizo Texto-para-Fala de KDE +Comment[he]=שירות הטקסט לדיבור של KDE +Comment[hu]=KDE szövegfelolvasó szolgáltatás +Comment[is]=KDE texti-í-tal púki +Comment[it]=Demone di pronuncia di KDE +Comment[ja]=KDE テキスト読み上げデーモン +Comment[ka]=KDE ტექსტის გახმოვანების დემონი +Comment[km]=ដេមិនអត្ថបទដែលត្រូវនិយាយរបស់ KDE +Comment[mk]=Даемон на KDE за текст-во-говор +Comment[ms]=Daemon Teks Ke Tutur KDE +Comment[nb]=KDE tekst-til-tale-nisse +Comment[nds]=Vörlees-Dämoon vun KDE +Comment[ne]=केडीई पाठ वाचक डेइमन +Comment[nl]=KDE Tekst-tot-spraak-daemon +Comment[pa]=KDE ਪਾਠ ਤੋਂ ਬੋਲੀ ਡਾਈਮੋਨ +Comment[pl]=Usługa odczytywania tekstu dla KDE +Comment[pt]=Servidor do Texto para Fala do KDE +Comment[pt_BR]=Serviço de Conversão de Texto para Fala do KDE +Comment[ru]=Служба синтеза речи +Comment[sk]=Démon KDE text-na-reč +Comment[sl]=Demon KDE za besedilo v govor +Comment[sr]=KDE-ов демон за текст-у-говор +Comment[sr@Latn]=KDE-ov demon za tekst-u-govor +Comment[sv]=KDE:s text-till-tal demon +Comment[ta]=கேடியி உரையில் இருந்து பேச்சு டெமான் +Comment[tg]=Демон таҳлили овоз +Comment[tr]=KDE Metinden Konuşmaya Artalan Süreci +Comment[uk]=Демон KDE синтезу мовлення з тексту +Comment[vi]=Trình nền Văn bản sang Tiếng nói KDE +Comment[zh_TW]=KDE 文字轉語音的常駐精靈 + diff --git a/kttsd/kttsd/kttsd.h b/kttsd/kttsd/kttsd.h new file mode 100644 index 0000000..bdbd756 --- /dev/null +++ b/kttsd/kttsd/kttsd.h @@ -0,0 +1,686 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KTTSD main class + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + ******************************************************************************/ + +/*************************************************************************** + * * + * 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 _KTTSD_H_ +#define _KTTSD_H_ + +#include "speechdata.h" +#include "talkermgr.h" +#include "speaker.h" +#include "kspeech.h" + +/** +* KTTSD - the KDE Text-to-speech Deamon. +* +* Provides the capability for applications to speak text. +* Applications may speak text by sending DCOP messages to application "kttsd" object "KSpeech". +* +* @author José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> +* @author Olaf Schmidt <ojschmidt@kde.org> +* @author Gary Cramblitt <garycramblitt@comcast.net> +*/ + +class KTTSD : public QObject, virtual public KSpeech +{ + Q_OBJECT + K_DCOP + + public: + /** + * Constructor. + * + * Create objects, speechData and speaker. + * Start thread + */ + KTTSD(const QCString& objId, QObject *parent=0, const char *name=0); + + /** + * Destructor. + * + * Terminate speaker thread. + */ + ~KTTSD(); + + /** DCOP exported functions for kspeech interface **/ + + /** + * Determine whether the currently-configured speech plugin supports a speech markup language. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * @param markupType The kttsd code for the desired speech markup language. + * @return True if the plugin currently configured for the indicated + * talker supports the indicated speech markup language. + * @see kttsdMarkupType + */ + virtual bool supportsMarkup(const QString &talker=NULL, const uint markupType = 0) const; + + /** + * Determine whether the currently-configured speech plugin supports markers in speech markup. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * @return True if the plugin currently configured for the indicated + * talker supports markers. + */ + virtual bool supportsMarkers(const QString &talker=NULL) const; + + /** + * Say a message as soon as possible, interrupting any other speech in progress. + * IMPORTANT: This method is reserved for use by Screen Readers and should not be used + * by any other applications. + * @param msg The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * + * If an existing Screen Reader output is in progress, it is stopped and discarded and + * replaced with this new message. + */ + virtual ASYNC sayScreenReaderOutput(const QString &msg, const QString &talker=NULL); + + /** + * Say a warning. The warning will be spoken when the current sentence + * stops speaking and takes precedence over Messages and regular text. Warnings should only + * be used for high-priority messages requiring immediate user attention, such as + * "WARNING. CPU is overheating." + * @param warning The warning to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + */ + virtual ASYNC sayWarning(const QString &warning, const QString &talker=NULL); + + /** + * Say a message. The message will be spoken when the current sentence stops speaking + * but after any warnings have been spoken. + * Messages should be used for one-shot messages that can't wait for + * normal text messages to stop speaking, such as "You have mail.". + * @param message The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no talker has been configured for the specified Talker code, + * defaults to the closest matching talker. + */ + virtual ASYNC sayMessage(const QString &message, const QString &talker=NULL); + + /** + * Sets the GREP pattern that will be used as the sentence delimiter. + * @param delimiter A valid GREP pattern. + * + * The default sentence delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])\\s + @endverbatim + * + * Note that backward slashes must be escaped. + * + * Changing the sentence delimiter does not affect other applications. + * @see sentenceparsing + */ + virtual ASYNC setSentenceDelimiter(const QString &delimiter); + + /** + * Queue a text job. Does not start speaking the text. + * @param text The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default plugin. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @return Job number. + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. + * Call @ref getTextCount to retrieve the sentence count after calling setText. + * + * The text may contain speech mark language, such as Sable, JSML, or SMML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + * + * Call @ref startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * @see getTextCount + * @see startText + */ + virtual uint setText(const QString &text, const QString &talker=NULL); + + /** + * Say a plain text job. This is a convenience method that + * combines @ref setText and @ref startText into a single call. + * @param text The message to be spoken. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default plugin. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @return Job number. + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call @ref setSentenceDelimiter to change the sentence delimiter prior to + * calling setText. + * Call @ref getTextCount to retrieve the sentence count after calling setText. + * + * The text may contain speech mark language, such as Sable, JSML, or SSML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + * + * The job is marked speakable. + * If there are other speakable jobs preceeding this one in the queue, + * those jobs continue speaking and when finished, this job will begin speaking. + * If there are no other speakable jobs preceeding this one, it begins speaking. + * + * @see getTextCount + * + * @since KDE 3.5 + */ + virtual uint sayText(const QString &text, const QString &talker); + + /** + * Adds another part to a text job. Does not start speaking the text. + * (thread safe) + * @param text The message to be spoken. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return Part number for the added part. Parts are numbered starting at 1. + * + * The text is parsed into individual sentences. Call getTextCount to retrieve + * the sentence count. Call startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * @see setText. + * @see startText. + */ + int appendText(const QString &text, const uint jobNum=0); + + /** + * Queue a text job from the contents of a file. Does not start speaking the text. + * @param filename Full path to the file to be spoken. May be a URL. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @param encoding Name of the encoding to use when reading the file. If + * NULL or Empty, uses default stream encoding. + * @return Job number. 0 if an error occurs. + * + * Plain text is parsed into individual sentences using the current sentence delimiter. + * Call @ref setSentenceDelimiter to change the sentence delimiter prior to calling setText. + * Call @ref getTextCount to retrieve the sentence count after calling setText. + * + * The text may contain speech mark language, such as Sable, JSML, or SMML, + * provided that the speech plugin/engine support it. In this case, + * sentence parsing follows the semantics of the markup language. + * + * Call @ref startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * @see getTextCount + * @see startText + */ + virtual uint setFile(const QString &filename, const QString &talker=NULL, + const QString& encoding=NULL); + + /** + * Get the number of sentences in a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return The number of sentences in the job. -1 if no such job. + * + * The sentences of a job are given sequence numbers from 1 to the number returned by this + * method. The sequence numbers are emitted in the @ref sentenceStarted and + * @ref sentenceFinished signals. + */ + virtual int getTextCount(const uint jobNum=0); + + /** + * Get the job number of the current text job. + * @return Job number of the current text job. 0 if no jobs. + * + * Note that the current job may not be speaking. See @ref isSpeakingText. + * @see getTextJobState. + * @see isSpeakingText + */ + virtual uint getCurrentTextJob(); + + /** + * Get the number of jobs in the text job queue. + * @return Number of text jobs in the queue. 0 if none. + */ + virtual uint getTextJobCount(); + + /** + * Get a comma-separated list of text job numbers in the queue. + * @return Comma-separated list of text job numbers in the queue. + */ + virtual QString getTextJobNumbers(); + + /** + * Get the state of a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return State of the job. -1 if invalid job number. + * + * @see kttsdJobState + */ + virtual int getTextJobState(const uint jobNum=0); + + /** + * Get information about a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return A QDataStream containing information about the job. + * Blank if no such job. + * + * The stream contains the following elements: + * - int state Job state. + * - QCString appId DCOP senderId of the application that requested the speech job. + * - QString talker Language code in which to speak the text. + * - int seq Current sentence being spoken. Sentences are numbered starting at 1. + * - int sentenceCount Total number of sentences in the job. + * - int partNum Current part of the job begin spoken. Parts are numbered starting at 1. + * - int partCount Total number of parts in the job. + * + * Note that sequence numbers apply to the entire job. They do not start from 1 at the beginning of + * each part. + * + * The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talker; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + @endverbatim + */ + virtual QByteArray getTextJobInfo(const uint jobNum=0); + + /** + * Given a Talker Code, returns the Talker ID of the talker that would speak + * a text job with that Talker Code. + * @param talkerCode Talker Code. + * @return Talker ID of the talker that would speak the text job. + */ + virtual QString talkerCodeToTalkerId(const QString& talkerCode); + + /** + * Return a sentence of a job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @param seq Sequence number of the sentence. + * @return The specified sentence in the specified job. If not such + * job or sentence, returns "". + */ + virtual QString getTextJobSentence(const uint jobNum=0, const uint seq=1); + + /** + * Determine if kttsd is currently speaking any text jobs. + * @return True if currently speaking any text jobs. + */ + virtual bool isSpeakingText() const; + + /** + * Remove a text job from the queue. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is deleted from the queue and the @ref textRemoved signal is emitted. + * + * If there is another job in the text queue, and it is marked speakable, + * that job begins speaking. + */ + virtual ASYNC removeText(const uint jobNum=0); + + /** + * Start a text job at the beginning. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * Rewinds the job to the beginning. + * + * The job is marked speakable. + * If there are other speakable jobs preceeding this one in the queue, + * those jobs continue speaking and when finished, this job will begin speaking. + * If there are no other speakable jobs preceeding this one, it begins speaking. + * + * The @ref textStarted signal is emitted when the text job begins speaking. + * When all the sentences of the job have been spoken, the job is marked for deletion from + * the text queue and the @ref textFinished signal is emitted. + */ + virtual ASYNC startText(const uint jobNum=0); + + /** + * Stop a text job and rewind to the beginning. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText + * is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. + * Depending upon the speech engine and plugin used, speeking may not stop immediately + * (it might finish the current sentence). + */ + virtual ASYNC stopText(const uint jobNum=0); + + /** + * Pause a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is marked as paused and will not be speakable until @ref resumeText or + * @ref startText is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. + * Depending upon the speech engine and plugin used, speeking may not stop immediately + * (it might finish the current sentence). + * @see resumeText + */ + virtual ASYNC pauseText(const uint jobNum=0); + + /** + * Start or resume a text job where it was paused. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * The job is marked speakable. + * + * If the job is currently speaking, or is waiting to be spoken (speakable + * state), the resumeText() call is ignored. + * + * If the job is currently queued, or is finished, it is the same as calling + * @ref startText . + * + * If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, + * when finished this job will begin speaking where it left off. + * + * The @ref textResumed signal is emitted when the job resumes. + * @see pauseText + */ + virtual ASYNC resumeText(const uint jobNum=0); + + /** + * Get a list of the talkers configured in KTTS. + * @return A QStringList of fully-specified talker codes, one + * for each talker user has configured. + * + * @see talkers + */ + virtual QStringList getTalkers(); + + /** + * Change the talker for a text job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @param talker New code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + */ + virtual ASYNC changeTextTalker(const QString &talker, uint jobNum=0); + + /** + * Get the user's default talker. + * @return A fully-specified talker code. + * + * @see talkers + * @see getTalkers + */ + virtual QString userDefaultTalker(); + + /** + * Move a text job down in the queue so that it is spoken later. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * + * If the job is currently speaking, it is paused. + * If the next job in the queue is speakable, it begins speaking. + */ + virtual ASYNC moveTextLater(const uint jobNum=0); + + /** + * Jump to the first sentence of a specified part of a text job. + * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return Part number of the part actually jumped to. + * + * If partNum is greater than the number of parts in the job, jumps to last part. + * If partNum is 0, does nothing and returns the current part number. + * If no such job, does nothing and returns 0. + * Does not affect the current speaking/not-speaking state of the job. + */ + int jumpToTextPart(const int partNum, const uint jobNum=0); + + /** + * Advance or rewind N sentences in a text job. + * @param n Number of sentences to advance (positive) or rewind (negative) in the job. + * @param jobNum Job number of the text job. + * If zero, applies to the last job queued by the application, + * but if no such job, applies to the current job (if any). + * @return Sequence number of the sentence actually moved to. Sequence numbers + * are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + */ + uint moveRelTextSentence(const int n, const uint jobNum=0); + + /** + * Add the clipboard contents to the text queue and begin speaking it. + */ + virtual ASYNC speakClipboard(); + + /** + * Displays the %KTTS Manager dialog. In this dialog, the user may backup or skip forward in + * any text job by sentence or paragraph, rewind jobs, pause or resume jobs, or + * delete jobs. + */ + virtual void showDialog(); + + /** + * Stop the service. + */ + virtual void kttsdExit(); + + /** + * Re-start %KTTSD. + */ + virtual void reinit(); + + /** + * Return the KTTSD deamon version number. + * @since KDE 3.5 + */ + virtual QString version(); + + protected: + + k_dcop: + /** + * This signal is emitted by KNotify when a notification event occurs. + */ + void notificationSignal(const QString &event, const QString &fromApp, + const QString &text, const QString &sound, const QString &file, + const int present, const int level, const int winId, const int eventId ); + + private slots: + /* + * These functions are called whenever + * the status of the speaker object has changed + */ + void slotSentenceStarted(QString text, QString language, + const QCString& appId, const uint jobNum, const uint seq); + void slotSentenceFinished(const QCString& appId, const uint jobNum, const uint seq); + + /* + * These functions are called whenever + * the status of the speechData object has changed + */ + void slotTextSet(const QCString& appId, const uint jobNum); + void slotTextAppended(const QCString& appId, const uint jobNum, const int partNum); + void slotTextStarted(const QCString& appId, const uint jobNum); + void slotTextFinished(const QCString& appId, const uint jobNum); + void slotTextStopped(const QCString& appId, const uint jobNum); + void slotTextPaused(const QCString& appId, const uint jobNum); + void slotTextResumed(const QCString& appId, const uint jobNum); + void slotTextRemoved(const QCString& appId, const uint jobNum); + + /* + * Fires whenever user clicks Apply or OK buttons in Settings dialog. + */ + void configCommitted(); + + private: + /* + * Checks if KTTSD is ready to speak and at least one talker is configured. + * If not, user is prompted to display the configuration dialog. + */ + bool ready(); + + /* + * Create and initialize the SpeechData object. + */ + bool initializeSpeechData(); + + /* + * Create and initialize the TalkerMgr object. + */ + bool initializeTalkerMgr(); + + /* + * Create and initialize the speaker. + */ + bool initializeSpeaker(); + + /* + * Returns the senderId (appId) of the DCOP application that called us. + * @return appId The DCOP sendId of calling application. NULL if called internally by kttsd itself. + */ + const QCString getAppId(); + + /* + * If a job number is 0, returns the default job number for a command. + * Returns the job number of the last job queued by the application, or if + * no such job, the current job number. + * @return Default job number. 0 if no such job. + */ + uint applyDefaultJobNum(const uint jobNum); + + /* + * Fixes a talker argument passed in via dcop. + * If NULL or "0" return QString::null. + */ + QString fixNullString(const QString &talker) const; + + /* + * SpeechData containing all the data and the manipulating methods for all KTTSD + */ + SpeechData* m_speechData; + + /* + * TalkerMgr keeps a list of all the Talkers (synth plugins). + */ + TalkerMgr* m_talkerMgr; + + /* + * Speaker that will be run as another thread, actually saying the messages, warnings, and texts + */ + Speaker* m_speaker; +}; + +// kspeech is obsolete. Applications should use KSpeech instead. +class kspeech : public QObject, virtual public KSpeech +{ + Q_OBJECT + K_DCOP + + public: + // Constructor. + kspeech(const QCString& objId, QObject *parent=0, const char *name=0); + + // Destructor. + ~kspeech(); + + // Delegate all DCOP methods to KTTSD object. + virtual bool supportsMarkup(const QString &talker, uint markupType = 0) const; + virtual bool supportsMarkers(const QString &talker) const; + virtual ASYNC sayScreenReaderOutput(const QString &msg, const QString &talker); + virtual ASYNC sayWarning(const QString &warning, const QString &talker); + virtual ASYNC sayMessage(const QString &message, const QString &talker); + virtual ASYNC setSentenceDelimiter(const QString &delimiter); + virtual uint setText(const QString &text, const QString &talker); + virtual uint sayText(const QString &text, const QString &talker); + virtual int appendText(const QString &text, uint jobNum=0); + virtual uint setFile(const QString &filename, const QString &talker, + const QString& encoding); + virtual int getTextCount(uint jobNum=0); + virtual uint getCurrentTextJob(); + virtual uint getTextJobCount(); + virtual QString getTextJobNumbers(); + virtual int getTextJobState(uint jobNum=0); + virtual QByteArray getTextJobInfo(uint jobNum=0); + virtual QString talkerCodeToTalkerId(const QString& talkerCode); + virtual QString getTextJobSentence(uint jobNum=0, uint seq=0); + virtual bool isSpeakingText() const; + virtual ASYNC removeText(uint jobNum=0); + virtual ASYNC startText(uint jobNum=0); + virtual ASYNC stopText(uint jobNum=0); + virtual ASYNC pauseText(uint jobNum=0); + virtual ASYNC resumeText(uint jobNum=0); + virtual QStringList getTalkers(); + virtual ASYNC changeTextTalker(const QString &talker, uint jobNum=0 ); + virtual QString userDefaultTalker(); + virtual ASYNC moveTextLater(uint jobNum=0); + virtual int jumpToTextPart(int partNum, uint jobNum=0); + virtual uint moveRelTextSentence(int n, uint jobNum=0); + virtual ASYNC speakClipboard(); + virtual void showDialog(); + virtual void kttsdExit(); + virtual void reinit(); + virtual QString version(); + + private: + KTTSD m_kttsd; +}; + +#endif // _KTTSD_H_ diff --git a/kttsd/kttsd/main.cpp b/kttsd/kttsd/main.cpp new file mode 100644 index 0000000..3c47df4 --- /dev/null +++ b/kttsd/kttsd/main.cpp @@ -0,0 +1,68 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Where the main function for KTTSD resides. + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kuniqueapplication.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <klocale.h> +#include <dcopclient.h> + +#include "kttsd.h" + +int main (int argc, char *argv[]){ + KLocale::setMainCatalogue("kttsd"); + KAboutData aboutdata("kttsd", I18N_NOOP("kttsd"), + "0.3.5.2", I18N_NOOP("Text-to-speech synthesis deamon"), + KAboutData::License_GPL, "(C) 2002, José Pablo Ezequiel Fernández"); + aboutdata.addAuthor("José Pablo Ezequiel Fernández",I18N_NOOP("Original Author"),"pupeno@pupeno.com"); + aboutdata.addAuthor("Gary Cramblitt", I18N_NOOP("Maintainer"),"garycramblitt@comcast.net"); + aboutdata.addAuthor("Gunnar Schmi Dt", I18N_NOOP("Contributor"),"gunnar@schmi-dt.de"); + aboutdata.addAuthor("Olaf Schmidt", I18N_NOOP("Contributor"),"ojschmidt@kde.org"); + aboutdata.addAuthor("Paul Giannaros", I18N_NOOP("Contributor"), "ceruleanblaze@gmail.com"); + aboutdata.addCredit("Jorge Luis Arzola", I18N_NOOP("Testing"), "arzolacub@hotmail.com"); + aboutdata.addCredit("David Powell", I18N_NOOP("Testing"), "achiestdragon@gmail.com"); + + KCmdLineArgs::init( argc, argv, &aboutdata ); + // KCmdLineArgs::addCmdLineOptions( options ); + KUniqueApplication::addCmdLineOptions(); + + if(!KUniqueApplication::start()){ + kdDebug() << "KTTSD is already running" << endl; + return (0); + } + + KUniqueApplication app; + // This app is started automatically, no need for session management + app.disableSessionManagement(); + // TODO: kspeech is obsolete. Use KSpeech instead. For backwards compatibility, + // kspeech creates the "real" KSpeech object (KTTSD). At some point in the future, + // change following statement to + // KTTSD *service = new KTTSD("KSpeech"); + kspeech *service = new kspeech("kspeech"); + + // kdDebug() << "Entering event loop." << endl; + return app.exec(); + delete service; +} diff --git a/kttsd/kttsd/speaker.cpp b/kttsd/kttsd/speaker.cpp new file mode 100644 index 0000000..b965a9e --- /dev/null +++ b/kttsd/kttsd/speaker.cpp @@ -0,0 +1,1701 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Speaker class. + This class is in charge of getting the messages, warnings and text from + the queue and call the plug ins function to actually speak the texts. + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + ******************************************************************************/ + +/****************************************************************************** + * * + * 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. * + * * + ******************************************************************************/ + +// Qt includes. +#include <qfile.h> +#include <qtimer.h> +#include <qdir.h> + +// KDE includes. +#include <kdebug.h> +#include <klocale.h> +#include <kparts/componentfactory.h> +#include <ktrader.h> +#include <kapplication.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +//#include <kio/job.h> + +// KTTSD includes. +#include "player.h" +#include "speaker.h" +#include "speaker.moc" +#include "talkermgr.h" +#include "utils.h" + +/** +* The Speaker class takes sentences from the text queue, messages from the +* messages queue, warnings from the warnings queue, and Screen Reader +* output and places them into an internal "utterance queue". It then +* loops through this queue, farming the work off to the plugins. +* It tries to optimize processing so as to keep the plugins busy as +* much as possible, while ensuring that only one stream of audio is +* heard at any one time. +* +* The message queues are maintained in the SpeechData class. +* +* Text jobs in the text queue each have a state (queued, speakable, +* speaking, paused, finished). Each plugin has a state (idle, saying, synthing, +* or finished). And finally, each utterance has a state (waiting, saying, +* synthing, playing, finished). It can be confusing if you are not aware +* of all these states. +* +* Speaker takes some pains to ensure speech is spoken in the correct order, +* namely Screen Reader Output has the highest priority, Warnings are next, +* Messages are next, and finally regular text jobs. Since Screen Reader +* Output, Warnings, and Messages can be queued in the middle of a text +* job, Speaker must be prepared to reorder utterances in its queue. +* +* At the same time, it must issue the signals to inform programs +* what is happening. +* +* Finally, users can pause, restart, delete, advance, or rewind text jobs +* and Speaker must respond to these commands. In some cases, utterances that +* have already been synthesized and are ready for audio output must be +* discarded in response to these commands. +* +* Some general guidelines for programmers modifying this code: +* - Avoid blocking at all cost. If a plugin won't stopText, keep going. +* You might have to wait for the plugin to complete before asking it +* to perform the next operation, but in the meantime, there might be +* other useful work that can be performed. +* - In no case allow the main thread Qt event loop to block. +* - Plugins that do not have asynchronous support are wrapped in the +* ThreadedPlugin class, which attempts to make them as asynchronous as +* it can, but there are limits. +* - doUtterances is the main worker method. If in doubt, call doUtterances. +* - Because Speaker controls the ordering of utterances, most sequence-related +* signals must be emitted by Speaker; not SpeechData. Only the add +* and delete job-related signals eminate from SpeechData. +* - The states of the 3 types of objects mentioned above (jobs, utterances, +* and plugins) can interact in subtle ways. Test fully. For example, while +* a text job might be in a paused state, the plugin could be synthesizing +* in anticipation of resume, or sythesizing a Warning, Message, or +* Screen Reader Output. Meanwhile, while one of the utterances might +* have a paused state, others from the same job could be synthing, waiting, +* or finished. +* - There can be more than one Audio Player object in existence at one time, although +* there must never be more than one actually playing at one time. For +* example, an Audio Player playing an utterance from a text job can be +* in a paused state, while another Audio Player is playing a Screen Reader +* Output utterance. Finally, since some plugins do their own audio, it +* might be that none of the Audio Player objects are playing. +*/ + +/* Public Methods ==========================================================*/ + +/** +* Constructor. +* Loads plugins. +*/ +Speaker::Speaker( SpeechData*speechData, TalkerMgr* talkerMgr, + QObject *parent, const char *name) : + QObject(parent, name), + m_speechData(speechData), + m_talkerMgr(talkerMgr) +{ + // kdDebug() << "Running: Speaker::Speaker()" << endl; + m_exitRequested = false; + m_textInterrupted = false; + m_currentJobNum = 0; + m_lastAppId = 0; + m_lastJobNum = 0; + m_lastSeq = 0; + m_timer = new QTimer(this, "kttsdAudioTimer"); + m_speechData->config->setGroup("General"); + m_playerOption = m_speechData->config->readNumEntry("AudioOutputMethod", 0); // default to aRts. + // Map 50% to 100% onto 2.0 to 0.5. + m_audioStretchFactor = 1.0/(float(m_speechData->config->readNumEntry("AudioStretchFactor", 100))/100.0); + switch (m_playerOption) + { + case 0: break; + case 1: + m_speechData->config->setGroup("GStreamerPlayer"); + m_sinkName = m_speechData->config->readEntry("SinkName", "osssink"); + m_periodSize = m_speechData->config->readNumEntry("PeriodSize", 128); + m_periods = m_speechData->config->readNumEntry("Periods", 8); + m_playerDebugLevel = m_speechData->config->readNumEntry("DebugLevel", 1); + break; + case 2: + m_speechData->config->setGroup("ALSAPlayer"); + m_sinkName = m_speechData->config->readEntry("PcmName", "default"); + if ("custom" == m_sinkName) + m_sinkName = m_speechData->config->readEntry("CustomPcmName", "default"); + m_periodSize = m_speechData->config->readNumEntry("PeriodSize", 128); + m_periods = m_speechData->config->readNumEntry("Periods", 8); + m_playerDebugLevel = m_speechData->config->readNumEntry("DebugLevel", 1); + break; + case 3: + m_speechData->config->setGroup("aKodePlayer"); + m_sinkName = m_speechData->config->readEntry("SinkName", "auto"); + m_periodSize = m_speechData->config->readNumEntry("PeriodSize", 128); + m_periods = m_speechData->config->readNumEntry("Periods", 8); + m_playerDebugLevel = m_speechData->config->readNumEntry("DebugLevel", 1); + break; + } + // Connect timer timeout signal. + connect(m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout())); + + // Connect plugins to slots. + QPtrList<PlugInProc> plugins = m_talkerMgr->getLoadedPlugIns(); + const int pluginsCount = plugins.count(); + for (int ndx = 0; ndx < pluginsCount; ++ndx) + { + PlugInProc* speech = plugins.at(ndx); + connect(speech, SIGNAL(synthFinished()), + this, SLOT(slotSynthFinished())); + connect(speech, SIGNAL(sayFinished()), + this, SLOT(slotSayFinished())); + connect(speech, SIGNAL(stopped()), + this, SLOT(slotStopped())); + connect(speech, SIGNAL(error(bool, const QString&)), + this, SLOT(slotError(bool, const QString&))); + } +} + +/** +* Destructor. +*/ +Speaker::~Speaker(){ + // kdDebug() << "Running: Speaker::~Speaker()" << endl; + m_timer->stop(); + delete m_timer; + if (!m_uttQueue.isEmpty()) + { + uttIterator it; + for (it = m_uttQueue.begin(); it != m_uttQueue.end(); ) + it = deleteUtterance(it); + } +} + +/** + * Tells the speaker it is requested to exit. + * TODO: I don't think this actually accomplishes anything. + */ +void Speaker::requestExit(){ + // kdDebug() << "Speaker::requestExit: Running" << endl; + m_exitRequested = true; +} + +/** + * Main processing loop. Dequeues utterances and sends them to the + * plugins and/or Audio Player. + */ +void Speaker::doUtterances() +{ + // kdDebug() << "Running: Speaker::doUtterances()" << endl; + + // Used to prevent exiting prematurely. + m_again = true; + + while(m_again && !m_exitRequested) + { + m_again = false; + + if (m_exitRequested) + { + // kdDebug() << "Speaker::run: exiting due to request 1." << endl; + return; + } + + uttIterator it; + uttIterator itBegin; + uttIterator itEnd = 0; // Init to zero to avoid compiler warning. + + // If Screen Reader Output is waiting, we need to process it ASAP. + if (m_speechData->screenReaderOutputReady()) + { + m_again = getNextUtterance(); + } +// kdDebug() << "Speaker::doUtterances: queue dump:" << endl; +// for (it = m_uttQueue.begin(); it != m_uttQueue.end(); ++it) +// { +// QString pluginState = "no plugin"; +// if (it->plugin) pluginState = pluginStateToStr(it->plugin->getState()); +// QString jobState = +// jobStateToStr(m_speechData->getTextJobState(it->sentence->jobNum)); +// kdDebug() << +// " State: " << uttStateToStr(it->state) << +// "," << pluginState << +// "," << jobState << +// " Type: " << uttTypeToStr(it->utType) << +// " Text: " << it->sentence->text << endl; +// } + + if (!m_uttQueue.isEmpty()) + { + // Delete utterances that are finished. + it = m_uttQueue.begin(); + while (it != m_uttQueue.end()) + { + if (it->state == usFinished) + it = deleteUtterance(it); + else + ++it; + } + // Loop through utterance queue. + int waitingCnt = 0; + int waitingMsgCnt = 0; + int transformingCnt = 0; + bool playing = false; + int synthingCnt = 0; + itEnd = m_uttQueue.end(); + itBegin = m_uttQueue.begin(); + for (it = itBegin; it != itEnd; ++it) + { + uttState utState = it->state; + uttType utType = it->utType; + switch (utState) + { + case usNone: + { + setInitialUtteranceState(*it); + m_again = true; + break; + } + case usWaitingTransform: + { + // Create an XSLT transformer and transform the text. + it->transformer = new SSMLConvert(); + connect(it->transformer, SIGNAL(transformFinished()), + this, SLOT(slotTransformFinished())); + if (it->transformer->transform(it->sentence->text, + it->plugin->getSsmlXsltFilename())) + { + it->state = usTransforming; + ++transformingCnt; + } + else + { + // If an error occurs transforming, skip it. + it->state = usTransforming; + setInitialUtteranceState(*it); + } + m_again = true; + break; + } + case usTransforming: + { + // See if transformer is finished. + if (it->transformer->getState() == SSMLConvert::tsFinished) + { + // Get the transformed text. + it->sentence->text = it->transformer->getOutput(); + // Set next state (usWaitingSynth or usWaitingSay) + setInitialUtteranceState(*it); + m_again = true; + --transformingCnt; + } + break; + } + case usWaitingSignal: + { + // If first in queue, emit signal. + if (it == itBegin) + { + if (utType == utStartOfJob) + { + m_speechData->setTextJobState( + it->sentence->jobNum, KSpeech::jsSpeaking); + if (it->sentence->seq == 0) + emit textStarted(it->sentence->appId, + it->sentence->jobNum); + else + emit textResumed(it->sentence->appId, + it->sentence->jobNum); + } else { + m_speechData->setTextJobState( + it->sentence->jobNum, KSpeech::jsFinished); + emit textFinished(it->sentence->appId, it->sentence->jobNum); + } + it->state = usFinished; + m_again = true; + } + break; + } + case usSynthed: + { + // Don't bother stretching if factor is 1.0. + // Don't bother stretching if SSML. + // TODO: This is because sox mangles SSML pitch settings. Would be nice + // to figure out how to avoid this. + if (m_audioStretchFactor == 1.0 || it->isSSML) + { + it->state = usStretched; + m_again = true; + } + else + { + it->audioStretcher = new Stretcher(); + connect(it->audioStretcher, SIGNAL(stretchFinished()), + this, SLOT(slotStretchFinished())); + if (it->audioStretcher->stretch(it->audioUrl, makeSuggestedFilename(), + m_audioStretchFactor)) + { + it->state = usStretching; + m_again = true; // Is this needed? + } + else + { + // If stretch failed, it is most likely caused by sox not being + // installed. Just skip it. + it->state = usStretched; + m_again = true; + delete it->audioStretcher; + it->audioStretcher= 0; + } + } + break; + } + case usStretching: + { + // See if Stretcher is finished. + if (it->audioStretcher->getState() == Stretcher::ssFinished) + { + QFile::remove(it->audioUrl); + it->audioUrl = it->audioStretcher->getOutFilename(); + it->state = usStretched; + delete it->audioStretcher; + it->audioStretcher = 0; + m_again = true; + } + break; + } + case usStretched: + { + // If first in queue, start playback. + if (it == itBegin) + { + if (startPlayingUtterance(it)) + { + playing = true; + m_again = true; + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + break; + } + case usPlaying: + { + playing = true; + break; + } + case usPaused: + case usPreempted: + { + if (!playing) + { + if (startPlayingUtterance(it)) + { + playing = true; + m_again = true; + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + break; + } + case usWaitingSay: + { + // If first in queue, start it. + if (it == itBegin) + { + int jobState = + m_speechData->getTextJobState(it->sentence->jobNum); + if ((jobState == KSpeech::jsSpeaking) || + (jobState == KSpeech::jsSpeakable)) + { + if (it->plugin->getState() == psIdle) + { + // Set job to speaking state and set sequence number. + mlText* sentence = it->sentence; + m_currentJobNum = sentence->jobNum; + m_speechData->setTextJobState(m_currentJobNum, KSpeech::jsSpeaking); + m_speechData->setJobSequenceNum(m_currentJobNum, sentence->seq); + prePlaySignals(it); + // kdDebug() << "Async synthesis and audibilizing." << endl; + it->state = usSaying; + playing = true; + it->plugin->sayText(it->sentence->text); + m_again = true; + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + } else { + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + break; + } + case usWaitingSynth: + { + // TODO: If the synth is busy and the waiting text is screen + // reader output, it would be nice to call the synth's + // stopText() method. However, some of the current plugins + // have horrible startup times, so we won't do that for now. + if (it->plugin->getState() == psIdle) + { + // kdDebug() << "Async synthesis." << endl; + it->state = usSynthing; + ++synthingCnt; + it->plugin->synthText(it->sentence->text, + makeSuggestedFilename()); + m_again = true; + } + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + break; + } + case usSaying: + { + // See if synthesis and audibilizing is finished. + if (it->plugin->getState() == psFinished) + { + it->plugin->ackFinished(); + it->state = usFinished; + m_again = true; + } else { + playing = true; + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + } + break; + } + case usSynthing: + { + // See if synthesis is completed. + if (it->plugin->getState() == psFinished) + { + it->audioUrl = KStandardDirs::realFilePath(it->plugin->getFilename()); + // kdDebug() << "Speaker::doUtterances: synthesized filename: " << it->audioUrl << endl; + it->plugin->ackFinished(); + it->state = usSynthed; + m_again = true; + } else ++synthingCnt; + ++waitingCnt; + if (utType == utWarning || utType == utMessage) ++waitingMsgCnt; + break; + } + case usFinished: break; + } + } + // See if there are any messages or warnings to process. + // We keep up to 2 such utterances in the queue. + if ((waitingMsgCnt < 2) && (transformingCnt < 3)) + { + if (m_speechData->warningInQueue() || m_speechData->messageInQueue()) + { + getNextUtterance(); + m_again = true; + } + } + // Try to keep at least two utterances in the queue waiting to be played, + // and no more than 3 transforming at one time. + if ((waitingCnt < 2) && (transformingCnt < 3)) + if (getNextUtterance()) m_again = true; + } else { + // See if another utterance is ready to be worked on. + // If so, loop again since we've got work to do. + m_again = getNextUtterance(); + } + } + // kdDebug() << "Speaker::doUtterances: exiting." << endl; +} + +/** + * Determine if kttsd is currently speaking any text jobs. + * @return True if currently speaking any text jobs. + */ +bool Speaker::isSpeakingText() +{ + return (m_speechData->getTextJobState(m_currentJobNum) == KSpeech::jsSpeaking); +} + +/** + * Get the job number of the current text job. + * @return Job number of the current text job. 0 if no jobs. + * + * Note that the current job may not be speaking. See @ref isSpeakingText. + */ +uint Speaker::getCurrentTextJob() { return m_currentJobNum; } + +/** + * Remove a text job from the queue. + * @param jobNum Job number of the text job. + * + * The job is deleted from the queue and the @ref textRemoved signal is emitted. + * + * If there is another job in the text queue, and it is marked speakable, + * that job begins speaking. + */ +void Speaker::removeText(const uint jobNum) +{ + deleteUtteranceByJobNum(jobNum); + m_speechData->removeText(jobNum); + doUtterances(); +} + +/** + * Start a text job at the beginning. + * @param jobNum Job number of the text job. + * + * Rewinds the job to the beginning. + * + * The job is marked speakable. + * If there are other speakable jobs preceeding this one in the queue, + * those jobs continue speaking and when finished, this job will begin speaking. + * If there are no other speakable jobs preceeding this one, it begins speaking. + * + * The @ref textStarted signal is emitted when the text job begins speaking. + * When all the sentences of the job have been spoken, the job is marked for deletion from + * the text queue and the @ref textFinished signal is emitted. + */ +void Speaker::startText(const uint jobNum) +{ + deleteUtteranceByJobNum(jobNum); + m_speechData->setJobSequenceNum(jobNum, 1); + m_speechData->setTextJobState(jobNum, KSpeech::jsSpeakable); + if (m_lastJobNum == jobNum) + { + // kdDebug() << "Speaker::startText: startText called on speaking job " << jobNum << endl; + m_lastJobNum = 0; + m_lastAppId = 0; + m_lastSeq = 0; + } + doUtterances(); +} + +/** + * Stop a text job and rewind to the beginning. + * @param jobNum Job number of the text job. + * + * The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText + * is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. + * Depending upon the speech engine and plugin used, speeking may not stop immediately + * (it might finish the current sentence). + */ +void Speaker::stopText(const uint jobNum) +{ + bool emitSignal = (m_speechData->getTextJobState(jobNum) == KSpeech::jsSpeaking); + deleteUtteranceByJobNum(jobNum); + m_speechData->setJobSequenceNum(jobNum, 1); + m_speechData->setTextJobState(jobNum, KSpeech::jsQueued); + if (emitSignal) textStopped(m_speechData->getAppIdByJobNum(jobNum), jobNum); + // Call doUtterances to process other jobs. + doUtterances(); +} + +/** + * Pause a text job. + * @param jobNum Job number of the text job. + * + * The job is marked as paused and will not be speakable until @ref resumeText or + * @ref startText is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. + * Depending upon the speech engine and plugin used, speeking may not stop immediately + * (it might finish the current sentence). + * @see resumeText + */ +void Speaker::pauseText(const uint jobNum) +{ + bool emitSignal = (m_speechData->getTextJobState(jobNum) == KSpeech::jsSpeaking); + pauseUtteranceByJobNum(jobNum); + kdDebug() << "Speaker::pauseText: setting Job State of job " << jobNum << " to jsPaused" << endl; + m_speechData->setTextJobState(jobNum, KSpeech::jsPaused); + if (emitSignal) textPaused(m_speechData->getAppIdByJobNum(jobNum),jobNum); +} + +/** + * Start or resume a text job where it was paused. + * @param jobNum Job number of the text job. + * + * The job is marked speakable. + * + * If the job is currently speaking, or is waiting to be spoken (speakable + * state), the resumeText() call is ignored. + * + * If the job is currently queued, or is finished, it is the same as calling + * @ref startText . + * + * If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, + * when finished this job will begin speaking where it left off. + * + * The @ref textResumed signal is emitted when the job resumes. + * @see pauseText + */ +void Speaker::resumeText(const uint jobNum) +{ + int state = m_speechData->getTextJobState(jobNum); + switch (state) + { + case KSpeech::jsQueued: + case KSpeech::jsFinished: + startText(jobNum); + break; + case KSpeech::jsSpeakable: + case KSpeech::jsSpeaking: + doUtterances(); + break; + case KSpeech::jsPaused: + if (jobNum == m_currentJobNum) + m_speechData->setTextJobState(jobNum, KSpeech::jsSpeaking); + else + m_speechData->setTextJobState(jobNum, KSpeech::jsSpeakable); + doUtterances(); + break; + } +} + +/** + * Move a text job down in the queue so that it is spoken later. + * @param jobNum Job number of the text job. + * + * If the job is currently speaking, it is paused. + * If the next job in the queue is speakable, it begins speaking. + */ +void Speaker::moveTextLater(const uint jobNum) +{ + if (m_speechData->getTextJobState(jobNum) == KSpeech::jsSpeaking) + m_speechData->setTextJobState(jobNum, KSpeech::jsPaused); + deleteUtteranceByJobNum(jobNum); + m_speechData->moveTextLater(jobNum); + doUtterances(); +} + +/** + * Jump to the first sentence of a specified part of a text job. + * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. + * @param jobNum Job number of the text job. + * @return Part number of the part actually jumped to. + * + * If partNum is greater than the number of parts in the job, jumps to last part. + * If partNum is 0, does nothing and returns the current part number. + * If no such job, does nothing and returns 0. + * Does not affect the current speaking/not-speaking state of the job. + */ +int Speaker::jumpToTextPart(const int partNum, const uint jobNum) +{ + if (partNum == 0) return m_speechData->jumpToTextPart(partNum, jobNum); + deleteUtteranceByJobNum(jobNum); + int pNum = m_speechData->jumpToTextPart(partNum, jobNum); + if (pNum) + { + uint seq = m_speechData->getJobSequenceNum(jobNum); + if (jobNum == m_lastJobNum) + { + if (seq == 0) + m_lastSeq = seq; + else + m_lastSeq = seq - 1; + } + if (jobNum == m_currentJobNum) + { + m_lastJobNum = jobNum; + if (seq == 0) + m_lastSeq = 0; + else + m_lastSeq = seq - 1; + doUtterances(); + } + } + return pNum; +} + +/** + * Advance or rewind N sentences in a text job. + * @param n Number of sentences to advance (positive) or rewind (negative) + * in the job. + * @param jobNum Job number of the text job. + * @return Sequence number of the sentence actually moved to. + * Sequence numbers are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + */ +uint Speaker::moveRelTextSentence(const int n, const uint jobNum) +{ + if (0 == n) + return m_speechData->getJobSequenceNum(jobNum); + else { + deleteUtteranceByJobNum(jobNum); + // TODO: More efficient way to advance one or two sentences, since there is a + // good chance those utterances are already in the queue and synthesized. + uint seq = m_speechData->moveRelTextSentence(n, jobNum); + kdDebug() << "Speaker::moveRelTextSentence: job num: " << jobNum << " moved to seq: " << seq << endl; + if (jobNum == m_lastJobNum) + { + if (seq == 0) + m_lastSeq = seq; + else + m_lastSeq = seq - 1; + } + if (jobNum == m_currentJobNum) + { + m_lastJobNum = jobNum; + if (seq == 0) + m_lastSeq = 0; + else + m_lastSeq = seq - 1; + doUtterances(); + } + return seq; + } +} + +/* Private Methods ==========================================================*/ + +/** + * Converts an utterance state enumerator to a displayable string. + * @param state Utterance state. + */ +QString Speaker::uttStateToStr(uttState state) +{ + switch (state) + { + case usNone: return "usNone"; + case usWaitingTransform: return "usWaitingTransform"; + case usTransforming: return "usTransforming"; + case usWaitingSay: return "usWaitingSay"; + case usWaitingSynth: return "usWaitingSynth"; + case usWaitingSignal: return "usWaitingSignal"; + case usSaying: return "usSaying"; + case usSynthing: return "usSynthing"; + case usSynthed: return "usSynthed"; + case usStretching: return "usStretching"; + case usStretched: return "usStretched"; + case usPlaying: return "usPlaying"; + case usPaused: return "usPaused"; + case usPreempted: return "usPreempted"; + case usFinished: return "usFinished"; + } + return QString::null; +} + +/** + * Converts an utterance type enumerator to a displayable string. + * @param utType Utterance type. + * @return Displayable string for utterance type. + */ +QString Speaker::uttTypeToStr(uttType utType) +{ + switch (utType) + { + case utText: return "utText"; + case utInterruptMsg: return "utInterruptMsg"; + case utInterruptSnd: return "utInterruptSnd"; + case utResumeMsg: return "utResumeMsg"; + case utResumeSnd: return "utResumeSnd"; + case utMessage: return "utMessage"; + case utWarning: return "utWarning"; + case utScreenReader: return "utScreenReader"; + case utStartOfJob: return "utStartOfJob"; + case utEndOfJob: return "utEndOfJob"; + } + return QString::null; +} + +/** + * Converts a plugin state enumerator to a displayable string. + * @param state Plugin state. + * @return Displayable string for plugin state. + */ +QString Speaker::pluginStateToStr(pluginState state) +{ + switch( state ) + { + case psIdle: return "psIdle"; + case psSaying: return "psSaying"; + case psSynthing: return "psSynthing"; + case psFinished: return "psFinished"; + } + return QString::null; +} + +/** + * Converts a job state enumerator to a displayable string. + * @param state Job state. + * @return Displayable string for job state. + */ +QString Speaker::jobStateToStr(int state) +{ + switch ( state ) + { + case KSpeech::jsQueued: return "jsQueued"; + case KSpeech::jsSpeakable: return "jsSpeakable"; + case KSpeech::jsSpeaking: return "jsSpeaking"; + case KSpeech::jsPaused: return "jsPaused"; + case KSpeech::jsFinished: return "jsFinished"; + } + return QString::null; +} + +/** + * Delete any utterances in the queue with this jobNum. + * @param jobNum Job Number of the utterances to delete. + * If currently processing any deleted utterances, stop them. + */ +void Speaker::deleteUtteranceByJobNum(const uint jobNum) +{ + uttIterator it = m_uttQueue.begin(); + while (it != m_uttQueue.end()) + { + if (it->sentence) + { + if (it->sentence->jobNum == jobNum) + it = deleteUtterance(it); + else + ++it; + } else + ++it; + } +} + +/** + * Pause the utterance with this jobNum if it is playing on the Audio Player. + * @param jobNum The Job Number of the utterance to pause. + */ +void Speaker::pauseUtteranceByJobNum(const uint jobNum) +{ + uttIterator itEnd = m_uttQueue.end(); + for (uttIterator it = m_uttQueue.begin(); it != itEnd; ++it) + { + if (it->sentence) // TODO: Why is this necessary? + if (it->sentence->jobNum == jobNum) + if (it->state == usPlaying) + { + if (it->audioPlayer) + if (it->audioPlayer->playing()) + { + m_timer->stop(); + kdDebug() << "Speaker::pauseUtteranceByJobNum: pausing audio player" << endl; + it->audioPlayer->pause(); + kdDebug() << "Speaker::pauseUtteranceByJobNum: Setting utterance state to usPaused" << endl; + it->state = usPaused; + return; + } + // Audio player has finished, but timeout hasn't had a chance + // to clean up. So do nothing, and let timeout do the cleanup. + } + } +} + +/** + * Determines whether the given text is SSML markup. + */ +bool Speaker::isSsml(const QString &text) +{ + return KttsUtils::hasRootElement( text, "speak" ); +} + +/** + * Determines the initial state of an utterance. If the utterance contains + * SSML, the state is set to usWaitingTransform. Otherwise, if the plugin + * supports async synthesis, sets to usWaitingSynth, otherwise usWaitingSay. + * If an utterance has already been transformed, usWaitingTransform is + * skipped to either usWaitingSynth or usWaitingSay. + * @param utt The utterance. + */ +void Speaker::setInitialUtteranceState(Utt &utt) +{ + if ((utt.state != usTransforming) && utt.isSSML) +{ + utt.state = usWaitingTransform; + return; +} + if (utt.plugin->supportsSynth()) + utt.state = usWaitingSynth; + else + utt.state = usWaitingSay; +} + +/** + * Returns true if the given job and sequence number are already in the utterance queue. + */ +bool Speaker::isInUtteranceQueue(uint jobNum, uint seqNum) +{ + uttIterator itEnd = m_uttQueue.end(); + for (uttIterator it = m_uttQueue.begin(); it != itEnd; ++it) + { + if (it->sentence) + { + if (it->sentence->jobNum == jobNum && it->sentence->seq == seqNum) return true; + } + } + return false; +} + +/** + * Gets the next utterance to be spoken from speechdata and adds it to the queue. + * @return True if one or more utterances were added to the queue. + * + * Checks for waiting ScreenReaderOutput, Warnings, Messages, or Text, + * in that order. + * If Warning or Message and interruption messages have been configured, + * adds those to the queue as well. + * Determines which plugin should be used for the utterance. + */ +bool Speaker::getNextUtterance() +{ + bool gotOne = false; + Utt* utt = 0; + if (m_speechData->screenReaderOutputReady()) { + utt = new Utt; + utt->utType = utScreenReader; + utt->sentence = m_speechData->getScreenReaderOutput(); + } else { + if (m_speechData->warningInQueue()) { + utt = new Utt; + utt->utType = utWarning; + utt->sentence = m_speechData->dequeueWarning(); + } else { + if (m_speechData->messageInQueue()) { + utt = new Utt; + utt->utType = utMessage; + utt->sentence = m_speechData->dequeueMessage(); + } else { + uint jobNum = m_lastJobNum; + uint seq = m_lastSeq; + mlText* sentence = m_speechData->getNextSentenceText(jobNum, seq); + // Skip over blank lines. + while (sentence && sentence->text.isEmpty()) + { + jobNum = sentence->jobNum; + seq = sentence->seq; + sentence = m_speechData->getNextSentenceText(jobNum, seq); + } + // If this utterance is already in the queue, it means we have run out of + // stuff to say and are trying to requeue already queued (and waiting stuff). + if (sentence && !isInUtteranceQueue(sentence->jobNum, sentence->seq)) + { + utt = new Utt; + utt->utType = utText; + utt->sentence = sentence; + } + } + } + } + if (utt) + { + gotOne = true; + utt->isSSML = isSsml(utt->sentence->text); + utt->state = usNone; + utt->audioPlayer = 0; + utt->audioStretcher = 0; + utt->audioUrl = QString::null; + utt->plugin = m_talkerMgr->talkerToPlugin(utt->sentence->talker); + // Save some time by setting initial state now. + setInitialUtteranceState(*utt); + // Screen Reader Outputs need to be processed ASAP. + if (utt->utType == utScreenReader) + { + m_uttQueue.insert(m_uttQueue.begin(), *utt); + // Delete any other Screen Reader Outputs in the queue. + // Only one Screen Reader Output at a time. + uttIterator it = m_uttQueue.begin(); + ++it; + while (it != m_uttQueue.end()) + { + if (it->utType == utScreenReader) + it = deleteUtterance(it); + else + ++it; + } + } + // If the new utterance is a Warning or Message... + if ((utt->utType == utWarning) || (utt->utType == utMessage)) + { + uttIterator itEnd = m_uttQueue.end(); + uttIterator it = m_uttQueue.begin(); + bool interrupting = false; + if (it != itEnd) + { + // New Warnings go after Screen Reader Output, other Warnings, + // Interruptions, and in-process text, + // but before Resumes, waiting text or signals. + if (utt->utType == utWarning) + while ( it != itEnd && + ((it->utType == utScreenReader) || + (it->utType == utWarning) || + (it->utType == utInterruptMsg) || + (it->utType == utInterruptSnd))) ++it; + // New Messages go after Screen Reader Output, Warnings, other Messages, + // Interruptions, and in-process text, + // but before Resumes, waiting text or signals. + if (utt->utType == utMessage) + while ( it != itEnd && + ((it->utType == utScreenReader) || + (it->utType == utWarning) || + (it->utType == utMessage) || + (it->utType == utInterruptMsg) || + (it->utType == utInterruptSnd))) ++it; + if (it != itEnd) + if (it->utType == utText && + ((it->state == usPlaying) || + (it->state == usSaying))) ++it; + // If now pointing at a text message, we are interrupting. + // Insert optional Interruption message and sound. + if (it != itEnd) interrupting = (it->utType == utText && it->state != usPaused); + if (interrupting) + { + if (m_speechData->textPreSndEnabled) + { + Utt intrUtt; + intrUtt.sentence = new mlText; + intrUtt.sentence->text = QString::null; + intrUtt.sentence->talker = utt->sentence->talker; + intrUtt.sentence->appId = utt->sentence->appId; + intrUtt.sentence->jobNum = utt->sentence->jobNum; + intrUtt.sentence->seq = 0; + intrUtt.audioUrl = m_speechData->textPreSnd; + intrUtt.audioPlayer = 0; + intrUtt.utType = utInterruptSnd; + intrUtt.isSSML = false; + intrUtt.state = usStretched; + intrUtt.plugin = 0; + it = m_uttQueue.insert(it, intrUtt); + ++it; + } + if (m_speechData->textPreMsgEnabled) + { + Utt intrUtt; + intrUtt.sentence = new mlText; + intrUtt.sentence->text = m_speechData->textPreMsg; + // Interruptions are spoken using default Talker. + intrUtt.sentence->talker = QString::null; + intrUtt.sentence->appId = utt->sentence->appId; + intrUtt.sentence->jobNum = utt->sentence->jobNum; + intrUtt.sentence->seq = 0; + intrUtt.audioUrl = QString::null; + intrUtt.audioPlayer = 0; + intrUtt.utType = utInterruptMsg; + intrUtt.isSSML = isSsml(intrUtt.sentence->text); + intrUtt.plugin = m_talkerMgr->talkerToPlugin(intrUtt.sentence->talker); + intrUtt.state = usNone; + setInitialUtteranceState(intrUtt); + it = m_uttQueue.insert(it, intrUtt); + ++it; + } + } + } + // Insert the new message or warning. + it = m_uttQueue.insert(it, *utt); + ++it; + // Resumption message and sound. + if (interrupting) + { + if (m_speechData->textPostSndEnabled) + { + Utt resUtt; + resUtt.sentence = new mlText; + resUtt.sentence->text = QString::null; + resUtt.sentence->talker = utt->sentence->talker; + resUtt.sentence->appId = utt->sentence->appId; + resUtt.sentence->jobNum = utt->sentence->jobNum; + resUtt.sentence->seq = 0; + resUtt.audioUrl = m_speechData->textPostSnd; + resUtt.audioPlayer = 0; + resUtt.utType = utResumeSnd; + resUtt.isSSML = false; + resUtt.state = usStretched; + resUtt.plugin = 0; + it = m_uttQueue.insert(it, resUtt); + ++it; + } + if (m_speechData->textPostMsgEnabled) + { + Utt resUtt; + resUtt.sentence = new mlText; + resUtt.sentence->text = m_speechData->textPostMsg; + resUtt.sentence->talker = QString::null; + resUtt.sentence->appId = utt->sentence->appId; + resUtt.sentence->jobNum = utt->sentence->jobNum; + resUtt.sentence->seq = 0; + resUtt.audioUrl = QString::null; + resUtt.audioPlayer = 0; + resUtt.utType = utResumeMsg; + resUtt.isSSML = isSsml(resUtt.sentence->text); + resUtt.plugin = m_talkerMgr->talkerToPlugin(resUtt.sentence->talker); + resUtt.state = usNone; + setInitialUtteranceState(resUtt); + it = m_uttQueue.insert(it, resUtt); + } + } + } + // If a text message... + if (utt->utType == utText) + { + // If job number has changed... + if (utt->sentence->jobNum != m_lastJobNum) + { + // If we finished the last job, append End-of-job to the queue, + // which will become a textFinished signal when it is processed. + if (m_lastJobNum) + { + if (m_lastSeq == static_cast<uint>(m_speechData->getTextCount(m_lastJobNum))) + { + Utt jobUtt; + jobUtt.sentence = new mlText; + jobUtt.sentence->text = QString::null; + jobUtt.sentence->talker = QString::null; + jobUtt.sentence->appId = m_lastAppId; + jobUtt.sentence->jobNum = m_lastJobNum; + jobUtt.sentence->seq = 0; + jobUtt.audioUrl = QString::null; + jobUtt.utType = utEndOfJob; + jobUtt.isSSML = false; + jobUtt.plugin = 0; + jobUtt.state = usWaitingSignal; + m_uttQueue.append(jobUtt); + } + } + m_lastJobNum = utt->sentence->jobNum; + m_lastAppId = utt->sentence->appId; + // If we are at beginning of new job, append Start-of-job to queue, + // which will become a textStarted signal when it is processed. + if (utt->sentence->seq == 1) + { + Utt jobUtt; + jobUtt.sentence = new mlText; + jobUtt.sentence->text = QString::null; + jobUtt.sentence->talker = QString::null; + jobUtt.sentence->appId = m_lastAppId; + jobUtt.sentence->jobNum = m_lastJobNum; + jobUtt.sentence->seq = utt->sentence->seq; + jobUtt.audioUrl = QString::null; + jobUtt.utType = utStartOfJob; + jobUtt.isSSML = false; + jobUtt.plugin = 0; + jobUtt.state = usWaitingSignal; + m_uttQueue.append(jobUtt); + } + } + m_lastSeq = utt->sentence->seq; + // Add the new utterance to the queue. + m_uttQueue.append(*utt); + } + delete utt; + } else { + // If no more text to speak, and we finished the last job, issue textFinished signal. + if (m_lastJobNum) + { + if (m_lastSeq == static_cast<uint>(m_speechData->getTextCount(m_lastJobNum))) + { + Utt jobUtt; + jobUtt.sentence = new mlText; + jobUtt.sentence->text = QString::null; + jobUtt.sentence->talker = QString::null; + jobUtt.sentence->appId = m_lastAppId; + jobUtt.sentence->jobNum = m_lastJobNum; + jobUtt.sentence->seq = 0; + jobUtt.audioUrl = QString::null; + jobUtt.utType = utEndOfJob; + jobUtt.isSSML = false; + jobUtt.plugin = 0; + jobUtt.state = usWaitingSignal; + m_uttQueue.append(jobUtt); + gotOne = true; + ++m_lastSeq; // Don't append another End-of-job + } + } + } + + return gotOne; +} + +/** + * Given an iterator pointing to the m_uttQueue, deletes the utterance + * from the queue. If the utterance is currently being processed by a + * plugin or the Audio Player, halts that operation and deletes Audio Player. + * Also takes care of deleting temporary audio file. + * @param it Iterator pointer to m_uttQueue. + * @return Iterator pointing to the next utterance in the + * queue, or m_uttQueue.end(). + */ +uttIterator Speaker::deleteUtterance(uttIterator it) +{ + switch (it->state) + { + case usNone: + case usWaitingTransform: + case usWaitingSay: + case usWaitingSynth: + case usWaitingSignal: + case usSynthed: + case usFinished: + case usStretched: + break; + + case usTransforming: + { + delete it->transformer; + it->transformer = 0; + break; + } + case usSaying: + case usSynthing: + { + // If plugin supports asynchronous mode, and it is busy, halt it. + PlugInProc* plugin = it->plugin; + if (it->plugin->supportsAsync()) + if ((plugin->getState() == psSaying) || (plugin->getState() == psSynthing)) + { + kdDebug() << "Speaker::deleteUtterance calling stopText" << endl; + plugin->stopText(); + } + break; + } + case usStretching: + { + delete it->audioStretcher; + it->audioStretcher = 0; + break; + } + case usPlaying: + { + m_timer->stop(); + it->audioPlayer->stop(); + delete it->audioPlayer; + break; + } + case usPaused: + case usPreempted: + { + // Note: Must call stop(), even if player not currently playing. Why? + it->audioPlayer->stop(); + delete it->audioPlayer; + break; + } + } + if (!it->audioUrl.isNull()) + { + // If the audio file was generated by a plugin, delete it. + if (it->plugin) + { + if (m_speechData->keepAudio) + { + QCString seqStr; + seqStr.sprintf("%08i", it->sentence->seq); // Zero-fill to 8 chars. + QCString jobStr; + jobStr.sprintf("%08i", it->sentence->jobNum); + QString dest = m_speechData->keepAudioPath + "/kttsd-" + + QString("%1-%2").arg(jobStr).arg(seqStr) + ".wav"; + QFile::remove(dest); + QDir d; + d.rename(it->audioUrl, dest); + // TODO: This is always producing the following. Why and how to fix? + // It moves the files just fine. + // kio (KIOJob): stat file:///home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav + // kio (KIOJob): error 11 /home/kde-devel/.kde/share/apps/kttsd/audio/kttsd-5-1.wav + // kio (KIOJob): This seems to be a suitable case for trying to rename before stat+[list+]copy+del + // KIO::move(it->audioUrl, dest, false); + } + else + QFile::remove(it->audioUrl); + } + } + // Delete the utterance from queue. + delete it->sentence; + return m_uttQueue.erase(it); +} + +/** + * Given an iterator pointing to the m_uttQueue, starts playing audio if + * 1) An audio file is ready to be played, and + * 2) It is not already playing. + * If another audio player is already playing, pauses it before starting + * the new audio player. + * @param it Iterator pointer to m_uttQueue. + * @return True if an utterance began playing or resumed. + */ +bool Speaker::startPlayingUtterance(uttIterator it) +{ + // kdDebug() << "Speaker::startPlayingUtterance running" << endl; + if (it->state == usPlaying) return false; + if (it->audioUrl.isNull()) return false; + bool started = false; + // Pause (preempt) any other utterance currently being spoken. + // If any plugins are audibilizing, must wait for them to finish. + uttIterator itEnd = m_uttQueue.end(); + for (uttIterator it2 = m_uttQueue.begin(); it2 != itEnd; ++it2) + if (it2 != it) + { + if (it2->state == usPlaying) + { + m_timer->stop(); + it2->audioPlayer->pause(); + it2->state = usPreempted; + } + if (it2->state == usSaying) return false; + } + uttState utState = it->state; + switch (utState) + { + case usNone: + case usWaitingTransform: + case usTransforming: + case usWaitingSay: + case usWaitingSynth: + case usWaitingSignal: + case usSaying: + case usSynthing: + case usSynthed: + case usStretching: + case usPlaying: + case usFinished: + break; + + case usStretched: + { + // Don't start playback yet if text job is paused. + if ((it->utType != utText) || + (m_speechData->getTextJobState(it->sentence->jobNum) != KSpeech::jsPaused)) + { + + it->audioPlayer = createPlayerObject(); + if (it->audioPlayer) + { + it->audioPlayer->startPlay(it->audioUrl); + // Set job to speaking state and set sequence number. + mlText* sentence = it->sentence; + m_currentJobNum = sentence->jobNum; + m_speechData->setTextJobState(m_currentJobNum, KSpeech::jsSpeaking); + m_speechData->setJobSequenceNum(m_currentJobNum, sentence->seq); + prePlaySignals(it); + it->state = usPlaying; + if (!m_timer->start(timerInterval, FALSE)) + kdDebug() << "Speaker::startPlayingUtterance: timer.start failed" << endl; + started = true; + } else { + // If could not create audio player object, best we can do is silence. + it->state = usFinished; + } + } + break; + } + case usPaused: + { + // Unpause playback only if user has resumed. + // kdDebug() << "Speaker::startPlayingUtterance: checking whether to resume play" << endl; + if ((it->utType != utText) || + (m_speechData->getTextJobState(it->sentence->jobNum) != KSpeech::jsPaused)) + { + // kdDebug() << "Speaker::startPlayingUtterance: resuming play" << endl; + it->audioPlayer->startPlay(QString::null); // resume + it->state = usPlaying; + if (!m_timer->start(timerInterval, FALSE)) + kdDebug() << "Speaker::startPlayingUtterance: timer.start failed" << endl; + started = true; + } + break; + } + + case usPreempted: + { + // Preempted playback automatically resumes. + // Note: Must call stop(), even if player not currently playing. Why? + it->audioPlayer->startPlay(QString::null); // resume + it->state = usPlaying; + if (!m_timer->start(timerInterval, FALSE)) + kdDebug() << "Speaker::startPlayingUtterance: timer.start failed" << endl; + started = true; + break; + } + } + return started; +} + +/** + * Takes care of emitting reading interrupted/resumed and sentence started signals. + * Should be called just before audibilizing an utterance. + * @param it Iterator pointer to m_uttQueue. + * It also makes sure the job state is set to jsSpeaking. + */ +void Speaker::prePlaySignals(uttIterator it) +{ + uttType utType = it->utType; + if (utType == utText) + { + // If this utterance is for a regular text message, + // and it was interrupted, emit reading resumed signal. + if (m_textInterrupted) + { + m_textInterrupted = false; + emit readingResumed(); + } + // Set job to speaking state and set sequence number. + mlText* sentence = it->sentence; + // Emit sentence started signal. + emit sentenceStarted(sentence->text, + sentence->talker, sentence->appId, + m_currentJobNum, sentence->seq); + } else { + // If this utterance is not a regular text message, + // and we are doing a text job, emit reading interrupted signal. + if (isSpeakingText()) + { + m_textInterrupted = true; + emit readingInterrupted(); + } + } +} + +/** + * Takes care of emitting sentenceFinished signal. + * Should be called immediately after an utterance has completed playback. + * @param it Iterator pointer to m_uttQueue. + */ +void Speaker::postPlaySignals(uttIterator it) +{ + uttType utType = it->utType; + if (utType == utText) + { + // If this utterance is for a regular text message, + // emit sentence finished signal. + mlText* sentence = it->sentence; + emit sentenceFinished(sentence->appId, + sentence->jobNum, sentence->seq); + } +} + +/** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ +QString Speaker::makeSuggestedFilename() +{ + KTempFile tempFile (locateLocal("tmp", "kttsd-"), ".wav"); + QString waveFile = tempFile.file()->name(); + tempFile.close(); + QFile::remove(waveFile); + // kdDebug() << "Speaker::makeSuggestedFilename: Suggesting filename: " << waveFile << endl; + return KStandardDirs::realFilePath(waveFile); +} + +/** + * Creates and returns a player object based on user option. + */ +Player* Speaker::createPlayerObject() +{ + Player* player = 0; + QString plugInName; + switch(m_playerOption) + { + case 1 : + { + plugInName = "kttsd_gstplugin"; + break; + } + case 2 : + { + plugInName = "kttsd_alsaplugin"; + break; + } + case 3 : + { + plugInName = "kttsd_akodeplugin"; + break; + } + default: + { + plugInName = "kttsd_artsplugin"; + break; + } + } + KTrader::OfferList offers = KTrader::self()->query( + "KTTSD/AudioPlugin", QString("DesktopEntryName == '%1'").arg(plugInName)); + + if(offers.count() == 1) + { + kdDebug() << "Speaker::createPlayerObject: Loading " << offers[0]->library() << endl; + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if (factory) + player = + KParts::ComponentFactory::createInstanceFromLibrary<Player>( + offers[0]->library().latin1(), this, offers[0]->library().latin1()); + } + if (player == 0) + { + // If we tried for GStreamer or ALSA plugin and failed, fall back to aRts plugin. + if (m_playerOption != 0) + { + kdDebug() << "Speaker::createPlayerObject: Could not load " + plugInName + + " plugin. Falling back to aRts." << endl; + m_playerOption = 0; + return createPlayerObject(); + } + else + kdDebug() << "Speaker::createPlayerObject: Could not load aRts plugin. Is KDEDIRS set correctly?" << endl; + } else + // Must have GStreamer >= 0.8.7. If not, use aRts. + if (m_playerOption == 1) + { + if (!player->requireVersion(0, 8, 7)) + { + delete player; + m_playerOption = 0; + return createPlayerObject(); + } + } + if (player) { + player->setSinkName(m_sinkName); + player->setPeriodSize(m_periodSize); + player->setPeriods(m_periodSize); + player->setDebugLevel(m_playerDebugLevel); + } + return player; +} + + +/* Slots ==========================================================*/ + +/** +* Received from PlugIn objects when they finish asynchronous synthesis +* and audibilizing. +*/ +void Speaker::slotSayFinished() +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 101); + QApplication::postEvent(this, ev); +} + +/** +* Received from PlugIn objects when they finish asynchronous synthesis. +*/ +void Speaker::slotSynthFinished() +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 102); + QApplication::postEvent(this, ev); +} + +/** +* Received from PlugIn objects when they asynchronously stopText. +*/ +void Speaker::slotStopped() +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 103); + QApplication::postEvent(this, ev); +} + +/** +* Received from audio stretcher when stretching (speed adjustment) is finished. +*/ +void Speaker::slotStretchFinished() +{ + // Convert to postEvent and return immediately. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 104); + QApplication::postEvent(this, ev); +} + +/** +* Received from transformer (SSMLConvert) when transforming is finished. +*/ +void Speaker::slotTransformFinished() +{ + // Convert to postEvent and return immediately. + QCustomEvent* ev = new QCustomEvent(QEvent::User + 105); + QApplication::postEvent(this, ev); +} + +/** Received from PlugIn object when they encounter an error. +* @param keepGoing True if the plugin can continue processing. +* False if the plugin cannot continue, for example, +* the speech engine could not be started. +* @param msg Error message. +*/ +void Speaker::slotError(bool /*keepGoing*/, const QString& /*msg*/) +{ + // Since this signal handler may be running from a plugin's thread, + // convert to postEvent and return immediately. + // TODO: Do something with error messages. + /* + if (keepGoing) + QCustomEvent* ev = new QCustomEvent(QEvent::User + 106); + else + QCustomEvent* ev = new QCustomEvent(QEvent::User + 107); + QApplication::postEvent(this, ev); + */ +} + +/** +* Received from Timer when it fires. +* Check audio player to see if it is finished. +*/ +void Speaker::slotTimeout() +{ + uttIterator itEnd = m_uttQueue.end(); + for (uttIterator it = m_uttQueue.begin(); it != itEnd; ++it) + { + if (it->state == usPlaying) + { + if (it->audioPlayer->playing()) return; // Still playing. + m_timer->stop(); + postPlaySignals(it); + deleteUtterance(it); + doUtterances(); + return; + } + } +} + +/** +* Processes events posted by plugins. When asynchronous plugins emit signals +* they are converted into these events. +*/ +bool Speaker::event ( QEvent * e ) +{ + // TODO: Do something with event numbers 106 (error; keepGoing=True) + // and 107 (error; keepGoing=False). + if ((e->type() >= (QEvent::User + 101)) && (e->type() <= (QEvent::User + 105))) + { + // kdDebug() << "Speaker::event: received event." << endl; + doUtterances(); + return TRUE; + } + else return FALSE; +} + diff --git a/kttsd/kttsd/speaker.h b/kttsd/kttsd/speaker.h new file mode 100644 index 0000000..b72e832 --- /dev/null +++ b/kttsd/kttsd/speaker.h @@ -0,0 +1,599 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Speaker class. + + This class is in charge of getting the messages, warnings and text from + the queue and call the plug ins function to actually speak the texts. + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + ******************************************************************************/ + +/****************************************************************************** + * * + * 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. * + * * + ******************************************************************************/ + +#ifndef _SPEAKER_H_ +#define _SPEAKER_H_ + +// Qt includes. +#include <qobject.h> +#include <qvaluevector.h> +#include <qevent.h> + +// KTTSD includes. +#include <speechdata.h> +#include <pluginproc.h> +#include <stretcher.h> +#include <talkercode.h> +#include <ssmlconvert.h> + +class Player; +class QTimer; +class TalkerMgr; + +/** +* Type of utterance. +*/ +enum uttType +{ + utText, /**< Text */ + utInterruptMsg, /**< Interruption text message */ + utInterruptSnd, /**< Interruption sound file */ + utResumeMsg, /**< Resume text message */ + utResumeSnd, /**< Resume sound file */ + utMessage, /**< Message */ + utWarning, /**< Warning */ + utScreenReader, /**< Screen Reader Output */ + utStartOfJob, /**< Start-of-job */ + utEndOfJob /**< End-of-job */ +}; + +/** +* Processing state of an utterance. +*/ +enum uttState +{ + usNone, /**< Null state. Brand new utterance. */ + usWaitingTransform, /**< Waiting to be transformed (XSLT) */ + usTransforming, /**< Transforming the utterance (XSLT). */ + usWaitingSay, /**< Waiting to start synthesis. */ + usWaitingSynth, /**< Waiting to be synthesized and audibilized. */ + usWaitingSignal, /**< Waiting to emit a textStarted or textFinished signal. */ + usSaying, /**< Plugin is synthesizing and audibilizing. */ + usSynthing, /**< Plugin is synthesizing only. */ + usSynthed, /**< Plugin has finished synthesizing. Ready for stretch. */ + usStretching, /**< Adjusting speed. */ + usStretched, /**< Speed adjustment finished. Ready for playback. */ + usPlaying, /**< Playing on Audio Player. */ + usPaused, /**< Paused on Audio Player due to user action. */ + usPreempted, /**< Paused on Audio Player due to Screen Reader Output. */ + usFinished /**< Ready for deletion. */ +}; + +/** +* Structure containing an utterance being synthesized or audibilized. +*/ +struct Utt{ + mlText* sentence; /* The text, talker, appId, and sequence num. */ + uttType utType; /* The type of utterance (text, msg, screen reader) */ + bool isSSML; /* True if the utterance contains SSML markup. */ + uttState state; /* Processing state of the utterance. */ + SSMLConvert* transformer; /* XSLT transformer. */ + PlugInProc* plugin; /* The plugin that synthesizes the utterance. */ + Stretcher* audioStretcher; /* Audio stretcher object. Adjusts speed. */ + QString audioUrl; /* Filename containing synthesized audio. Null if + plugin has not yet synthesized the utterance, or if + plugin does not support synthesis. */ + Player* audioPlayer; /* The audio player audibilizing the utterance. Null + if not currently audibilizing or if plugin doesn't + support synthesis. */ +}; + +/** +* Iterator for queue of utterances. +*/ +typedef QValueVector<Utt>::iterator uttIterator; + +// Timer interval for checking whether audio playback is finished. +const int timerInterval = 500; + +/** + * This class is in charge of getting the messages, warnings and text from + * the queue and call the plug ins function to actually speak the texts. + */ +class Speaker : public QObject{ + Q_OBJECT + + public: + /** + * Constructor + * Calls load plug ins + */ + Speaker(SpeechData* speechData, TalkerMgr* talkerMgr, + QObject *parent = 0, const char *name = 0); + + /** + * Destructor + */ + ~Speaker(); + + /** + * Tells the thread to exit + */ + void requestExit(); + + /** + * Main processing loop. Dequeues utterances and sends them to the + * plugins and/or Audio Player. + */ + void doUtterances(); + + /** + * Determine if kttsd is currently speaking any text jobs. + * @return True if currently speaking any text jobs. + */ + bool isSpeakingText(); + + /** + * Get the job number of the current text job. + * @return Job number of the current text job. 0 if no jobs. + * + * Note that the current job may not be speaking. See @ref isSpeakingText. + * @see getTextJobState. + * @see isSpeakingText + */ + uint getCurrentTextJob(); + + /** + * Remove a text job from the queue. + * @param jobNum Job number of the text job. + * + * The job is deleted from the queue and the @ref textRemoved signal is emitted. + * + * If there is another job in the text queue, and it is marked speakable, + * that job begins speaking. + */ + void removeText(const uint jobNum); + + /** + * Start a text job at the beginning. + * @param jobNum Job number of the text job. + * + * Rewinds the job to the beginning. + * + * The job is marked speakable. + * If there are other speakable jobs preceeding this one in the queue, + * those jobs continue speaking and when finished, this job will begin speaking. + * If there are no other speakable jobs preceeding this one, it begins speaking. + * + * The @ref textStarted signal is emitted when the text job begins speaking. + * When all the sentences of the job have been spoken, the job is marked for deletion from + * the text queue and the @ref textFinished signal is emitted. + */ + void startText(const uint jobNum); + + /** + * Stop a text job and rewind to the beginning. + * @param jobNum Job number of the text job. + * + * The job is marked not speakable and will not be speakable until @ref startText or @ref resumeText + * is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * If the job is currently speaking, the @ref textStopped signal is emitted and the job stops speaking. + * Depending upon the speech engine and plugin used, speeking may not stop immediately + * (it might finish the current sentence). + */ + void stopText(const uint jobNum); + + /** + * Pause a text job. + * @param jobNum Job number of the text job. + * + * The job is marked as paused and will not be speakable until @ref resumeText or + * @ref startText is called. + * + * If there are speaking jobs preceeding this one in the queue, they continue speaking. + * If the job is currently speaking, the @ref textPaused signal is emitted and the job stops speaking. + * Depending upon the speech engine and plugin used, speeking may not stop immediately + * (it might finish the current sentence). + * @see resumeText + */ + void pauseText(const uint jobNum); + + /** + * Start or resume a text job where it was paused. + * @param jobNum Job number of the text job. + * + * The job is marked speakable. + * + * If the job is currently speaking, or is waiting to be spoken (speakable + * state), the resumeText() call is ignored. + * + * If the job is currently queued, or is finished, it is the same as calling + * @ref startText . + * + * If there are speaking jobs preceeding this one in the queue, those jobs continue speaking and, + * when finished this job will begin speaking where it left off. + * + * The @ref textResumed signal is emitted when the job resumes. + * @see pauseText + */ + void resumeText(const uint jobNum); + + /** + * Move a text job down in the queue so that it is spoken later. + * @param jobNum Job number of the text job. + * + * If the job is currently speaking, it is paused. + * If the next job in the queue is speakable, it begins speaking. + */ + void moveTextLater(const uint jobNum); + + /** + * Jump to the first sentence of a specified part of a text job. + * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. + * @param jobNum Job number of the text job. + * @return Part number of the part actually jumped to. + * + * If partNum is greater than the number of parts in the job, jumps to last part. + * If partNum is 0, does nothing and returns the current part number. + * If no such job, does nothing and returns 0. + * Does not affect the current speaking/not-speaking state of the job. + */ + int jumpToTextPart(const int partNum, const uint jobNum); + + /** + * Advance or rewind N sentences in a text job. + * @param n Number of sentences to advance (positive) or rewind (negative) + * in the job. + * @param jobNum Job number of the text job. + * @return Sequence number of the sentence actually moved to. + * Sequence numbers are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + */ + uint moveRelTextSentence(const int n, const uint jobNum); + + signals: + /** + * Emitted whenever reading a text was started or resumed + */ + void readingStarted(); + + /** + * Emitted whenever reading a text was finished, + * or paused, or stopped before it was finished + */ + void readingStopped(); + + /** + * Emitted whenever a message or warning interrupts reading a text + */ + void readingInterrupted(); + + /** + * Emitted whenever reading a text is resumed after it was interrupted + * Note: In function resumeText, readingStarted is called instead + */ + void readingResumed(); + + /* The following signals correspond to the signals in the KSpeech interface. */ + + /** + * This signal is emitted when the speech engine/plugin encounters a marker in the text. + * @param appId DCOP application ID of the application that queued the text. + * @param markerName The name of the marker seen. + * @see markers + */ + void markerSeen(const QCString& appId, const QString& markerName); + + /** + * This signal is emitted whenever a sentence begins speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + */ + void sentenceStarted(QString text, QString language, const QCString& appId, + const uint jobNum, const uint seq); + + /** + * This signal is emitted when a sentence has finished speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + */ + void sentenceFinished(const QCString& appId, const uint jobNum, const uint seq); + + /** + * This signal is emitted whenever speaking of a text job begins. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + void textStarted(const QCString& appId, const uint jobNum); + + /** + * This signal is emitted whenever a text job is finished. The job has + * been marked for deletion from the queue and will be deleted when another + * job reaches the Finished state. (Only one job in the text queue may be + * in state Finished at one time.) If @ref startText or @ref resumeText is + * called before the job is deleted, it will remain in the queue for speaking. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textFinished(const QCString& appId, const uint jobNum); + + /** + * This signal is emitted whenever a speaking text job stops speaking. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textStopped(const QCString& appId, const uint jobNum); + /** + * This signal is emitted whenever a speaking text job is paused. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textPaused(const QCString& appId, const uint jobNum); + /** + * This signal is emitted when a text job, that was previously paused, resumes speaking. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textResumed(const QCString& appId, const uint jobNum); + + protected: + /** + * Processes events posted by ThreadedPlugIns. + */ + virtual bool event ( QEvent * e ); + + private slots: + /** + * Received from PlugIn objects when they finish asynchronous synthesis. + */ + void slotSynthFinished(); + /** + * Received from PlugIn objects when they finish asynchronous synthesis + * and audibilizing. + */ + void slotSayFinished(); + /** + * Received from PlugIn objects when they asynchronously stopText. + */ + void slotStopped(); + /** + * Received from audio stretcher when stretching (speed adjustment) is finished. + */ + void slotStretchFinished(); + /** + * Received from transformer (SSMLConvert) when transforming is finished. + */ + void slotTransformFinished(); + /** Received from PlugIn object when they encounter an error. + * @param keepGoing True if the plugin can continue processing. + * False if the plugin cannot continue, for example, + * the speech engine could not be started. + * @param msg Error message. + */ + void slotError(bool keepGoing, const QString &msg); + /** + * Received from Timer when it fires. + * Check audio player to see if it is finished. + */ + void slotTimeout(); + + private: + + /** + * Converts an utterance state enumerator to a displayable string. + * @param state Utterance state. + * @return Displayable string for utterance state. + */ + QString uttStateToStr(uttState state); + + /** + * Converts an utterance type enumerator to a displayable string. + * @param utType Utterance type. + * @return Displayable string for utterance type. + */ + QString uttTypeToStr(uttType utType); + + /** + * Converts a plugin state enumerator to a displayable string. + * @param state Plugin state. + * @return Displayable string for plugin state. + */ + QString pluginStateToStr(pluginState state); + + /** + * Converts a job state enumerator to a displayable string. + * @param state Job state. + * @return Displayable string for job state. + */ + QString jobStateToStr(int state); + + /** + * Determines whether the given text is SSML markup. + */ + bool isSsml(const QString &text); + + /** + * Determines the initial state of an utterance. If the utterance contains + * SSML, the state is set to usWaitingTransform. Otherwise, if the plugin + * supports async synthesis, sets to usWaitingSynth, otherwise usWaitingSay. + * If an utterance has already been transformed, usWaitingTransform is + * skipped to either usWaitingSynth or usWaitingSay. + * @param utt The utterance. + */ + void setInitialUtteranceState(Utt &utt); + + /** + * Returns true if the given job and sequence number is already in the utterance queue. + */ + bool isInUtteranceQueue(uint jobNum, uint seqNum); + + /** + * Gets the next utterance to be spoken from speechdata and adds it to the queue. + * @return True if one or more utterances were added to the queue. + * + * Checks for waiting ScreenReaderOutput, Warnings, Messages, or Text, + * in that order. + * If Warning or Message and interruption messages have been configured, + * adds those to the queue as well. + * Determines which plugin should be used for the utterance. + */ + bool getNextUtterance(); + + /** + * Given an iterator pointing to the m_uttQueue, deletes the utterance + * from the queue. If the utterance is currently being processed by a + * plugin or the Audio Player, halts that operation and deletes Audio Player. + * Also takes care of deleting temporary audio file. + * @param it Iterator pointer to m_uttQueue. + * @return Iterator pointing to the next utterance in the + * queue, or m_uttQueue.end(). + */ + uttIterator deleteUtterance(uttIterator it); + + /** + * Given an iterator pointing to the m_uttQueue, starts playing audio if + * 1) An audio file is ready to be played, and + * 2) It is not already playing. + * If another audio player is already playing, pauses it before starting + * the new audio player. + * @param it Iterator pointer to m_uttQueue. + * @return True if an utterance began playing or resumed. + */ + bool startPlayingUtterance(uttIterator it); + + /** + * Delete any utterances in the queue with this jobNum. + * @param jobNum The Job Number of the utterance(s) to delete. + * If currently processing any deleted utterances, stop them. + */ + void deleteUtteranceByJobNum(const uint jobNum); + + /** + * Pause the utterance with this jobNum and if it is playing on the Audio Player, + * pause the Audio Player. + * @param jobNum The Job Number of the utterance to pause. + */ + void pauseUtteranceByJobNum(const uint jobNum); + + /** + * Takes care of emitting reading interrupted/resumed and sentence started signals. + * Should be called just before audibilizing an utterance. + * @param it Iterator pointer to m_uttQueue. + */ + void prePlaySignals(uttIterator it); + + /** + * Takes care of emitting sentenceFinished signal. + * Should be called immediately after an utterance has completed playback. + * @param it Iterator pointer to m_uttQueue. + */ + void postPlaySignals(uttIterator it); + + /** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ + QString makeSuggestedFilename(); + + /** + * Creates and returns a player object based on user option. + */ + Player* createPlayerObject(); + + /** + * SpeechData local pointer + */ + SpeechData* m_speechData; + + /** + * TalkerMgr local pointer. + */ + TalkerMgr* m_talkerMgr; + + /** + * True if the speaker was requested to exit. + */ + volatile bool m_exitRequested; + + /** + * Queue of utterances we are currently processing. + */ + QValueVector<Utt> m_uttQueue; + + /** + * True when text job reading has been interrupted. + */ + bool m_textInterrupted; + + /** + * Used to prevent doUtterances from prematurely exiting. + */ + bool m_again; + + /** + * Which audio player to use. + * 0 = aRts + * 1 = gstreamer + * 2 = ALSA + */ + int m_playerOption; + + /** + * Audio stretch factor (Speed). + */ + float m_audioStretchFactor; + + /** + * GStreamer sink name to use, or ALSA PCM device name. + */ + QString m_sinkName; + + /** + * Timer for monitoring audio player. + */ + QTimer* m_timer; + + /** + * Current Text job being processed. + */ + uint m_currentJobNum; + + /** + * Job Number, appId, and sequence number of the last text sentence queued. + */ + uint m_lastJobNum; + QCString m_lastAppId; + uint m_lastSeq; + + /** + * Some parameters used by ALSA plugin. + * Size of buffer interrupt period (in frames) + * Number of periods in buffer. + */ + uint m_periodSize; + uint m_periods; + + /** + * Debug level in players. + */ + uint m_playerDebugLevel; +}; + +#endif // _SPEAKER_H_ diff --git a/kttsd/kttsd/speechdata.cpp b/kttsd/kttsd/speechdata.cpp new file mode 100644 index 0000000..a1bf26e --- /dev/null +++ b/kttsd/kttsd/speechdata.cpp @@ -0,0 +1,1275 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This contains the SpeechData class which is in charge of maintaining + all the data on the memory. + It maintains queues manages the text. + We could say that this is the common repository between the KTTSD class + (dcop service) and the Speaker class (speaker, loads plug ins, call plug in + functions) + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + ******************************************************************************/ + +/****************************************************************************** + * * + * 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. * + * * + ******************************************************************************/ + +// C++ includes. +#include <stdlib.h> + +// Qt includes. +#include <qregexp.h> +#include <qpair.h> +#include <qvaluelist.h> +#include <qdom.h> +#include <qfile.h> + +// KDE includes. +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kapplication.h> + +// KTTS includes. +#include "talkermgr.h" +#include "notify.h" + +// SpeechData includes. +#include "speechdata.h" +#include "speechdata.moc" + +// Set this to 1 to turn off filter support, including SBD as a plugin. +#define NO_FILTERS 0 + +/** +* Constructor +* Sets text to be stopped and warnings and messages queues to be autodelete. +*/ +SpeechData::SpeechData(){ + // kdDebug() << "Running: SpeechData::SpeechData()" << endl; + // The text should be stoped at the beggining (thread safe) + jobCounter = 0; + config = 0; + textJobs.setAutoDelete(true); + supportsHTML = false; + + // Warnings queue to be autodelete (thread safe) + warnings.setAutoDelete(true); + + // Messages queue to be autodelete (thread safe) + messages.setAutoDelete(true); + + screenReaderOutput.jobNum = 0; + screenReaderOutput.text = ""; +} + +bool SpeechData::readConfig(){ + // Load configuration + delete config; + //config = KGlobal::config(); + config = new KConfig("kttsdrc"); + + // Set the group general for the configuration of KTTSD itself (no plug ins) + config->setGroup("General"); + + // Load the configuration of the text interruption messages and sound + textPreMsgEnabled = config->readBoolEntry("TextPreMsgEnabled", false); + textPreMsg = config->readEntry("TextPreMsg"); + + textPreSndEnabled = config->readBoolEntry("TextPreSndEnabled", false); + textPreSnd = config->readEntry("TextPreSnd"); + + textPostMsgEnabled = config->readBoolEntry("TextPostMsgEnabled", false); + textPostMsg = config->readEntry("TextPostMsg"); + + textPostSndEnabled = config->readBoolEntry("TextPostSndEnabled", false); + textPostSnd = config->readEntry("TextPostSnd"); + keepAudio = config->readBoolEntry("KeepAudio", false); + keepAudioPath = config->readEntry("KeepAudioPath", locateLocal("data", "kttsd/audio/")); + + // Notification (KNotify). + notify = config->readBoolEntry("Notify", false); + notifyExcludeEventsWithSound = config->readBoolEntry("ExcludeEventsWithSound", true); + loadNotifyEventsFromFile( locateLocal("config", "kttsd_notifyevents.xml"), true ); + + // KTTSMgr auto start and auto exit. + autoStartManager = config->readBoolEntry("AutoStartManager", false); + autoExitManager = config->readBoolEntry("AutoExitManager", false); + + // Clear the pool of filter managers so that filters re-init themselves. + QPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs ); + for( ; it.current(); ++it ) + { + PooledFilterMgr* pooledFilterMgr = it.current(); + delete pooledFilterMgr->filterMgr; + delete pooledFilterMgr->talkerCode; + delete pooledFilterMgr; + } + m_pooledFilterMgrs.clear(); + + // Create an initial FilterMgr for the pool to save time later. + PooledFilterMgr* pooledFilterMgr = new PooledFilterMgr(); + FilterMgr* filterMgr = new FilterMgr(); + filterMgr->init(config, "General"); + supportsHTML = filterMgr->supportsHTML(); + pooledFilterMgr->filterMgr = filterMgr; + pooledFilterMgr->busy = false; + pooledFilterMgr->job = 0; + pooledFilterMgr->partNum = 0; + // Connect signals from FilterMgr. + connect (filterMgr, SIGNAL(filteringFinished()), this, SLOT(slotFilterMgrFinished())); + connect (filterMgr, SIGNAL(filteringStopped()), this, SLOT(slotFilterMgrStopped())); + m_pooledFilterMgrs.append(pooledFilterMgr); + + return true; +} + +/** + * Loads notify events from a file. Clearing data if clear is True. + */ +void SpeechData::loadNotifyEventsFromFile( const QString& filename, bool clear) +{ + // Open existing event list. + QFile file( filename ); + if ( !file.open( IO_ReadOnly ) ) + { + kdDebug() << "SpeechData::loadNotifyEventsFromFile: Unable to open file " << filename << endl; + } + // QDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" ); + QDomDocument doc( "" ); + if ( !doc.setContent( &file ) ) { + file.close(); + kdDebug() << "SpeechData::loadNotifyEventsFromFile: File not in proper XML format. " << filename << endl; + } + // kdDebug() << "StringReplacerConf::load: document successfully parsed." << endl; + file.close(); + + if ( clear ) + { + notifyDefaultPresent = NotifyPresent::Passive; + notifyDefaultOptions.action = NotifyAction::SpeakMsg; + notifyDefaultOptions.talker = QString::null; + notifyDefaultOptions.customMsg = QString::null; + notifyAppMap.clear(); + } + + // Event list. + QDomNodeList eventList = doc.elementsByTagName("notifyEvent"); + const int eventListCount = eventList.count(); + for (int eventIndex = 0; eventIndex < eventListCount; ++eventIndex) + { + QDomNode eventNode = eventList.item(eventIndex); + QDomNodeList propList = eventNode.childNodes(); + QString eventSrc; + QString event; + QString actionName; + QString message; + TalkerCode talkerCode; + const int propListCount = propList.count(); + for (int propIndex = 0; propIndex < propListCount; ++propIndex) + { + QDomNode propNode = propList.item(propIndex); + QDomElement prop = propNode.toElement(); + if (prop.tagName() == "eventSrc") eventSrc = prop.text(); + if (prop.tagName() == "event") event = prop.text(); + if (prop.tagName() == "action") actionName = prop.text(); + if (prop.tagName() == "message") message = prop.text(); + if (prop.tagName() == "talker") talkerCode = TalkerCode(prop.text(), false); + } + NotifyOptions notifyOptions; + notifyOptions.action = NotifyAction::action( actionName ); + notifyOptions.talker = talkerCode.getTalkerCode(); + notifyOptions.customMsg = message; + if ( eventSrc != "default" ) + { + notifyOptions.eventName = NotifyEvent::getEventName( eventSrc, event ); + NotifyEventMap notifyEventMap = notifyAppMap[ eventSrc ]; + notifyEventMap[ event ] = notifyOptions; + notifyAppMap[ eventSrc ] = notifyEventMap; + } else { + notifyOptions.eventName = QString::null; + notifyDefaultPresent = NotifyPresent::present( event ); + notifyDefaultOptions = notifyOptions; + } + } +} + +/** +* Destructor +*/ +SpeechData::~SpeechData(){ + // kdDebug() << "Running: SpeechData::~SpeechData()" << endl; + // Walk through jobs and emit a textRemoved signal for each job. + for (mlJob* job = textJobs.first(); (job); job = textJobs.next()) + { + emit textRemoved(job->appId, job->jobNum); + } + + QPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs ); + for( ; it.current(); ++it ) + { + PooledFilterMgr* pooledFilterMgr = it.current(); + delete pooledFilterMgr->filterMgr; + delete pooledFilterMgr->talkerCode; + delete pooledFilterMgr; + } + + delete config; +} + +/** +* Say a message as soon as possible, interrupting any other speech in progress. +* IMPORTANT: This method is reserved for use by Screen Readers and should not be used +* by any other applications. +* @param msg The message to be spoken. +* @param talker Code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* +* If an existing Screen Reader output is in progress, it is stopped and discarded and +* replaced with this new message. +*/ +void SpeechData::setScreenReaderOutput(const QString &msg, const QString &talker, const QCString &appId) +{ + screenReaderOutput.text = msg; + screenReaderOutput.talker = talker; + screenReaderOutput.appId = appId; + screenReaderOutput.seq = 1; +} + +/** +* Retrieves the Screen Reader Output. +*/ +mlText* SpeechData::getScreenReaderOutput() +{ + mlText* temp = new mlText(); + temp->text = screenReaderOutput.text; + temp->talker = screenReaderOutput.talker; + temp->appId = screenReaderOutput.appId; + temp->seq = screenReaderOutput.seq; + // Blank the Screen Reader to text to "empty" it. + screenReaderOutput.text = ""; + return temp; +} + +/** +* Returns true if Screen Reader Output is ready to be spoken. +*/ +bool SpeechData::screenReaderOutputReady() +{ + return !screenReaderOutput.text.isEmpty(); +} + +/** +* Add a new warning to the queue. +*/ +void SpeechData::enqueueWarning( const QString &warning, const QString &talker, const QCString &appId){ + // kdDebug() << "Running: SpeechData::enqueueWarning( const QString &warning )" << endl; + mlJob* job = new mlJob(); + ++jobCounter; + if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums. + uint jobNum = jobCounter; + job->jobNum = jobNum; + job->talker = talker; + job->appId = appId; + job->seq = 1; + job->partCount = 1; + warnings.enqueue( job ); + job->sentences = QStringList(); + // Do not apply Sentence Boundary Detection filters to warnings. + startJobFiltering( job, warning, true ); + // uint count = warnings.count(); + // kdDebug() << "Adding '" << temp->text << "' with talker '" << temp->talker << "' from application " << appId << " to the warnings queue leaving a total of " << count << " items." << endl; +} + +/** +* Pop (get and erase) a warning from the queue. +* @return Pointer to mlText structure containing the message. +* +* Caller is responsible for deleting the structure. +*/ +mlText* SpeechData::dequeueWarning(){ + // kdDebug() << "Running: SpeechData::dequeueWarning()" << endl; + mlJob* job = warnings.dequeue(); + waitJobFiltering(job); + mlText* temp = new mlText(); + temp->jobNum = job->jobNum; + temp->text = job->sentences.join(""); + temp->talker = job->talker; + temp->appId = job->appId; + temp->seq = 1; + delete job; + // uint count = warnings.count(); + // kdDebug() << "Removing '" << temp->text << "' with talker '" << temp->talker << "' from the warnings queue leaving a total of " << count << " items." << endl; + return temp; +} + +/** +* Are there any Warnings? +*/ +bool SpeechData::warningInQueue(){ + // kdDebug() << "Running: SpeechData::warningInQueue() const" << endl; + bool temp = !warnings.isEmpty(); + // if(temp){ + // kdDebug() << "The warnings queue is NOT empty" << endl; + // } else { + // kdDebug() << "The warnings queue is empty" << endl; + // } + return temp; +} + +/** +* Add a new message to the queue. +*/ +void SpeechData::enqueueMessage( const QString &message, const QString &talker, const QCString& appId){ + // kdDebug() << "Running: SpeechData::enqueueMessage" << endl; + mlJob* job = new mlJob(); + ++jobCounter; + if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums. + uint jobNum = jobCounter; + job->jobNum = jobNum; + job->talker = talker; + job->appId = appId; + job->seq = 1; + job->partCount = 1; + messages.enqueue( job ); + job->sentences = QStringList(); + // Do not apply Sentence Boundary Detection filters to messages. + startJobFiltering( job, message, true ); + // uint count = messages.count(); + // kdDebug() << "Adding '" << temp->text << "' with talker '" << temp->talker << "' from application " << appId << " to the messages queue leaving a total of " << count << " items." << endl; +} + +/** +* Pop (get and erase) a message from the queue. +* @return Pointer to mlText structure containing the message. +* +* Caller is responsible for deleting the structure. +*/ +mlText* SpeechData::dequeueMessage(){ + // kdDebug() << "Running: SpeechData::dequeueMessage()" << endl; + mlJob* job = messages.dequeue(); + waitJobFiltering(job); + mlText* temp = new mlText(); + temp->jobNum = job->jobNum; + temp->text = job->sentences.join(""); + temp->talker = job->talker; + temp->appId = job->appId; + temp->seq = 1; + delete job; + /* mlText *temp = messages.dequeue(); */ + // uint count = messages.count(); + // kdDebug() << "Removing '" << temp->text << "' with talker '" << temp->talker << "' from the messages queue leaving a total of " << count << " items." << endl; + return temp; +} + +/** +* Are there any Messages? +*/ +bool SpeechData::messageInQueue(){ + // kdDebug() << "Running: SpeechData::messageInQueue() const" << endl; + bool temp = !messages.isEmpty(); + // if(temp){ + // kdDebug() << "The messages queue is NOT empty" << endl; + // } else { + // kdDebug() << "The messages queue is empty" << endl; + // } + return temp; +} + +/** +* Determines whether the given text is SSML markup. +*/ +bool SpeechData::isSsml(const QString &text) +{ + /// This checks to see if the root tag of the text is a <speak> tag. + QDomDocument ssml; + ssml.setContent(text, false); // No namespace processing. + /// Check to see if this is SSML + QDomElement root = ssml.documentElement(); + return (root.tagName() == "speak"); +} + +/** +* Parses a block of text into sentences using the application-specified regular expression +* or (if not specified), the default regular expression. +* @param text The message to be spoken. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* @return List of parsed sentences. +* +* If the text contains SSML, it is not parsed into sentences at all. +* TODO: Need a way to preserve SSML but still parse into sentences. +* We will walk before we run for now and not sentence parse. +*/ + +QStringList SpeechData::parseText(const QString &text, const QCString &appId /*=NULL*/) +{ + // There has to be a better way + // kdDebug() << "I'm getting: " << endl << text << " from application " << appId << endl; + if (isSsml(text)) + { + QString tempList(text); + return tempList; + } + // See if app has specified a custom sentence delimiter and use it, otherwise use default. + QRegExp sentenceDelimiter; + if (sentenceDelimiters.find(appId) != sentenceDelimiters.end()) + sentenceDelimiter = QRegExp(sentenceDelimiters[appId]); + else + sentenceDelimiter = QRegExp("([\\.\\?\\!\\:\\;]\\s)|(\\n *\\n)"); + QString temp = text; + // Replace spaces, tabs, and formfeeds with a single space. + temp.replace(QRegExp("[ \\t\\f]+"), " "); + // Replace sentence delimiters with tab. + temp.replace(sentenceDelimiter, "\\1\t"); + // Replace remaining newlines with spaces. + temp.replace("\n"," "); + temp.replace("\r"," "); + // Remove leading spaces. + temp.replace(QRegExp("\\t +"), "\t"); + // Remove trailing spaces. + temp.replace(QRegExp(" +\\t"), "\t"); + // Remove blank lines. + temp.replace(QRegExp("\t\t+"),"\t"); + // Split into sentences. + QStringList tempList = QStringList::split("\t", temp, false); + +// for ( QStringList::Iterator it = tempList.begin(); it != tempList.end(); ++it ) { +// kdDebug() << "'" << *it << "'" << endl; +// } + return tempList; +} + +/** +* Queues a text job. +*/ +uint SpeechData::setText( const QString &text, const QString &talker, const QCString &appId) +{ + // kdDebug() << "Running: SpeechData::setText" << endl; + mlJob* job = new mlJob; + ++jobCounter; + if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums. + uint jobNum = jobCounter; + job->jobNum = jobNum; + job->appId = appId; + job->talker = talker; + job->state = KSpeech::jsQueued; + job->seq = 0; + job->partCount = 1; +#if NO_FILTERS + QStringList tempList = parseText(text, appId); + job->sentences = tempList; + job->partSeqNums.append(tempList.count()); + textJobs.append(job); + emit textSet(appId, jobNum); +#else + job->sentences = QStringList(); + job->partSeqNums = QValueList<int>(); + textJobs.append(job); + startJobFiltering(job, text, false); +#endif + return jobNum; +} + +/** +* Adds another part to a text job. Does not start speaking the text. +* (thread safe) +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application, +* but if no such job, applies to the last job queued by any application. +* @param text The message to be spoken. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* @return Part number for the added part. Parts are numbered starting at 1. +* +* The text is parsed into individual sentences. Call getTextCount to retrieve +* the sentence count. Call startText to mark the job as speakable and if the +* job is the first speakable job in the queue, speaking will begin. +* @see setText. +* @see startText. +*/ +int SpeechData::appendText(const QString &text, const uint jobNum, const QCString& /*appId*/) +{ + // kdDebug() << "Running: SpeechData::appendText" << endl; + int newPartNum = 0; + mlJob* job = findJobByJobNum(jobNum); + if (job) + { + job->partCount++; +#if NO_FILTERS + QStringList tempList = parseText(text, appId); + int sentenceCount = job->sentences.count(); + job->sentences += tempList; + job->partSeqNums.append(sentenceCount + tempList.count()); + newPartNum = job->partSeqNums.count() + 1; + emit textAppended(job->appId, jobNum, newPartNum); +#else + newPartNum = job->partSeqNums.count() + 1; + startJobFiltering(job, text, false); +#endif + } + return newPartNum; +} + +/** +* Given an appId, returns the last (most recently queued) job with that appId. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* @return Pointer to the text job. +* If no such job, returns 0. +* If appId is NULL, returns the last job in the queue. +* Does not change textJobs.current(). +*/ +mlJob* SpeechData::findLastJobByAppId(const QCString& appId) +{ + if (appId == NULL) + return textJobs.getLast(); + else + { + QPtrListIterator<mlJob> it(textJobs); + for (it.toLast() ; it.current(); --it ) + { + if (it.current()->appId == appId) + { + return it.current(); + } + } + return 0; + } +} + +/** +* Given an appId, returns the last (most recently queued) job with that appId, +* or if no such job, the last (most recent) job in the queue. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* @return Pointer to the text job. +* If no such job, returns 0. +* If appId is NULL, returns the last job in the queue. +* Does not change textJobs.current(). +*/ +mlJob* SpeechData::findAJobByAppId(const QCString& appId) +{ + mlJob* job = findLastJobByAppId(appId); + // if (!job) job = textJobs.getLast(); + return job; +} + +/** +* Given an appId, returns the last (most recently queued) Job Number with that appId, +* or if no such job, the Job Number of the last (most recent) job in the queue. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* @return Job Number of the text job. +* If no such job, returns 0. +* If appId is NULL, returns the Job Number of the last job in the queue. +* Does not change textJobs.current(). +*/ +uint SpeechData::findAJobNumByAppId(const QCString& appId) +{ + mlJob* job = findAJobByAppId(appId); + if (job) + return job->jobNum; + else + return 0; +} + +/** +* Given a jobNum, returns the first job with that jobNum. +* @return Pointer to the text job. +* If no such job, returns 0. +* Does not change textJobs.current(). +*/ +mlJob* SpeechData::findJobByJobNum(const uint jobNum) +{ + QPtrListIterator<mlJob> it(textJobs); + for ( ; it.current(); ++it ) + { + if (it.current()->jobNum == jobNum) + { + return it.current(); + } + } + return 0; +} + +/** +* Given a jobNum, returns the appId of the application that owns the job. +* @param jobNum Job number of the text job. +* @return appId of the job. +* If no such job, returns "". +* Does not change textJobs.current(). +*/ +QCString SpeechData::getAppIdByJobNum(const uint jobNum) +{ + QCString appId; + mlJob* job = findJobByJobNum(jobNum); + if (job) appId = job->appId; + return appId; +} + +/** +* Sets pointer to the TalkerMgr object. +*/ +void SpeechData::setTalkerMgr(TalkerMgr* talkerMgr) { m_talkerMgr = talkerMgr; } + +/** +* Remove a text job from the queue. +* (thread safe) +* @param jobNum Job number of the text job. +* +* The job is deleted from the queue and the textRemoved signal is emitted. +*/ +void SpeechData::removeText(const uint jobNum) +{ + // kdDebug() << "Running: SpeechData::removeText" << endl; + uint removeJobNum = 0; + QCString removeAppId; // The appId of the removed (and stopped) job. + mlJob* removeJob = findJobByJobNum(jobNum); + if (removeJob) + { + removeAppId = removeJob->appId; + removeJobNum = removeJob->jobNum; + // If filtering on the job, cancel it. + QPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs ); + for ( ; it.current(); ++it ) { + PooledFilterMgr* pooledFilterMgr = it.current(); + if (pooledFilterMgr->job && (pooledFilterMgr->job->jobNum == removeJobNum)) + { + pooledFilterMgr->busy = false; + pooledFilterMgr->job = 0; + pooledFilterMgr->partNum = 0; + delete pooledFilterMgr->talkerCode; + pooledFilterMgr->talkerCode = 0; + pooledFilterMgr->filterMgr->stopFiltering(); + } + } + // Delete the job. + textJobs.removeRef(removeJob); + } + if (removeJobNum) emit textRemoved(removeAppId, removeJobNum); +} + +/** +* Given a job and a sequence number, returns the part that sentence is in. +* If no such job or sequence number, returns 0. +* @param job The text job. +* @param seq Sequence number of the sentence. Sequence numbers begin with 1. +* @return Part number of the part the sentence is in. Parts are numbered +* beginning with 1. If no such job or sentence, returns 0. +* +*/ +int SpeechData::getJobPartNumFromSeq(const mlJob& job, const int seq) +{ + int foundPartNum = 0; + int desiredSeq = seq; + uint partNum = 0; + // Wait until all filtering has stopped for the job. + waitJobFiltering(&job); + while (partNum < job.partSeqNums.count()) + { + if (desiredSeq <= job.partSeqNums[partNum]) + { + foundPartNum = partNum + 1; + break; + } + desiredSeq = desiredSeq - job.partSeqNums[partNum]; + ++partNum; + } + return foundPartNum; +} + + +/** +* Delete expired jobs. At most, one finished job is kept on the queue. +* @param finishedJobNum Job number of a job that just finished. +* The just finished job is not deleted, but any other finished jobs are. +* Does not change the textJobs.current() pointer. +*/ +void SpeechData::deleteExpiredJobs(const uint finishedJobNum) +{ + // Save current pointer. + typedef QPair<QCString, uint> removedJob; + typedef QValueList<removedJob> removedJobsList; + removedJobsList removedJobs; + // Walk through jobs and delete any other finished jobs. + for (mlJob* job = textJobs.first(); (job); job = textJobs.next()) + { + if (job->jobNum != finishedJobNum && job->state == KSpeech::jsFinished) + { + removedJobs.append(removedJob(job->appId, job->jobNum)); + textJobs.removeRef(job); + } + } + // Emit signals for removed jobs. + removedJobsList::const_iterator it; + removedJobsList::const_iterator endRemovedJobsList(removedJobs.constEnd()); + for (it = removedJobs.constBegin(); it != endRemovedJobsList ; ++it) + { + QCString appId = (*it).first; + uint jobNum = (*it).second; + textRemoved(appId, jobNum); + } +} + +/** +* Given a Job Number, returns the next speakable text job on the queue. +* @param prevJobNum Current job number (which should not be returned). +* @return Pointer to mlJob structure of the first speakable job +* not equal prevJobNum. If no such job, returns null. +* +* Caller must not delete the job. +*/ +mlJob* SpeechData::getNextSpeakableJob(const uint prevJobNum) +{ + for (mlJob* job = textJobs.first(); (job); job = textJobs.next()) + if (job->jobNum != prevJobNum) + if (job->state == KSpeech::jsSpeakable) + { + waitJobFiltering(job); + return job; + } + return 0; +} + +/** +* Given previous job number and sequence number, returns the next sentence from the +* text queue. If no such sentence is available, either because we've run out of +* jobs, or because all jobs are paused, returns null. +* @param prevJobNum Previous Job Number. +* @param prevSeq Previous sequency number. +* @return Pointer to n mlText structure containing the next sentence. If no +* sentence, returns null. +* +* Caller is responsible for deleting the returned mlText structure (if not null). +*/ +mlText* SpeechData::getNextSentenceText(const uint prevJobNum, const uint prevSeq) +{ + // kdDebug() << "SpeechData::getNextSentenceText running with prevJobNum " << prevJobNum << " prevSeq " << prevSeq << endl; + mlText* temp = 0; + uint jobNum = prevJobNum; + mlJob* job = 0; + uint seq = prevSeq; + ++seq; + if (!jobNum) + { + job = getNextSpeakableJob(jobNum); + if (job) seq =+ job->seq; + } else + job = findJobByJobNum(prevJobNum); + if (!job) + { + job = getNextSpeakableJob(jobNum); + if (job) seq =+ job->seq; + } + else + { + if ((job->state != KSpeech::jsSpeakable) && (job->state != KSpeech::jsSpeaking)) + { + job = getNextSpeakableJob(job->jobNum); + if (job) seq =+ job->seq; + } + } + if (job) + { + // If we run out of sentences in the job, move on to next job. + jobNum = job->jobNum; + if (seq > job->sentences.count()) + { + job = getNextSpeakableJob(jobNum); + if (job) seq =+ job->seq; + } + } + if (job) + { + if (seq == 0) seq = 1; + temp = new mlText; + temp->text = job->sentences[seq - 1]; + temp->appId = job->appId; + temp->talker = job->talker; + temp->jobNum = job->jobNum; + temp->seq = seq; + // kdDebug() << "SpeechData::getNextSentenceText: return job number " << temp->jobNum << " seq " << temp->seq << " sentence count = " << job->sentences.count() << endl; + } // else kdDebug() << "SpeechData::getNextSentenceText: no more sentences in queue" << endl; + return temp; +} + +/** +* Given a Job Number, sets the current sequence number of the job. +* @param jobNum Job Number. +* @param seq Sequence number. +* If for some reason, the job does not exist, nothing happens. +*/ +void SpeechData::setJobSequenceNum(const uint jobNum, const uint seq) +{ + mlJob* job = findJobByJobNum(jobNum); + if (job) job->seq = seq; +} + +/** +* Given a Job Number, returns the current sequence number of the job. +* @param jobNum Job Number. +* @return Sequence number of the job. If no such job, returns 0. +*/ +uint SpeechData::getJobSequenceNum(const uint jobNum) +{ + mlJob* job = findJobByJobNum(jobNum); + if (job) + return job->seq; + else + return 0; +} + +/** +* Sets the GREP pattern that will be used as the sentence delimiter. +* @param delimiter A valid GREP pattern. +* @param appId The DCOP senderId of the application. NULL if kttsd. +* +* The default delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])\\s + @endverbatim +* +* Note that backward slashes must be escaped. +* +* Changing the sentence delimiter does not affect other applications. +* @see sentenceparsing +*/ +void SpeechData::setSentenceDelimiter(const QString &delimiter, const QCString appId) +{ + sentenceDelimiters[appId] = delimiter; +} + +/** +* Get the number of sentences in a text job. +* (thread safe) +* @param jobNum Job number of the text job. +* @return The number of sentences in the job. -1 if no such job. +* +* The sentences of a job are given sequence numbers from 1 to the number returned by this +* method. The sequence numbers are emitted in the sentenceStarted and sentenceFinished signals. +*/ +int SpeechData::getTextCount(const uint jobNum) +{ + mlJob* job = findJobByJobNum(jobNum); + int temp; + if (job) + { + waitJobFiltering(job); + temp = job->sentences.count(); + } else + temp = -1; + return temp; +} + +/** +* Get the number of jobs in the text job queue. +* (thread safe) +* @return Number of text jobs in the queue. 0 if none. +*/ +uint SpeechData::getTextJobCount() +{ + return textJobs.count(); +} + +/** +* Get a comma-separated list of text job numbers in the queue. +* @return Comma-separated list of text job numbers in the queue. +*/ +QString SpeechData::getTextJobNumbers() +{ + QString jobs; + QPtrListIterator<mlJob> it(textJobs); + for ( ; it.current(); ++it ) + { + if (!jobs.isEmpty()) jobs.append(","); + jobs.append(QString::number(it.current()->jobNum)); + } + return jobs; +} + +/** +* Get the state of a text job. +* (thread safe) +* @param jobNum Job number of the text job. +* @return State of the job. -1 if invalid job number. +*/ +int SpeechData::getTextJobState(const uint jobNum) +{ + mlJob* job = findJobByJobNum(jobNum); + int temp; + if (job) + temp = job->state; + else + temp = -1; + return temp; +} + +/** +* Set the state of a text job. +* @param jobNum Job Number of the job. +* @param state New state for the job. +* +* If the new state is Finished, deletes other expired jobs. +* +**/ +void SpeechData::setTextJobState(const uint jobNum, const KSpeech::kttsdJobState state) +{ + mlJob* job = findJobByJobNum(jobNum); + if (job) + { + job->state = state; + if (state == KSpeech::jsFinished) deleteExpiredJobs(jobNum); + } +} + +/** +* Get information about a text job. +* @param jobNum Job number of the text job. +* @return A QDataStream containing information about the job. +* Blank if no such job. +* +* The stream contains the following elements: +* - int state Job state. +* - QCString appId DCOP senderId of the application that requested the speech job. +* - QString talker Language code in which to speak the text. +* - int seq Current sentence being spoken. Sentences are numbered starting at 1. +* - int sentenceCount Total number of sentences in the job. +* - int partNum Current part of the job begin spoken. Parts are numbered starting at 1. +* - int partCount Total number of parts in the job. +* +* Note that sequence numbers apply to the entire job. +* They do not start from 1 at the beginning of each part. +* +* The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talker; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + @endverbatim +*/ +QByteArray SpeechData::getTextJobInfo(const uint jobNum) +{ + mlJob* job = findJobByJobNum(jobNum); + QByteArray temp; + if (job) + { + waitJobFiltering(job); + QDataStream stream(temp, IO_WriteOnly); + stream << job->state; + stream << job->appId; + stream << job->talker; + stream << job->seq; + stream << job->sentences.count(); + stream << getJobPartNumFromSeq(*job, job->seq); + stream << job->partSeqNums.count(); + } + return temp; +} + +/** +* Return a sentence of a job. +* @param jobNum Job number of the text job. +* @param seq Sequence number of the sentence. +* @return The specified sentence in the specified job. If no such +* job or sentence, returns "". +*/ +QString SpeechData::getTextJobSentence(const uint jobNum, const uint seq /*=1*/) +{ + mlJob* job = findJobByJobNum(jobNum); + QString temp; + if (job) + { + waitJobFiltering(job); + temp = job->sentences[seq - 1]; + } + return temp; +} + +/** +* Change the talker for a text job. +* @param jobNum Job number of the text job. +* If zero, applies to the last job queued by the application, +* but if no such job, applies to the last job queued by any application. +* @param talker New code for the talker to do the speaking. Example "en". +* If NULL, defaults to the user's default talker. +* If no plugin has been configured for the specified Talker code, +* defaults to the closest matching talker. +*/ +void SpeechData::changeTextTalker(const QString &talker, uint jobNum) +{ + mlJob* job = findJobByJobNum(jobNum); + if (job) job->talker = talker; +} + +/** +* Move a text job down in the queue so that it is spoken later. +* @param jobNum Job number of the text job. +*/ +void SpeechData::moveTextLater(const uint jobNum) +{ + // kdDebug() << "Running: SpeechData::moveTextLater" << endl; + mlJob* job = findJobByJobNum(jobNum); + if (job) + { + // Get index of the job. + uint index = textJobs.findRef(job); + // Move job down one position in the queue. + // kdDebug() << "In SpeechData::moveTextLater, moving jobNum " << movedJobNum << endl; + if (textJobs.insert(index + 2, job)) textJobs.take(index); + } +} + +/** +* Jump to the first sentence of a specified part of a text job. +* @param partNum Part number of the part to jump to. Parts are numbered starting at 1. +* @param jobNum Job number of the text job. +* @return Part number of the part actually jumped to. +* +* If partNum is greater than the number of parts in the job, jumps to last part. +* If partNum is 0, does nothing and returns the current part number. +* If no such job, does nothing and returns 0. +* Does not affect the current speaking/not-speaking state of the job. +*/ +int SpeechData::jumpToTextPart(const int partNum, const uint jobNum) +{ + // kdDebug() << "Running: SpeechData::jumpToTextPart" << endl; + int newPartNum = 0; + mlJob* job = findJobByJobNum(jobNum); + if (job) + { + waitJobFiltering(job); + if (partNum > 0) + { + newPartNum = partNum; + int partCount = job->partSeqNums.count(); + if (newPartNum > partCount) newPartNum = partCount; + if (newPartNum > 1) + job->seq = job->partSeqNums[newPartNum - 1]; + else + job->seq = 0; + } + else + newPartNum = getJobPartNumFromSeq(*job, job->seq); + } + return newPartNum; +} + +/** +* Advance or rewind N sentences in a text job. +* @param n Number of sentences to advance (positive) or rewind (negative) +* in the job. +* @param jobNum Job number of the text job. +* @return Sequence number of the sentence actually moved to. Sequence numbers +* are numbered starting at 1. +* +* If no such job, does nothing and returns 0. +* If n is zero, returns the current sequence number of the job. +* Does not affect the current speaking/not-speaking state of the job. +*/ +uint SpeechData::moveRelTextSentence(const int n, const uint jobNum /*=0*/) +{ + // kdDebug() << "Running: SpeechData::moveRelTextSentence" << endl; + int newSeqNum = 0; + mlJob* job = findJobByJobNum(jobNum); + if (job) + { + waitJobFiltering(job); + int oldSeqNum = job->seq; + newSeqNum = oldSeqNum + n; + if (n != 0) + { + if (newSeqNum < 0) newSeqNum = 0; + int sentenceCount = job->sentences.count(); + if (newSeqNum > sentenceCount) newSeqNum = sentenceCount; + job->seq = newSeqNum; + } + } + return newSeqNum; +} + +/** +* Assigns a FilterMgr to a job and starts filtering on it. +*/ +void SpeechData::startJobFiltering(mlJob* job, const QString& text, bool noSBD) +{ + uint jobNum = job->jobNum; + int partNum = job->partCount; + // kdDebug() << "SpeechData::startJobFiltering: jobNum = " << jobNum << " partNum = " << partNum << " text.left(500) = " << text.left(500) << endl; + // Find an idle FilterMgr, if any. + // If filtering is already in progress for this job and part, do nothing. + PooledFilterMgr* pooledFilterMgr = 0; + QPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs ); + for( ; it.current(); ++it ) + { + if (it.current()->busy) { + if ((it.current()->job->jobNum == jobNum) && (it.current()->partNum == partNum)) return; + } else { + if (!it.current()->job && !pooledFilterMgr) pooledFilterMgr = it.current(); + } + } + // Create a new FilterMgr if needed and add to pool. + if (!pooledFilterMgr) + { + // kdDebug() << "SpeechData::startJobFiltering: adding new pooledFilterMgr for job " << jobNum << " part " << partNum << endl; + pooledFilterMgr = new PooledFilterMgr(); + FilterMgr* filterMgr = new FilterMgr(); + filterMgr->init(config, "General"); + pooledFilterMgr->filterMgr = filterMgr; + // Connect signals from FilterMgr. + connect (filterMgr, SIGNAL(filteringFinished()), this, SLOT(slotFilterMgrFinished())); + connect (filterMgr, SIGNAL(filteringStopped()), this, SLOT(slotFilterMgrStopped())); + m_pooledFilterMgrs.append(pooledFilterMgr); + } + // else kdDebug() << "SpeechData::startJobFiltering: re-using idle pooledFilterMgr for job " << jobNum << " part " << partNum << endl; + // Flag the FilterMgr as busy and set it going. + pooledFilterMgr->busy = true; + pooledFilterMgr->job = job; + pooledFilterMgr->partNum = partNum; + pooledFilterMgr->filterMgr->setNoSBD( noSBD ); + // Get TalkerCode structure of closest matching Talker. + pooledFilterMgr->talkerCode = m_talkerMgr->talkerToTalkerCode(job->talker); + // Pass Sentence Boundary regular expression (if app overrode default); + if (sentenceDelimiters.find(job->appId) != sentenceDelimiters.end()) + pooledFilterMgr->filterMgr->setSbRegExp(sentenceDelimiters[job->appId]); + pooledFilterMgr->filterMgr->asyncConvert(text, pooledFilterMgr->talkerCode, job->appId); +} + +/** +* Waits for filtering to be completed on a job. +* This is typically called because an app has requested job info that requires +* filtering to be completed, such as getJobInfo. +*/ +void SpeechData::waitJobFiltering(const mlJob* job) +{ +#if NO_FILTERS + return; +#endif + uint jobNum = job->jobNum; + bool waited = false; + QPtrListIterator<PooledFilterMgr> it(m_pooledFilterMgrs); + for ( ; it.current(); ++it ) + { + PooledFilterMgr* pooledFilterMgr = it.current(); + if (pooledFilterMgr->busy) + { + if (pooledFilterMgr->job->jobNum == jobNum) + { + if (!pooledFilterMgr->filterMgr->noSBD()) + kdDebug() << "SpeechData::waitJobFiltering: Waiting for filter to finish. Not optimium. " << + "Try waiting for textSet signal before querying for job information." << endl; + pooledFilterMgr->filterMgr->waitForFinished(); + // kdDebug() << "SpeechData::waitJobFiltering: waiting for job " << jobNum << endl; + waited = true; + } + } + } + if (waited) + doFiltering(); +} + +/** +* Processes filters by looping across the pool of FilterMgrs. +* As each FilterMgr finishes, emits appropriate signals and flags it as no longer busy. +*/ +void SpeechData::doFiltering() +{ + // kdDebug() << "SpeechData::doFiltering: Running. " << m_pooledFilterMgrs.count() << " filters in pool." << endl; + bool again = true; + while (again) + { + again = false; + QPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs ); + for( ; it.current(); ++it ) + { + PooledFilterMgr* pooledFilterMgr = it.current(); + // If FilterMgr is busy, see if it is now finished. + if (pooledFilterMgr->busy) + { + FilterMgr* filterMgr = pooledFilterMgr->filterMgr; + if (filterMgr->getState() == FilterMgr::fsFinished) + { + mlJob* job = pooledFilterMgr->job; + // kdDebug() << "SpeechData::doFiltering: filter finished, jobNum = " << job->jobNum << " partNum = " << pooledFilterMgr->partNum << endl; + // We have to retrieve parts in order, but parts may not be completed in order. + // See if this is the next part we need. + if ((int)job->partSeqNums.count() == (pooledFilterMgr->partNum - 1)) + { + pooledFilterMgr->busy = false; + // Retrieve text from FilterMgr. + QString text = filterMgr->getOutput(); + // kdDebug() << "SpeechData::doFiltering: text.left(500) = " << text.left(500) << endl; + filterMgr->ackFinished(); + // Convert the TalkerCode back into string. + job->talker = pooledFilterMgr->talkerCode->getTalkerCode(); + // TalkerCode object no longer needed. + delete pooledFilterMgr->talkerCode; + pooledFilterMgr->talkerCode = 0; + if (filterMgr->noSBD()) + job->sentences = text; + else + { + // Split the text into sentences and store in the job. + // The SBD plugin does all the real sentence parsing, inserting tabs at each + // sentence boundary. + QStringList sentences = QStringList::split("\t", text, false); + int sentenceCount = job->sentences.count(); + job->sentences += sentences; + job->partSeqNums.append(sentenceCount + sentences.count()); + } + int partNum = job->partSeqNums.count(); + // Clean up. + pooledFilterMgr->job = 0; + pooledFilterMgr->partNum = 0; + // Emit signal. + if (!filterMgr->noSBD()) + { + if (partNum == 1) + emit textSet(job->appId, job->jobNum); + else + emit textAppended(job->appId, job->jobNum, partNum); + } + } else { + // A part is ready, but need to first process a finished preceeding part + // that follows this one in the pool of filter managers. + again = true; + // kdDebug() << "SpeechData::doFiltering: filter is finished, but must wait for earlier part to finish filter, job = " << pooledFilterMgr->job->jobNum << endl; + } + } + // else kdDebug() << "SpeechData::doFiltering: filter for job " << pooledFilterMgr->job->jobNum << " is busy." << endl; + } + // else kdDebug() << "SpeechData::doFiltering: filter is idle" << endl; + } + } +} + +void SpeechData::slotFilterMgrFinished() +{ + // kdDebug() << "SpeechData::slotFilterMgrFinished: received signal FilterMgr finished signal." << endl; + doFiltering(); +} + +void SpeechData::slotFilterMgrStopped() +{ + doFiltering(); +} + diff --git a/kttsd/kttsd/speechdata.h b/kttsd/kttsd/speechdata.h new file mode 100644 index 0000000..40294bf --- /dev/null +++ b/kttsd/kttsd/speechdata.h @@ -0,0 +1,731 @@ +/*************************************************** vim:set ts=4 sw=4 sts=4: + This contains the SpeechData class which is in charge of maintaining + all the data on the memory. + It maintains queues manages the text. + We could say that this is the common repository between the KTTSD class + (dcop service) and the Speaker class (speaker, loads plug ins, call plug in + functions) + ------------------- + Copyright: + (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + (C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org> + (C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández + ******************************************************************************/ + +/****************************************************************************** + * * + * 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. * + * * + ******************************************************************************/ + +#ifndef _SPEECHDATA_H_ +#define _SPEECHDATA_H_ + +// Qt includes. +#include <qptrqueue.h> +#include <qptrlist.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qmap.h> + +// KDE includes. +#include <kconfig.h> + +// KTTS includes. +#include <kspeech.h> +#include <talkercode.h> +#include <filtermgr.h> + +class TalkerMgr; + +/** +* Struct containing a text cell, for messages, warnings, and texts. +* Contains the text itself, the associated talker, +* the ID of the application that requested it be spoken, and a sequence number. +*/ +struct mlText{ + QString talker; /* Requested Talker code for the sentence. */ + QString text; /* Text of sentence. */ + QCString appId; /* DCOP senderId of the application that requested the speech. */ + uint jobNum; /* Text jobNum. Only applies to text messages; not warning and messages. */ + uint seq; /* Sequence number. */ +}; + +/** + * Struct containing a text job. + */ +struct mlJob { + uint jobNum; /* Job number. */ + KSpeech::kttsdJobState state; /* Job state. */ + QCString appId; /* DCOP senderId of the application that requested the speech job. */ + QString talker; /* Requested Talker code in which to speak the text. */ + int seq; /* Current sentence being spoken. */ + QValueList<int> partSeqNums; /* List containing last sequence number for each part of a job. */ + QStringList sentences; /* List of sentences in the job. */ + int partCount; /* Number of parts in the job. */ +}; + +/** + * Struct used to keep a pool of FilterMgr objects. + */ +struct PooledFilterMgr { + FilterMgr* filterMgr; /* The FilterMgr object. */ + bool busy; /* True if the FilterMgr is busy. */ + mlJob* job; /* The job the FilterMgr is filtering. */ + int partNum; /* The part number of the job that is filtering. */ + TalkerCode* talkerCode; /* TalkerCode object passed to FilterMgr. */ +}; + +/** + * Struct used to keep notification options. + */ +struct NotifyOptions { + QString eventName; + int action; + QString talker; + QString customMsg; +}; + +/** + * A list of notification options for a single app, indexed by event. + */ +typedef QMap<QString, NotifyOptions> NotifyEventMap; + +/** + * A list of notification event maps for all apps, indexed by app. + */ +typedef QMap<QString, NotifyEventMap> NotifyAppMap; + +/** + * SpeechData class which is in charge of maintaining all the data on the memory. + * It maintains queues and has methods to enque + * messages and warnings and manage the text queues. + * We could say that this is the common repository between the KTTSD class + * (dcop service) and the Speaker class (speaker, loads plug ins, call plug in + * functions) + */ +class SpeechData : public QObject { + Q_OBJECT + + public: + /** + * Constructor + * Sets text to be stopped and warnings and messages queues to be autodelete (thread safe) + */ + SpeechData(); + + /** + * Destructor + */ + ~SpeechData(); + + /** + * Read the configuration + */ + bool readConfig(); + + /** + * Say a message as soon as possible, interrupting any other speech in progress. + * IMPORTANT: This method is reserved for use by Screen Readers and should not be used + * by any other applications. + * @param msg The message to be spoken. + * @param talker Code for the talker to speak the message. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @param appId The DCOP senderId of the application. + * + * If an existing Screen Reader output is in progress, it is stopped and discarded and + * replaced with this new message. + */ + void setScreenReaderOutput(const QString &msg, const QString &talker, + const QCString& appId); + + /** + * Given an appId, returns the last (most recently queued) Job Number with that appId, + * or if no such job, the Job Number of the last (most recent) job in the queue. + * @param appId The DCOP senderId of the application. + * @return Job Number of the text job. + * If no such job, returns 0. + * If appId is NULL, returns the Job Number of the last job in the queue. + * Does not change textJobs.current(). + */ + uint findAJobNumByAppId(const QCString& appId); + + /** + * Retrieves the Screen Reader Output. + */ + mlText* getScreenReaderOutput(); + + /** + * Returns true if Screen Reader Output is ready to be spoken. + */ + bool screenReaderOutputReady(); + + /** + * Add a new warning to the queue. + */ + void enqueueWarning( const QString &, const QString &talker, + const QCString& appId); + + /** + * Pop (get and erase) a warning from the queue. + * @return Pointer to mlText structure containing the warning. + * + * Caller is responsible for deleting the structure. + */ + mlText* dequeueWarning(); + + /** + * Are there any Warnings? + */ + bool warningInQueue(); + + /** + * Add a new message to the queue. + */ + void enqueueMessage( const QString &, const QString &talker, + const QCString&); + + /** + * Pop (get and erase) a message from the queue. + * @return Pointer to mlText structure containing the message. + * + * Caller is responsible for deleting the structure. + */ + mlText* dequeueMessage(); + + /** + * Are there any Messages? + */ + bool messageInQueue(); + + /** + * Sets the GREP pattern that will be used as the sentence delimiter. + * @param delimiter A valid GREP pattern. + * @param appId The DCOP senderId of the application. + * + * The default delimiter is + @verbatim + ([\\.\\?\\!\\:\\;])\\s + @endverbatim + * + * Note that backward slashes must be escaped. + * + * Changing the sentence delimiter does not affect other applications. + * @see sentenceparsing + */ + void setSentenceDelimiter(const QString &delimiter, const QCString appId); + + /* The following methods correspond to the methods in KSpeech interface. */ + + /** + * Queue a text job. Does not start speaking the text. + * (thread safe) + * @param text The message to be spoken. + * @param talker Code for the talker to speak the text. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + * @param appId The DCOP senderId of the application. + * @return Job number. + * + * The text is parsed into individual sentences. Call getTextCount to retrieve + * the sentence count. Call startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * @see startText. + */ + uint setText(const QString &text, const QString &talker, const QCString& appId); + + /** + * Adds another part to a text job. Does not start speaking the text. + * (thread safe) + * @param jobNum Job number of the text job. + * @param text The message to be spoken. + * @param appId The DCOP senderId of the application. + * @return Part number for the added part. Parts are numbered starting at 1. + * + * The text is parsed into individual sentences. Call getTextCount to retrieve + * the sentence count. Call startText to mark the job as speakable and if the + * job is the first speakable job in the queue, speaking will begin. + * @see setText. + * @see startText. + */ + int appendText(const QString &text, const uint jobNum, const QCString& appId); + + /** + * Get the number of sentences in a text job. + * (thread safe) + * @param jobNum Job number of the text job. + * @return The number of sentences in the job. -1 if no such job. + * + * The sentences of a job are given sequence numbers from 1 to the number returned by this + * method. The sequence numbers are emitted in the sentenceStarted and sentenceFinished signals. + */ + int getTextCount(const uint jobNum); + + /** + * Get the number of jobs in the text job queue. + * (thread safe) + * @return Number of text jobs in the queue. 0 if none. + */ + uint getTextJobCount(); + + /** + * Get a comma-separated list of text job numbers in the queue. + * @return Comma-separated list of text job numbers in the queue. + */ + QString getTextJobNumbers(); + + /** + * Get the state of a text job. + * (thread safe) + * @param jobNum Job number of the text job. + * @return State of the job. -1 if invalid job number. + */ + int getTextJobState(const uint jobNum); + + /** + * Set the state of a text job. + * @param jobNum Job Number of the job. + * @param state New state for the job. + * + **/ + void setTextJobState(const uint jobNum, const KSpeech::kttsdJobState state); + + /** + * Get information about a text job. + * @param jobNum Job number of the text job. + * @return A QDataStream containing information about the job. + * Blank if no such job. + * + * The stream contains the following elements: + * - int state Job state. + * - QCString appId DCOP senderId of the application that requested the speech job. + * - QString talker Talker code as requested by application. + * - int seq Current sentence being spoken. Sentences are numbered starting at 1. + * - int sentenceCount Total number of sentences in the job. + * - int partNum Current part of the job begin spoken. Parts are numbered starting at 1. + * - int partCount Total number of parts in the job. + * + * Note that sequence numbers apply to the entire job. + * They do not start from 1 at the beginning of each part. + * + * The following sample code will decode the stream: + @verbatim + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talker; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + @endverbatim + */ + QByteArray getTextJobInfo(const uint jobNum); + + /** + * Return a sentence of a job. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the sentence. + * @return The specified sentence in the specified job. If no such + * job or sentence, returns "". + */ + QString getTextJobSentence(const uint jobNum, const uint seq=1); + + /** + * Remove a text job from the queue. + * (thread safe) + * @param jobNum Job number of the text job. + * + * The job is deleted from the queue and the textRemoved signal is emitted. + */ + void removeText(const uint jobNum); + + /** + * Change the talker for a text job. + * @param jobNum Job number of the text job. + * @param talker New code for the talker to do speaking. Example "en". + * If NULL, defaults to the user's default talker. + * If no plugin has been configured for the specified Talker code, + * defaults to the closest matching talker. + */ + void changeTextTalker(const QString &talker, uint jobNum); + + /** + * Move a text job down in the queue so that it is spoken later. + * @param jobNum Job number of the text job. + */ + void moveTextLater(const uint jobNum); + + /** + * Jump to the first sentence of a specified part of a text job. + * @param partNum Part number of the part to jump to. Parts are numbered starting at 1. + * @param jobNum Job number of the text job. + * @return Part number of the part actually jumped to. + * + * If partNum is greater than the number of parts in the job, jumps to last part. + * If partNum is 0, does nothing and returns the current part number. + * If no such job, does nothing and returns 0. + * Does not affect the current speaking/not-speaking state of the job. + */ + int jumpToTextPart(const int partNum, const uint jobNum); + + /** + * Advance or rewind N sentences in a text job. + * @param n Number of sentences to advance (positive) or rewind (negative) + * in the job. + * @param jobNum Job number of the text job. + * @return Sequence number of the sentence actually moved to. Sequence numbers + * are numbered starting at 1. + * + * If no such job, does nothing and returns 0. + * If n is zero, returns the current sequence number of the job. + * Does not affect the current speaking/not-speaking state of the job. + */ + uint moveRelTextSentence(const int n, const uint jobNum); + + /** + * Given a jobNum, returns the first job with that jobNum. + * @return Pointer to the text job. + * If no such job, returns 0. + * Does not change textJobs.current(). + */ + mlJob* findJobByJobNum(const uint jobNum); + + /** + * Given a Job Number, returns the next speakable text job on the queue. + * @param prevJobNum Current job number (which should not be returned). + * @return Pointer to mlJob structure of the first speakable job + * not equal prevJobNum. If no such job, returns null. + * + * Caller must not delete the job. + */ + mlJob* getNextSpeakableJob(const uint prevJobNum); + + /** + * Given previous job number and sequence number, returns the next sentence from the + * text queue. If no such sentence is available, either because we've run out of + * jobs, or because all jobs are paused, returns null. + * @param prevJobNum Previous Job Number. + * @param prevSeq Previous sequency number. + * @return Pointer to n mlText structure containing the next sentence. If no + * sentence, returns null. + * + * Caller is responsible for deleting the returned mlText structure (if not null). + */ + mlText* getNextSentenceText(const uint prevJobNum, const uint prevSeq); + + /** + * Given a Job Number, sets the current sequence number of the job. + * @param jobNum Job Number. + * @param seq Sequence number. + * If for some reason, the job does not exist, nothing happens. + */ + void setJobSequenceNum(const uint jobNum, const uint seq); + + /** + * Given a Job Number, returns the current sequence number of the job. + * @param jobNum Job Number. + * @return Sequence number of the job. If no such job, returns 0. + */ + uint getJobSequenceNum(const uint jobNum); + + /** + * Given a jobNum, returns the appId of the application that owns the job. + * @param jobNum Job number of the text job. + * @return appId of the job. + * If no such job, returns "". + * Does not change textJobs.current(). + */ + QCString getAppIdByJobNum(const uint jobNum); + + /** + * Sets pointer to the TalkerMgr object. + */ + void setTalkerMgr(TalkerMgr* talkerMgr); + + /* The following properties come from the configuration. */ + + /** + * Text pre message + */ + QString textPreMsg; + + /** + * Text pre message enabled ? + */ + bool textPreMsgEnabled; + + /** + * Text pre sound + */ + QString textPreSnd; + + /** + * Text pre sound enabled ? + */ + bool textPreSndEnabled; + + /** + * Text post message + */ + QString textPostMsg; + + /** + * Text post message enabled ? + */ + bool textPostMsgEnabled; + + /** + * Text post sound + */ + QString textPostSnd; + + /** + * Text post sound enabled ? + */ + bool textPostSndEnabled; + + /** + * Paragraph pre message + */ + QString parPreMsg; + + /** + * Paragraph pre message enabled ? + */ + bool parPreMsgEnabled; + + /** + * Paragraph pre sound + */ + QString parPreSnd; + + /** + * Paragraph pre sound enabled ? + */ + bool parPreSndEnabled; + + /** + * Paragraph post message + */ + QString parPostMsg; + + /** + * Paragraph post message enabled ? + */ + bool parPostMsgEnabled; + + /** + * Paragraph post sound + */ + QString parPostSnd; + + /** + * Paragraph post sound enabled ? + */ + bool parPostSndEnabled; + + /** + * Keep audio files. Do not delete generated tmp wav files. + */ + bool keepAudio; + QString keepAudioPath; + + /** + * Notification settings. + */ + bool notify; + bool notifyExcludeEventsWithSound; + NotifyAppMap notifyAppMap; + int notifyDefaultPresent; + NotifyOptions notifyDefaultOptions; + + /** + * Automatically start KTTSMgr whenever speaking. + */ + bool autoStartManager; + + /** + * Automatically exit auto-started KTTSMgr when speaking finishes. + */ + bool autoExitManager; + + /** + * Configuration + */ + KConfig *config; + + /** + * True if at least one XML Transformer plugin for html is enabled. + */ + bool supportsHTML; + + signals: + /** + * This signal is emitted whenever a new text job is added to the queue. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textSet(const QCString& appId, const uint jobNum); + + /** + * This signal is emitted whenever a new part is appended to a text job. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + * @param partNum Part number of the new part. Parts are numbered starting + * at 1. + */ + void textAppended(const QCString& appId, const uint jobNum, const int partNum); + + /** + * This signal is emitted whenever a text job is deleted from the queue. + * The job is no longer in the queue when this signal is emitted. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + */ + void textRemoved(const QCString& appId, const uint jobNum); + + private: + /** + * Screen Reader Output. + */ + mlText screenReaderOutput; + + /** + * Queue of warnings + */ + QPtrQueue<mlJob> warnings; + + /** + * Queue of messages + */ + QPtrQueue<mlJob> messages; + + /** + * Queue of text jobs. + */ + QPtrList<mlJob> textJobs; + + /** + * TalkerMgr object local pointer. + */ + TalkerMgr* m_talkerMgr; + + /** + * Pool of FilterMgrs. + */ + QPtrList<PooledFilterMgr> m_pooledFilterMgrs; + + /** + * Job counter. Each new job increments this counter. + */ + uint jobCounter; + + /** + * Talker of the text + */ + QString textTalker; + + /** + * Map of sentence delimiters. One per app. If none specified for an app, uses default. + */ + QMap<QCString, QString> sentenceDelimiters; + + /** + * Determines whether the given text is SSML markup. + */ + bool isSsml(const QString &text); + + /** + * Given an appId, returns the last (most recently queued) job with that appId. + * @param appId The DCOP senderId of the application. + * @return Pointer to the text job. + * If no such job, returns 0. + * If appId is NULL, returns the last job in the queue. + * Does not change textJobs.current(). + */ + mlJob* findLastJobByAppId(const QCString& appId); + + /** + * Given an appId, returns the last (most recently queued) job with that appId, + * or if no such job, the last (most recent) job in the queue. + * @param appId The DCOP senderId of the application. + * @return Pointer to the text job. + * If no such job, returns 0. + * If appId is NULL, returns the last job in the queue. + * Does not change textJobs.current(). + */ + mlJob* findAJobByAppId(const QCString& appId); + + /** + * Given a job and a sequence number, returns the part that sentence is in. + * If no such job or sequence number, returns 0. + * @param job The text job. + * @param seq Sequence number of the sentence. Sequence numbers begin with 1. + * @return Part number of the part the sentence is in. Parts are numbered + * beginning with 1. If no such job or sentence, returns 0. + */ + int getJobPartNumFromSeq(const mlJob& job, const int seq); + + /** + * Parses a block of text into sentences using the application-specified regular expression + * or (if not specified), the default regular expression. + * @param text The message to be spoken. + * @param appId The DCOP senderId of the application. + * @return List of parsed sentences. + */ + + QStringList parseText(const QString &text, const QCString &appId); + + /** + * Delete expired jobs. At most, one finished job is kept on the queue. + * @param finishedJobNum Job number of a job that just finished + * The just finished job is not deleted, but any other finished jobs are. + * Does not change the textJobs.current() pointer. + */ + void deleteExpiredJobs(const uint finishedJobNum); + + /** + * Assigns a FilterMgr to a job and starts filtering on it. + */ + void startJobFiltering(mlJob* job, const QString& text, bool noSBD); + + /** + * Waits for filtering to be completed on a job. + * This is typically called because an app has requested job info that requires + * filtering to be completed, such as getJobInfo. + */ + void waitJobFiltering(const mlJob* job); + + /** + * Processes filters by looping across the pool of FilterMgrs. + * As each FilterMgr finishes, emits appropriate signals and flags it as no longer busy. + */ + void doFiltering(); + + /** + * Loads notify events from a file. Clearing data if clear is True. + */ + void loadNotifyEventsFromFile( const QString& filename, bool clear); + + private slots: + void slotFilterMgrFinished(); + void slotFilterMgrStopped(); +}; + +#endif // _SPEECHDATA_H_ diff --git a/kttsd/kttsd/ssmlconvert.cpp b/kttsd/kttsd/ssmlconvert.cpp new file mode 100644 index 0000000..521a9a6 --- /dev/null +++ b/kttsd/kttsd/ssmlconvert.cpp @@ -0,0 +1,295 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + SSMLConvert class + + This class is in charge of converting SSML text into a format that can + be handled by individual synths. + ------------------- + Copyright: + (C) 2004 by Paul Giannaros <ceruleanblaze@gmail.com> + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> +******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> +#include <qdom.h> +#include <qfile.h> +#include <qtextstream.h> + +// KDE includes. +#include <kdeversion.h> +#include <kstandarddirs.h> +#include <kprocess.h> +#include <ktempfile.h> +#include <kdebug.h> + +// SSMLConvert includes. +#include "ssmlconvert.h" +#include "ssmlconvert.moc" + +/// Constructor. +SSMLConvert::SSMLConvert() { + m_talkers = QStringList(); + m_xsltProc = 0; + m_state = tsIdle; +} + +/// Constructor. Set the talkers to be used as reference for entered text. +SSMLConvert::SSMLConvert(const QStringList &talkers) { + m_talkers = talkers; + m_xsltProc = 0; + m_state = tsIdle; +} + +/// Destructor. +SSMLConvert::~SSMLConvert() { + delete m_xsltProc; + if (!m_inFilename.isEmpty()) QFile::remove(m_inFilename); + if (!m_outFilename.isEmpty()) QFile::remove(m_outFilename); +} + +/// Set the talkers to be used as reference for entered text. +void SSMLConvert::setTalkers(const QStringList &talkers) { + m_talkers = talkers; +} + +QString SSMLConvert::extractTalker(const QString &talkercode) { + QString t = talkercode.section("synthesizer=", 1, 1); + t = t.section('"', 1, 1); + if(t.contains("flite")) + return "flite"; + else + return t.left(t.find(" ")).lower(); +} + +/** +* Return the most appropriate talker for the text to synth talker code. +* @param text the text that will be parsed. +* @returns the appropriate talker for the job as a talker code. +* +* The appropriate talker is the one that has the most features that are required in some +* SSML markup. In the future i'm hoping to make the importance of individual features +* configurable, but better to walk before you can run. +* Currently, the searching method in place is like a filter: Those that meet the criteria we're +* searchin for stay while others are sifted out. This should leave us with the right talker to use. +* It's not a very good method, but should be appropriate in most cases and should do just fine for now. +* +* As it stands, here is the list of things that are looked for, in order of most importance: +* - Language +* Obviously the most important. If a language is specified, look for the talkers that support it. +* Default to en (or some form of en - en_US, en_GB, etc). Only one language at a time is allowed +* at the moment, and must be specified in the root speak element (<speak xml:lang="en-US">) +* - Gender +* If a gender is specified, look for talkers that comply. There is no default so if no gender is +* specified, no talkers will be removed. The only gender that will be searched for is the one +* specified in the root speak element. This should change in the future. +* - Prosody +* Check if prosody modification is allowed by the talker. Currently this is hardcoded (it +* is stated which talkers do and do not in a variable somewhere). +* +* Bear in mind that the XSL stylesheet that will be applied to the SSML is the same regardless +* of the how the talker is chosen, meaning that you don't lose some features of the talker if this +* search doesn't encompass them. +* +* QDom is the item of choice for the matching. Just walk the tree.. +*/ +QString SSMLConvert::appropriateTalker(const QString &text) const { + QDomDocument ssml; + ssml.setContent(text, false); // No namespace processing. + /// Matches are stored here. Obviously to begin with every talker matches. + QStringList matches = m_talkers; + + /// Check that this is (well formed) SSML and all our searching will not be in vain. + QDomElement root = ssml.documentElement(); + if(root.tagName() != "speak") { + // Not SSML. + return QString::null; + } + + /** + * For each rule that we are looking through, iterate over all currently + * matching talkers and remove all the talkers that don't match. + * + * Storage for talker code components. + */ + QString talklang, talkvoice, talkgender, talkvolume, talkrate, talkname; + + kdDebug() << "SSMLConvert::appropriateTalker: BEFORE LANGUAGE SEARCH: " << matches.join(" ") << endl;; + /** + * Language searching + */ + if(root.hasAttribute("xml:lang")) { + QString lang = root.attribute("xml:lang"); + kdDebug() << "SSMLConvert::appropriateTalker: xml:lang found (" << lang << ")" << endl; + /// If it is set to en*, then match all english speakers. They all sound the same anyways. + if(lang.contains("en-")) { + kdDebug() << "SSMLConvert::appropriateTalker: English" << endl; + lang = "en"; + } + /// Find all hits and place them in matches. We don't search for the closing " because if + /// the talker emits lang="en-UK" or something we'll be ignoring it, which we don't what. + matches = matches.grep("lang=\"" + lang); + } + else { + kdDebug() << "SSMLConvert::appropriateTalker: no xml:lang found. Defaulting to en.." << endl; + matches = matches.grep("lang=\"en"); + } + + kdDebug() << "SSMLConvert::appropriateTalker: AFTER LANGUAGE SEARCH: " << matches.join(" ") << endl;; + + /** + * Gender searching + * If, for example, male is specified and only female is found, + * ignore the choice and just use female. + */ + if(root.hasAttribute("gender")) { + QString gender = root.attribute("gender"); + kdDebug() << "SSMLConvert::appropriateTalker: gender found (" << gender << ")" << endl; + /// If the gender found is not 'male' or 'female' then ignore it. + if(!(gender == "male" || gender == "female")) { + /// Make sure that we don't strip away all the talkers because of no matches. + if(matches.grep("gender=\"" + gender).count() >= 1) + matches = matches.grep("gender=\"" + gender); + } + } + else { + kdDebug() << "SSMLConvert::appropriateTalker: no gender found." << endl; + } + + /** + * Prosody + * Search for talkers that allow modification of the synth output - louder, higher, + * slower, etc. There should be a direct way to query each synth to find out if this + * is supported (some function in PlugInConf), but for now, hardcode all the way :( + */ + /// Known to support (feel free to add to the list and if search): + /// Festival Int (not flite), Hadifix + if(matches.grep("synthesizer=\"Festival Interactive").count() >= 1 || + matches.grep("synthesizer=\"Hadifix").count() >= 1) { + + kdDebug() << "SSMLConvert::appropriateTalker: Prosody allowed" << endl; + QStringList tmpmatches = matches.grep("synthesizer=\"Festival Interactive"); + matches = matches.grep("synthesizer=\"Hadifix"); + matches = tmpmatches + matches; + } + else + kdDebug() << "SSMLConvert::appropriateTalker: No prosody-supporting talkers found" << endl; + + /// Return the first match that complies. Maybe a discrete way to + /// choose between all the matches could be offered in the future. Some form of preference. + return matches[0]; +} + +/** +* Applies the spreadsheet for a talker to the SSML and returns the talker-native output. +* @param text The markup to apply the spreadsheet to. +* @param xsltFilename The name of the stylesheet file that will be applied (i.e freetts, flite). +* @returns False if an error occurs. +* +* This converts a piece of SSML into a format the given talker can understand. It applies +* an XSLT spreadsheet to the SSML and returns the output. +* +* Emits transformFinished signal when completed. Caller then calls getOutput to retrieve +* the transformed text. +*/ + +bool SSMLConvert::transform(const QString &text, const QString &xsltFilename) { + m_xsltFilename = xsltFilename; + /// Write @param text to a temporary file. + KTempFile inFile(locateLocal("tmp", "kttsd-"), ".ssml"); + m_inFilename = inFile.file()->name(); + QTextStream* wstream = inFile.textStream(); + if (wstream == 0) { + /// wtf... + kdDebug() << "SSMLConvert::transform: Can't write to " << m_inFilename << endl;; + return false; + } + // TODO: Is encoding an issue here? + // TODO: It would be nice if we detected whether the XML is properly formed + // with the required xml processing instruction and encoding attribute. If + // not wrap it in such. But maybe this should be handled by SpeechData::setText()? + *wstream << text; + inFile.close(); +#if KDE_VERSION >= KDE_MAKE_VERSION (3,3,0) + inFile.sync(); +#endif + + // Get a temporary output file name. + KTempFile outFile(locateLocal("tmp", "kttsd-"), ".output"); + m_outFilename = outFile.file()->name(); + outFile.close(); + // outFile.unlink(); // only activate this if necessary. + + /// Spawn an xsltproc process to apply our stylesheet to our SSML file. + m_xsltProc = new KProcess; + *m_xsltProc << "xsltproc"; + *m_xsltProc << "-o" << m_outFilename << "--novalid" + << m_xsltFilename << m_inFilename; + // Warning: This won't compile under KDE 3.2. See FreeTTS::argsToStringList(). + // kdDebug() << "SSMLConvert::transform: executing command: " << + // m_xsltProc->args() << endl; + + connect(m_xsltProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + if (!m_xsltProc->start(KProcess::NotifyOnExit, KProcess::NoCommunication)) + { + kdDebug() << "SSMLConvert::transform: Error starting xsltproc" << endl; + return false; + } + m_state = tsTransforming; + return true; +} + +void SSMLConvert::slotProcessExited(KProcess* /*proc*/) +{ + m_xsltProc->deleteLater(); + m_xsltProc = 0; + m_state = tsFinished; + emit transformFinished(); +} + +/** +* Returns current processing state. +*/ +int SSMLConvert::getState() { return m_state; } + +/** +* Returns the output from call to transform. +*/ +QString SSMLConvert::getOutput() +{ + /// Read back the data that was written to /tmp/fileName.output. + QFile readfile(m_outFilename); + if(!readfile.open(IO_ReadOnly)) { + /// uhh yeah... Issues writing to the SSML file. + kdDebug() << "SSMLConvert::slotProcessExited: Could not read file " << m_outFilename << endl; + return QString::null; + } + QTextStream rstream(&readfile); + QString convertedData = rstream.read(); + readfile.close(); + + // kdDebug() << "SSMLConvert::slotProcessExited: Read SSML file at " + m_inFilename + " and created " + m_outFilename + " based on the stylesheet at " << m_xsltFilename << endl; + + // Clean up. + QFile::remove(m_inFilename); + m_inFilename = QString::null; + QFile::remove(m_outFilename); + m_outFilename = QString::null; + + // Ready for another transform. + m_state = tsIdle; + + return convertedData; +} + diff --git a/kttsd/kttsd/ssmlconvert.h b/kttsd/kttsd/ssmlconvert.h new file mode 100644 index 0000000..1ee332c --- /dev/null +++ b/kttsd/kttsd/ssmlconvert.h @@ -0,0 +1,129 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + SSMLConvert class + + This class is in charge of converting SSML text into a format that can + be handled by individual synths. + ------------------- + Copyright: + (C) 2004 by Paul Giannaros <ceruleanblaze@gmail.com> + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> +******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _SSMLCONVERT_H_ +#define _SSMLCONVERT_H_ + +/** + * SsmlConvert class: + * Receives a QStringList of talkers and, based on that information, + * evaluates received SSML to discover which of the given talkers best + * suits it. It can then convert the given SSML into a format understandable + * by the talker. + */ + +// Qt includes +#include <qobject.h> +#include <qstringlist.h> + +class KProcess; +class QString; + +class SSMLConvert : public QObject { + Q_OBJECT +public: + /** Constructors */ + SSMLConvert(); + SSMLConvert(const QStringList &talkers); + /** Destructor */ + virtual ~SSMLConvert(); + + enum TransformState { + tsIdle = 0, // Not doing anything. Ready to transform. + tsTransforming = 1, // Transforming. + tsFinished = 2 // Transforming finished. + }; + + /** + * Set the talker codes to be used. + * @param talkers talker codes to be used. + */ + void setTalkers(const QStringList &talkers); + + /** + * Extract the synth name from a talker code (i.e festival, flite, freetts). + * @param talkercode the talker code to extract the talker from. + * @returns the talker. + */ + QString extractTalker(const QString &talkercode); + + /** + * Returns the most appropriate talker for the text to synth's talker code. + * @param text the text that will be parsed. + * @returns the appropriate talker for the job as a talker code QString. + * + * The appropriate talker is the one that has the most features that are required in some + * SSML markup. In the future i'm hoping to make the importance of individual features + * configurable, but better to walk before you can run. + * Currently, the searching method in place is like a filter: Those that meet the criteria we're + * searchin for stay while others are sifted out. This should leave us with the right talker to use. + * It's not a very good method, but should be appropriate in most cases and should do just fine for now. + * + * See the implementation file for more detail. + */ + QString appropriateTalker(const QString &text) const; + + /** + * Applies the spreadsheet for a talker to the SSML and returns the talker-native output. + * @param text the markup to apply the spreadsheet to. + * @param xsltFilename the name of the stylesheet file that will be applied (i.e freetts, flite). + * @returns the output that the synth can understand. + * + * This converts a piece of SSML into a format the given talker can understand. It applies + * an XSLT spreadsheet to the SSML and returns the output. + */ + bool transform(const QString &text, const QString &xsltFilename); + + /** + * Returns current processing state. + */ + int getState(); + + /** + * Returns the output from call to transform. + */ + QString getOutput(); + +signals: + /** + * Emitted whenever tranforming is completed. + */ + void transformFinished(); + +private slots: + void slotProcessExited(KProcess* proc); + +private: + /// The XSLT processor. + KProcess *m_xsltProc; + /// Current talkers. + QStringList m_talkers; + // Current state. + int m_state; + // Name of XSLT file. + QString m_xsltFilename; + // Name of temporary input file. + QString m_inFilename; + // Name of temporary output file. + QString m_outFilename; +}; + +#endif // _SSMLCONVERT_H_ diff --git a/kttsd/kttsd/talkermgr.cpp b/kttsd/kttsd/talkermgr.cpp new file mode 100644 index 0000000..345f12b --- /dev/null +++ b/kttsd/kttsd/talkermgr.cpp @@ -0,0 +1,388 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Manages all the Talker (synth) plugins. + ------------------- + Copyright: + (C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. + +// KDE includes. +#include <kdebug.h> +#include <kparts/componentfactory.h> +#include <ktrader.h> +#include <kstandarddirs.h> + +// KTTS includes. +#include "pluginconf.h" +#include "talkermgr.h" +#include "threadedplugin.h" + +/** + * Constructor. + */ +TalkerMgr::TalkerMgr(QObject *parent, const char *name) : + QObject( parent, name ) +{ + m_loadedPlugIns.setAutoDelete(true); +} + +/** + * Destructor. + */ +TalkerMgr::~TalkerMgr() +{ + m_loadedPlugIns.clear(); +} + +/** + * Load all the configured synth plugins, populating loadedPlugIns structure. + */ +int TalkerMgr::loadPlugIns(KConfig* config) +{ + // kdDebug() << "Running: TalkerMgr::loadPlugIns()" << endl; + int good = 0; + int bad = 0; + + m_talkerToPlugInCache.clear(); + m_loadedPlugIns.clear(); + m_loadedTalkerCodes.clear(); + m_loadedTalkerIds.clear(); + + config->setGroup("General"); + QStringList talkerIDsList = config->readListEntry("TalkerIDs", ','); + if (!talkerIDsList.isEmpty()) + { + KLibFactory *factory; + QStringList::ConstIterator itEnd(talkerIDsList.constEnd()); + for( QStringList::ConstIterator it = talkerIDsList.constBegin(); it != itEnd; ++it ) + { + // kdDebug() << "Loading plugInProc for Talker ID " << *it << endl; + + // Talker ID. + QString talkerID = *it; + + // Set the group for the language we're loading + config->setGroup("Talker_" + talkerID); + + // Get the DesktopEntryName of the plugin we will try to load. + QString desktopEntryName = config->readEntry("DesktopEntryName", QString::null); + + // If a DesktopEntryName is not in the config file, it was configured before + // we started using them, when we stored translated plugin names instead. + // Try to convert the translated plugin name to a DesktopEntryName. + // DesktopEntryNames are better because user can change their desktop language + // and DesktopEntryName won't change. + if (desktopEntryName.isEmpty()) + { + QString synthName = config->readEntry("PlugIn", QString::null); + // See if the translated name will untranslate. If not, well, sorry. + desktopEntryName = TalkerCode::TalkerNameToDesktopEntryName(synthName); + // Record the DesktopEntryName from now on. + if (!desktopEntryName.isEmpty()) config->writeEntry("DesktopEntryName", desktopEntryName); + } + + // Get the talker code. + QString talkerCode = config->readEntry("TalkerCode", QString::null); + + // Normalize the talker code. + QString fullLanguageCode; + talkerCode = TalkerCode::normalizeTalkerCode(talkerCode, fullLanguageCode); + + // Find the KTTSD SynthPlugin. + KTrader::OfferList offers = KTrader::self()->query( + "KTTSD/SynthPlugin", QString("DesktopEntryName == '%1'").arg(desktopEntryName)); + + if(offers.count() > 1){ + ++bad; + kdDebug() << "More than 1 plug in doesn't make any sense, well, let's use any" << endl; + } else if(offers.count() < 1){ + ++bad; + kdDebug() << "Less than 1 plug in, nothing can be done" << endl; + } else { + kdDebug() << "Loading " << offers[0]->library() << endl; + factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if(factory){ + PlugInProc *speech = + KParts::ComponentFactory::createInstanceFromLibrary<PlugInProc>( + offers[0]->library().latin1(), this, offers[0]->library().latin1()); + if(!speech){ + kdDebug() << "Couldn't create the speech object from " << offers[0]->library() << endl; + ++bad; + } else { + if (speech->supportsAsync()) + { + speech->init(config, "Talker_" + talkerID); + // kdDebug() << "Plug in " << desktopEntryName << " created successfully." << endl; + m_loadedPlugIns.append(speech); + } else { + // Synchronous plugins are run in a separate thread. + // Init will start the thread and it will immediately go to sleep. + QString threadedPlugInName = QString::fromLatin1("threaded") + desktopEntryName; + ThreadedPlugIn* speechThread = new ThreadedPlugIn(speech, + this, threadedPlugInName.latin1()); + speechThread->init(config, "Talker_" + talkerCode); + // kdDebug() << "Threaded Plug in " << desktopEntryName << " for language " << (*it).right((*it).length()-5) << " created succesfully." << endl; + m_loadedPlugIns.append(speechThread); + } + ++good; + m_loadedTalkerCodes.append(TalkerCode(talkerCode)); + m_loadedTalkerIds.append(talkerID); + } + } else { + kdDebug() << "Couldn't create the factory object from " << offers[0]->library() << endl; + ++bad; + } + } + } + } + if(bad > 0){ + if(good == 0){ + // No plugin could be loaded. + return -1; + } else { + // At least one plugin was loaded and one failed. + return 0; + } + } else { + if (good == 0) + // No plugin could be loaded. + return -1; + else + // All the plug in were loaded perfectly + return 1; + } +} + +/** + * Get a list of the talkers configured in KTTS. + * @return A QStringList of fully-specified talker codes, one + * for each talker user has configured. + */ +QStringList TalkerMgr::getTalkers() +{ + QStringList talkerList; + for (int ndx = 0; ndx < int(m_loadedPlugIns.count()); ++ndx) + { + talkerList.append(m_loadedTalkerCodes[ndx].getTalkerCode()); + } + return talkerList; +} + +/** + * Returns a list of all the loaded plugins. + */ +QPtrList<PlugInProc> TalkerMgr::getLoadedPlugIns() +{ + return m_loadedPlugIns; +} + +/** + * Given a talker code, returns pointer to the closest matching plugin. + * @param talker The talker (language) code. + * @return Index to m_loadedPlugins array of Talkers. + * + * If a plugin has not been loaded to match the talker, returns the default + * plugin. + */ +int TalkerMgr::talkerToPluginIndex(const QString& talker) const +{ + // kdDebug() << "TalkerMgr::talkerToPluginIndex: matching talker " << talker << " to closest matching plugin." << endl; + // If we have a cached match, return that. + if (m_talkerToPlugInCache.contains(talker)) + return m_talkerToPlugInCache[talker]; + else + { + int winner = TalkerCode::findClosestMatchingTalker(m_loadedTalkerCodes, talker, true); + m_talkerToPlugInCache[talker] = winner; + return winner; + } +} + +/** + * Given a talker code, returns pointer to the closest matching plugin. + * @param talker The talker (language) code. + * @return Pointer to closest matching plugin. + * + * If a plugin has not been loaded to match the talker, returns the default + * plugin. + * + * TODO: When picking a talker, %KTTSD will automatically determine if text contains + * markup and pick a talker that supports that markup, if available. This + * overrides all other attributes, i.e, it is treated as an automatic "top priority" + * attribute. + */ +PlugInProc* TalkerMgr::talkerToPlugin(const QString& talker) const +{ + int talkerNdx = talkerToPluginIndex(talker); + return const_cast< QPtrList<PlugInProc>* >(&m_loadedPlugIns)->at(talkerNdx); +} + +/** + * Given a talker code, returns the parsed TalkerCode of the closest matching Talker. + * @param talker The talker (language) code. + * @return Parsed TalkerCode structure. + * + * If a plugin has not been loaded to match the talker, returns the default + * plugin. + * + * The returned TalkerCode is a copy and should be destroyed by caller. + * + * TODO: When picking a talker, %KTTSD will automatically determine if text contains + * markup and pick a talker that supports that markup, if available. This + * overrides all other attributes, i.e, it is treated as an automatic "top priority" + * attribute. + */ +TalkerCode* TalkerMgr::talkerToTalkerCode(const QString& talker) +{ + int talkerNdx = talkerToPluginIndex(talker); + return new TalkerCode(&m_loadedTalkerCodes[talkerNdx]); +} + +/** + * Given a Talker Code, returns the Talker ID of the talker that would speak + * a text job with that Talker Code. + * @param talkerCode Talker Code. + * @return Talker ID of the talker that would speak the text job. + */ +QString TalkerMgr::talkerCodeToTalkerId(const QString& talkerCode) +{ + int talkerNdx = talkerToPluginIndex(talkerCode); + return m_loadedTalkerIds[talkerNdx]; +} + +/** + * Get the user's default talker. + * @return A fully-specified talker code. + * + * @see talkers + * @see getTalkers + */ +QString TalkerMgr::userDefaultTalker() const +{ + return m_loadedTalkerCodes[0].getTalkerCode(); +} + +/** + * Determine whether the currently-configured speech plugin supports a speech markup language. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * @param markupType The kttsd code for the desired speech markup language. + * @return True if the plugin currently configured for the indicated + * talker supports the indicated speech markup language. + * @see kttsdMarkupType + */ +bool TalkerMgr::supportsMarkup(const QString& talker, const uint /*markupType*/) const +{ + kdDebug() << "TalkerMgr::supportsMarkup: Testing talker " << talker << endl; + QString matchingTalker = talker; + if (matchingTalker.isEmpty()) matchingTalker = userDefaultTalker(); + PlugInProc* plugin = talkerToPlugin(matchingTalker); + return ( plugin->getSsmlXsltFilename() != + KGlobal::dirs()->resourceDirs("data").last() + "kttsd/xslt/SSMLtoPlainText.xsl"); +} + +bool TalkerMgr::autoconfigureTalker(const QString& langCode, KConfig* config) +{ + // Not yet implemented. + // return false; + + QString languageCode = langCode; + + // Get last TalkerID from config. + QStringList talkerIDsList = config->readListEntry("TalkerIDs", ','); + int lastTalkerID = 0; + for (uint talkerIdNdx = 0; talkerIdNdx < talkerIDsList.count(); ++talkerIdNdx) + { + int id = talkerIDsList[talkerIdNdx].toInt(); + if (id > lastTalkerID) lastTalkerID = id; + } + + // Assign a new Talker ID for the talker. Wraps around to 1. + QString talkerID = QString::number(lastTalkerID + 1); + + // Query for all the KTTSD SynthPlugins. + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin"); + + // Iterate thru the possible plug ins. + for(unsigned int i=0; i < offers.count() ; ++i) + { + // See if this plugin supports the desired language. + QStringList languageCodes = offers[i]->property("X-KDE-Languages").toStringList(); + if (languageCodes.contains(languageCode)) + { + QString desktopEntryName = offers[i]->desktopEntryName(); + + // Load the plugin. + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if (factory) + { + // If the factory is created successfully, instantiate the PlugInConf class for the + // specific plug in to get the plug in configuration object. + PlugInConf* loadedTalkerPlugIn = + KParts::ComponentFactory::createInstanceFromLibrary<PlugInConf>( + offers[0]->library().latin1(), NULL, offers[0]->library().latin1()); + if (loadedTalkerPlugIn) + { + // Give plugin the language code and permit plugin to autoconfigure itself. + loadedTalkerPlugIn->setDesiredLanguage(languageCode); + loadedTalkerPlugIn->load(config, QString("Talker_")+talkerID); + + // If plugin was able to configure itself, it returns a full talker code. + QString talkerCode = loadedTalkerPlugIn->getTalkerCode(); + + if (!talkerCode.isEmpty()) + { + // Erase extraneous Talker configuration entries that might be there. + config->deleteGroup(QString("Talker_")+talkerID); + + // Let plugin save its configuration. + config->setGroup(QString("Talker_")+talkerID); + loadedTalkerPlugIn->save(config, QString("Talker_"+talkerID)); + + // Record configuration data. + config->setGroup(QString("Talker_")+talkerID); + config->writeEntry("DesktopEntryName", desktopEntryName); + talkerCode = TalkerCode::normalizeTalkerCode(talkerCode, languageCode); + config->writeEntry("TalkerCode", talkerCode); + + // Add TalkerID to configured list. + talkerIDsList.append(talkerID); + config->setGroup("General"); + config->writeEntry("TalkerIDs", talkerIDsList.join(",")); + config->sync(); + + // TODO: Now that we have modified the config, need a way to inform + // other apps, including KTTSMgr. As this routine is likely called + // when KTTSMgr is not running, is not a serious problem. + + // Success! + delete loadedTalkerPlugIn; + return true; + } + + // Plugin no longer needed. + delete loadedTalkerPlugIn; + } + } + } + } + + return false; +} diff --git a/kttsd/kttsd/talkermgr.h b/kttsd/kttsd/talkermgr.h new file mode 100644 index 0000000..5b890ae --- /dev/null +++ b/kttsd/kttsd/talkermgr.h @@ -0,0 +1,159 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Manages all the Talker (synth) plugins. + ------------------- + Copyright: + (C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _TALKERMGR_H_ +#define _TALKERMGR_H_ + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> +#include <qmap.h> +#include <qptrlist.h> + +// KTTS includes. +#include "talkercode.h" +#include "pluginproc.h" + +class TalkerMgr: public QObject +{ +public: + + /** + * Constructor. + */ + TalkerMgr(QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + */ + ~TalkerMgr(); + + /** + * Load all the configured plug ins populating loadedPlugIns + */ + int loadPlugIns(KConfig* config); + + /** + * Get a list of the talkers configured in KTTS. + * @return A QStringList of fully-specified talker codes, one + * for each talker user has configured. + */ + QStringList getTalkers(); + + /** + * Returns a list of all the loaded plugins. + */ + QPtrList<PlugInProc> getLoadedPlugIns(); + + /** + * Given a talker code, returns pointer to the closest matching plugin. + * @param talker The talker (language) code. + * @return Index to m_loadedPlugins array of Talkers. + * + * If a plugin has not been loaded to match the talker, returns the default + * plugin. + */ + int talkerToPluginIndex(const QString& talker) const; + + /** + * Given a talker code, returns pointer to the closest matching plugin. + * @param talker The talker (language) code. + * @return Pointer to closest matching plugin. + * + * If a plugin has not been loaded to match the talker, returns the default + * plugin. + */ + PlugInProc* talkerToPlugin(const QString& talker) const; + + /** + * Given a talker code, returns the parsed TalkerCode of the closest matching Talker. + * @param talker The talker (language) code. + * @return Parsed TalkerCode structure. + * + * If a plugin has not been loaded to match the talker, returns the default + * plugin. + * + * The returned TalkerCode is a copy and should be destroyed by caller. + * + * TODO: When picking a talker, %KTTSD will automatically determine if text contains + * markup and pick a talker that supports that markup, if available. This + * overrides all other attributes, i.e, it is treated as an automatic "top priority" + * attribute. + */ + TalkerCode* talkerToTalkerCode(const QString& talker); + + /** + * Given a Talker Code, returns the Talker ID of the talker that would speak + * a text job with that Talker Code. + * @param talkerCode Talker Code. + * @return Talker ID of the talker that would speak the text job. + */ + QString talkerCodeToTalkerId(const QString& talkerCode); + + /** + * Get the user's default talker. + * @return A fully-specified talker code. + * + * @see talkers + * @see getTalkers + */ + QString userDefaultTalker() const; + + /** + * Determine whether the currently-configured speech plugin supports a speech markup language. + * @param talker Code for the talker to do the speaking. Example "en". + * If NULL, defaults to the user's default talker. + * @param markupType The kttsd code for the desired speech markup language. + * @return True if the plugin currently configured for the indicated + * talker supports the indicated speech markup language. + * @see kttsdMarkupType + */ + bool supportsMarkup(const QString& talker, const uint markupType) const; + + /** + * Try to automatically configure a Talker in the specified language. + * @param langCode Two-letter language code. + * @param config KConfig to be updated if successful. + * @return True if successful. + * + * If successful, the KConfig rc file is updated but the talker has not been loaded. + */ + bool autoconfigureTalker(const QString& langCode, KConfig* config); + +private: + + /** + * Array of the loaded plug ins for different Talkers. + * Array of parsed Talker Codes for the plugins. + */ + QPtrList<PlugInProc> m_loadedPlugIns; + QStringList m_loadedTalkerIds; + TalkerCode::TalkerCodeList m_loadedTalkerCodes; + + /** + * Cache of talker codes and index of closest matching Talker. + */ + mutable QMap<QString,int> m_talkerToPlugInCache; +}; + +#endif // _TALKERMGR_H_ diff --git a/kttsd/kttsd/threadedplugin.cpp b/kttsd/kttsd/threadedplugin.cpp new file mode 100644 index 0000000..babc792 --- /dev/null +++ b/kttsd/kttsd/threadedplugin.cpp @@ -0,0 +1,282 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Converts a synchronous plugin into an asynchronous one. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <qevent.h> +#include <qapplication.h> + +#include <kdebug.h> + +#include "speaker.h" +#include "threadedplugin.h" + +/** +* Constructor. +*/ +ThreadedPlugIn::ThreadedPlugIn(PlugInProc* plugin, + QObject *parent /*= 0*/, const char *name /*= 0*/): + PlugInProc(parent, name), + QThread(), + m_plugin(plugin), + m_filename(QString::null), + m_requestExit(false), + m_supportsSynth(false) +{ + m_waitingStop = false; + m_state = psIdle; +} + +/** +* Destructor. +*/ +ThreadedPlugIn::~ThreadedPlugIn() +{ + if (running()) + { + // If thread is busy, try to stop it. + m_requestExit = true; + if (m_threadRunningMutex.locked()) m_plugin->stopText(); + m_action = paNone; + m_waitCondition.wakeOne(); + wait(5000); + // If thread still active, stopText didn't succeed. Terminate the thread. + if (running()) + { + terminate(); + wait(); + } + } + delete m_plugin; +} + +/** +* Initialize the speech plugin. +*/ +bool ThreadedPlugIn::init(KConfig *config, const QString &configGroup) +{ + bool stat = m_plugin->init(config, configGroup); + m_supportsSynth = m_plugin->supportsSynth(); + // Start thread running, which will immediately go to sleep. + start(); + return stat; +} + +/** +* 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. +*/ +void ThreadedPlugIn::sayText(const QString &text) +{ + kdDebug() << "ThreadedPlugin::sayText running with text " << text << endl; + waitThreadNotBusy(); + m_action = paSayText; + m_text = text; + m_state = psSaying; + m_waitCondition.wakeOne(); +} + +/** +* 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 synthFinished signal when synthesis is completed. +*/ +void ThreadedPlugIn::synthText(const QString &text, const QString &suggestedFilename) +{ + waitThreadNotBusy(); + m_action = paSynthText; + m_text = text; + m_filename = suggestedFilename; + m_state = psSynthing; + m_waitCondition.wakeOne(); +} + +/** +* 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 ThreadedPlugIn::getFilename() +{ + return m_filename; +} + +/** +* Stop current operation (saying or synthesizing text). +* This function only makes sense in asynchronus modes. +* The plugin should return to the psIdle state. +*/ +void ThreadedPlugIn::stopText() +{ + // If thread is busy, call stopText and wait for thread to stop being busy. + if (m_threadRunningMutex.locked()) + { + kdDebug() << "ThreadedPlugIn::stopText:: calling m_plugin->stopText" << endl; + m_plugin->stopText(); + // Set flag that will force state to idle once the plugin finishes. + m_waitingStop = true; +// waitThreadNotBusy(); + } else m_state = psIdle; +} + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @ref pluginState +*/ +pluginState ThreadedPlugIn::getState() +{ + m_stateMutex.unlock(); + bool emitStopped = false; + // If stopText was called, plugin may not have truly stopped, in which + // case, if has finally completed the operation, return idle state. + if (m_waitingStop) + { + if (m_state == psFinished) m_state = psIdle; + if (m_state == psIdle) + { + m_waitingStop = false; + emitStopped = true; + } + } + pluginState plugState = m_state; + m_stateMutex.unlock(); + if (emitStopped) emit stopped(); + return plugState; +} + +/** +* Acknowleges 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 ThreadedPlugIn::ackFinished() +{ + // Since plugin should not be running, don't bother with Mutex here. + if (m_state == psFinished) m_state = psIdle; + m_filename = 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. +* +* Since this is a threaded wrapper, return True. +*/ +bool ThreadedPlugIn::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 ThreadedPlugIn::supportsSynth() { return m_supportsSynth; } + +/** +* Waits for the thread to go to sleep. +*/ +void ThreadedPlugIn::waitThreadNotBusy() +{ + m_threadRunningMutex.lock(); + m_threadRunningMutex.unlock(); +} + +/** +* Base function, where the thread will start. +*/ +void ThreadedPlugIn::run() +{ + while (!m_requestExit) + { + + if (!m_threadRunningMutex.locked()) m_threadRunningMutex.lock(); + // Go to sleep until asked to do something. + // Mutex unlocks as we go to sleep and locks as we wake up. + kdDebug() << "ThreadedPlugIn::run going to sleep." << endl; + m_waitCondition.wait(&m_threadRunningMutex); + kdDebug() << "ThreadedPlugIn::run waking up." << endl; + // Woken up. + // See if we've been told to exit. + if (m_requestExit) + { + m_threadRunningMutex.unlock(); + return; + } + + // Branch on requested action. + switch( m_action ) + { + case paNone: break; + + case paSayText: + { + m_stateMutex.lock(); + m_state = psSaying; + m_stateMutex.unlock(); + kdDebug() << "ThreadedPlugIn::run calling sayText" << endl; + m_plugin->sayText(m_text); + kdDebug() << "ThreadedPlugIn::run back from sayText" << endl; + m_stateMutex.lock(); + if (m_state == psSaying) m_state = psFinished; + m_stateMutex.unlock(); + emit sayFinished(); + break; + } + + case paSynthText: + { + m_stateMutex.lock(); + m_state = psSynthing; + m_stateMutex.unlock(); + QString filename = m_filename; + m_filename = QString::null; + kdDebug() << "ThreadedPlugIn::run calling synthText" << endl; + m_plugin->synthText(m_text, filename); + kdDebug() << "ThreadedPlugIn::run back from synthText" << endl; + m_filename = m_plugin->getFilename(); + m_stateMutex.lock(); + if (m_state == psSynthing) m_state = psFinished; + m_stateMutex.unlock(); + emit synthFinished(); + break; + } + } + } + if (m_threadRunningMutex.locked()) m_threadRunningMutex.unlock(); +} diff --git a/kttsd/kttsd/threadedplugin.h b/kttsd/kttsd/threadedplugin.h new file mode 100644 index 0000000..bc9ed6a --- /dev/null +++ b/kttsd/kttsd/threadedplugin.h @@ -0,0 +1,200 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Converts a synchronous plugin into an asynchronous one. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _THREADEDPLUGIN_H_ +#define _THREADEDPLUGIN_H_ + +#include <qthread.h> +#include <qmutex.h> +#include <qwaitcondition.h> + +#include "pluginproc.h" + +class Speaker; + +class ThreadedPlugIn : public PlugInProc, public QThread +{ + public: + enum pluginAction + { + paNone = 0, + paSayText = 1, + paSynthText = 2 + }; + + /** + * Constructor. + */ + ThreadedPlugIn(PlugInProc* plugin, QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + */ + virtual ~ThreadedPlugIn(); + + /** + * Initializate the speech plugin. + */ + 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. + */ + 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 synthFinished signal when synthesis is completed. + */ + 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). + * This function only makes sense in asynchronus modes. + * The plugin should return to the psIdle state. + */ + virtual void stopText(); + + /** + * Return the current state of the plugin. + * This function only makes sense in asynchronous mode. + * @return The pluginState of the plugin. + * + * @ref pluginState + */ + virtual pluginState getState(); + + /** + * Acknowleges 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. + */ + 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(); + + protected: + /** + * Base function, where the thread will start. + */ + virtual void run(); + + private: + /** + * Waits for the thread to go to sleep. + */ + void waitThreadNotBusy(); + + /** + * The plugin we wrap. + */ + PlugInProc* m_plugin; + + /** + * An action requested of the plugin. + */ + pluginAction m_action; + + /** + * A text buffer to go with an action (if applicable). + */ + QString m_text; + + /** + * Current state of the plugin. + */ + volatile pluginState m_state; + + /** + * Mutext for accessing state variable. + */ + QMutex m_stateMutex; + + /** + * True when stopText was called but the plugin did not stop. + */ + bool m_waitingStop; + + /** + * Locked when thread is running. + */ + QMutex m_threadRunningMutex; + + /** + * Filename for generated synthesized text. + */ + QString m_filename; + + /** + * Thread wait condition. + */ + QWaitCondition m_waitCondition; + + /** + * Thread exit flag. + */ + volatile bool m_requestExit; + + /** + * Whether wrapped plugin supports separate synthesis. + */ + bool m_supportsSynth; +}; + +#endif // _THREADEDPLUGIN_H_ diff --git a/kttsd/kttsjobmgr/Makefile.am b/kttsd/kttsjobmgr/Makefile.am new file mode 100644 index 0000000..98727e0 --- /dev/null +++ b/kttsd/kttsjobmgr/Makefile.am @@ -0,0 +1,40 @@ +# Include paths. INCLUDES is maintained by KDevelop, AM_CPPFLAGS is the preferred variable, +# so keep them synchronized. +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(KTTS_KSPEECH_INCLUDE) \ + $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# KPART SECTION +######################################################################### +# This is the kpart that gets installed. It's name is used for all +# of the other Makefile.am variables. +kde_module_LTLIBRARIES = libkttsjobmgrpart.la + +kspeech_DIR = $(KTTS_KSPEECH_DIR) +kspeechsink_DIR = $(KTTS_KSPEECH_DIR) + +# The source, library search path, and link libraries. +libkttsjobmgrpart_la_SOURCES = \ + kspeech.stub kspeechsink.skel \ + kttsjobmgr.cpp + +libkttsjobmgrpart_la_LDFLAGS = -avoid-version -module -no-undefined $(all_libraries) $(KDE_PLUGIN) +libkttsjobmgrpart_la_LIBADD = \ + $(top_builddir)/kttsd/libkttsd/libkttsd.la \ + $(LIB_KFILE) $(LIBVM) $(LIB_KPARTS) + +# Header files not to be installed. +noinst_HEADERS = + +# Install desktop file to standard services directory. +kde_services_DATA = kttsjobmgr.desktop + +# This rc file should never have been installed. +# createGUI not supported within a KCModule. +install-data-local: + rm -f $(DESTDIR)$(kde_datadir)/kttsjobmgr/kttsjobmgrui.rc diff --git a/kttsd/kttsjobmgr/kttsjobmgr.cpp b/kttsd/kttsjobmgr/kttsjobmgr.cpp new file mode 100644 index 0000000..2b2e1c5 --- /dev/null +++ b/kttsd/kttsjobmgr/kttsjobmgr.cpp @@ -0,0 +1,1023 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + A KPart to display running jobs in KTTSD and permit user to stop, rewind, + advance, change Talker, etc. + ------------------- + Copyright : (C) 2004,2005 by 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; version 2 of the License. * + * * + ***************************************************************************/ + +// QT includes. +#include <qvbox.h> +#include <qhbox.h> +#include <qlabel.h> +#include <qsplitter.h> +#include <qclipboard.h> +#include <qpushbutton.h> +#include <qobjectlist.h> +#include <qwhatsthis.h> + +#include <qmime.h> + +// KDE includes. +#include <kinstance.h> +#include <klocale.h> +#include <kaboutdata.h> +#include <klistview.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <kencodingfiledialog.h> +#include <kapplication.h> +#include <kinputdialog.h> +#include <ktextedit.h> + +// KTTS includes. +#include "kspeech.h" +#include "talkercode.h" +#include "selecttalkerdlg.h" +#include "kttsjobmgr.h" +#include "kttsjobmgr.moc" + +K_EXPORT_COMPONENT_FACTORY( libkttsjobmgrpart, KttsJobMgrFactory ) + +/** +* We need one static instance of the factory for our C 'main' +* function +*/ +KInstance *KttsJobMgrFactory::s_instance = 0L; + +KttsJobMgrFactory::~KttsJobMgrFactory() +{ + if (s_instance) + { + delete s_instance->aboutData(); + delete s_instance; + } + + s_instance = 0; +} + +QObject *KttsJobMgrFactory::createObject(QObject *parent, const char *name, const char*, + const QStringList& ) +{ + QObject *obj = new KttsJobMgrPart((QWidget*)parent, name); + emit objectCreated(obj); + return obj; +} + +KInstance *KttsJobMgrFactory::instance() +{ + if ( !s_instance ) + s_instance = new KInstance( aboutData() ); + return s_instance; +} + +KAboutData *KttsJobMgrFactory::aboutData() +{ + KAboutData *about = new KAboutData("kttsjobmgr", I18N_NOOP("KttsJobMgr"), "1.99"); + return about; +} + +KttsJobMgrPart::KttsJobMgrPart(QWidget *parent, const char *name) : + DCOPStub("kttsd", "KSpeech"), + DCOPObject("kttsjobmgr_kspeechsink"), + KParts::ReadOnlyPart(parent, name) +{ + // Initialize some variables. + m_selectOnTextSet = false; + m_buttonBox = 0; + + setInstance(KttsJobMgrFactory::instance()); + + // All the ktts components use the same catalogue. + KGlobal::locale()->insertCatalogue("kttsd"); + + // Create a QVBox to host everything. + QVBox* vBox = new QVBox(parent); + vBox->setMargin(6); + + // Create a splitter to contain the Job List View and the current sentence. + QSplitter* splitter = new QSplitter(vBox); + splitter->setOrientation(QSplitter::Vertical); + + // Create Job List View widget. + m_jobListView = new KListView(splitter, "joblistview"); + m_jobListView->setSelectionModeExt(KListView::Single); + m_jobListView->addColumn(i18n("Job Num")); + m_jobListView->addColumn(i18n("Owner")); + m_jobListView->addColumn(i18n("Talker ID")); + m_jobListView->addColumn(i18n("State")); + m_jobListView->addColumn(i18n("Position")); + m_jobListView->addColumn(i18n("Sentences")); + m_jobListView->addColumn(i18n("Part Num")); + m_jobListView->addColumn(i18n("Parts")); + + // Do not sort the list. + m_jobListView->setSorting(-1); + + QString jobListViewWT = i18n( + "<p>These are all the text jobs. The <b>State</b> column " + "may be:" + "<ul>" + "<li><b>Queued</b> - the job is waiting and will not be spoken until its state " + "is changed to <b>Waiting</b> by clicking the <b>Resume</b> or <b>Restart</b> buttons.</li>" + "<li><b>Waiting</b> - the job is ready to be spoken. It will be spoken when the jobs " + "preceding it in the list have finished.</li>" + "<li><b>Speaking</b> - the job is speaking. The <b>Position</b> column shows the current " + "sentence of the job being spoken. You may pause a speaking job by clicking the " + "<b>Hold</b> button.</li>" + "<li><b>Paused</b> - the job is currently paused. Paused jobs prevent jobs below them " + "from speaking. Use the <b>Resume</b> or <b>Restart</b> buttons to resume speaking the " + "job, or click <b>Later</b> to move the job down in the list.</li>" + "<li><b>Finished</b> - the job has finished speaking. When a second job finishes, " + "this one will be deleted. You may click <b>Restart</b> to repeat the job.</li>" + "</ul>" + "<em>Note</em>: Messages, Warnings, and Screen Reader Output do not appear in this list. " + "See the Handbook for more information." + "</p>"); + QWhatsThis::add(m_jobListView, jobListViewWT); + + // splitter->setResizeMode(m_jobListView, QSplitter::Stretch); + + // Create a VBox to hold buttons and current sentence. + QVBox* bottomBox = new QVBox(splitter); + + // Create a box to hold buttons. + m_buttonBox = new QVBox(bottomBox); + m_buttonBox->setSpacing(6); + + // Create 3 HBoxes to host buttons. + QHBox* hbox1 = new QHBox(m_buttonBox); + hbox1->setSpacing(6); + QHBox* hbox2 = new QHBox(m_buttonBox); + hbox2->setSpacing(6); + QHBox* hbox3 = new QHBox(m_buttonBox); + hbox3->setSpacing(6); + + // Do not let button box stretch vertically. + m_buttonBox->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + // All the buttons with "job_" at start of their names will be enabled/disabled when a job is + // selected in the Job List View. + // All the buttons with "part_" at the start of their names will be enabled/disabled when a + // job is selected in the Job List View that has multiple parts. + + QPushButton* btn; + QString wt; + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("stop", KIcon::Small, 0, true), + i18n("Hold"), hbox1, "job_hold"); + wt = i18n( + "<p>Changes a job to Paused state. If currently speaking, the job stops speaking. " + "Paused jobs prevent jobs that follow them from speaking, so either click " + "<b>Resume</b> to make the job speakable, or click <b>Later</b> to move it " + "down in the list.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_hold())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("exec", KIcon::Small, 0, true), + i18n("Resume"), hbox1, "job_resume"); + wt = i18n( + "<p>Resumes a paused job or changes a Queued job to Waiting. If the job is the " + "top speakable job in the list, it begins speaking.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_resume())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("redo", KIcon::Small, 0, true), + i18n("R&estart"), hbox1, "job_restart"); + wt = i18n( + "<p>Rewinds a job to the beginning and changes its state to Waiting. If the job " + "is the top speakable job in the list, it begins speaking.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_restart())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("edittrash", KIcon::Small, 0, true), + i18n("Re&move"), hbox1, "job_remove"); + wt = i18n( + "<p>Deletes the job. If it is currently speaking, it stops speaking. The next " + "speakable job in the list begins speaking.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_remove())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("down", KIcon::Small, 0, true), + i18n("&Later"), hbox1, "job_later"); + wt = i18n( + "<p>Moves a job downward in the list so that it will be spoken later. If the job " + "is currently speaking, its state changes to Paused.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_move())); + + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("2leftarrow", KIcon::Small, 0, true), + i18n("Pre&vious Part"), hbox2, "part_prevpart"); + wt = i18n( + "<p>Rewinds a multi-part job to the previous part.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_prev_par())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("1leftarrow", KIcon::Small, 0, true), + i18n("&Previous Sentence"), hbox2, "job_prevsentence"); + wt = i18n( + "<p>Rewinds a job to the previous sentence.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_prev_sen())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("1rightarrow", KIcon::Small, 0, true), + i18n("&Next Sentence"), hbox2, "job_nextsentence"); + wt = i18n( + "<p>Advances a job to the next sentence.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_next_sen())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("2rightarrow", KIcon::Small, 0, true), + i18n("Ne&xt Part"), hbox2, "part_nextpart"); + wt = i18n( + "<p>Advances a multi-part job to the next part.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_next_par())); + + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("klipper", KIcon::Small, 0, true), + i18n("&Speak Clipboard"), hbox3, "speak_clipboard"); + wt = i18n( + "<p>Queues the current contents of the clipboard for speaking and sets its state " + "to Waiting. If the job is the topmost in the list, it begins speaking. " + "The job will be spoken by the topmost Talker in the <b>Talkers</b> tab.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_speak_clipboard())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("fileopen", KIcon::Small, 0, true), + i18n("Spea&k File"), hbox3, "speak_file"); + wt = i18n( + "<p>Prompts you for a file name and queues the contents of the file for speaking. " + "You must click the <b>Resume</b> button before the job will be speakable. " + "The job will be spoken by the topmost Talker in the <b>Talkers</b> tab.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_speak_file())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("translate", KIcon::Small, 0, true), + i18n("Change Talker"), hbox3, "job_changetalker"); + wt = i18n( + "<p>Prompts you with a list of your configured Talkers from the <b>Talkers</b> tab. " + "The job will be spoken using the selected Talker.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_job_change_talker())); + btn = new QPushButton(KGlobal::iconLoader()->loadIconSet("reload_page", KIcon::Small, 0, true), + i18n("&Refresh"), hbox3, "refresh"); + wt = i18n( + "<p>Refresh the list of jobs.</p>"); + QWhatsThis::add(btn, wt); + connect (btn, SIGNAL(clicked()), this, SLOT(slot_refresh())); + + // Disable job buttons until a job is selected. + enableJobActions(false); + enableJobPartActions(false); + + // Create a VBox for the current sentence and sentence label. + QVBox* sentenceVBox = new QVBox(bottomBox); + + // Create a label for current sentence. + QLabel* currentSentenceLabel = new QLabel(sentenceVBox); + currentSentenceLabel->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + currentSentenceLabel->setText(i18n("Current Sentence")); + + // Create a box to contain the current sentence. + m_currentSentence = new KTextEdit(sentenceVBox); + m_currentSentence->setReadOnly(true); + m_currentSentence->setWordWrap(QTextEdit::WidgetWidth); + m_currentSentence->setWrapPolicy(QTextEdit::AtWordOrDocumentBoundary); + m_currentSentence->setHScrollBarMode(QScrollView::AlwaysOff); + m_currentSentence->setVScrollBarMode(QScrollView::Auto); + wt = i18n( + "<p>The text of the sentence currently speaking.</p>"); + QWhatsThis::add(m_currentSentence, wt); + + // Set the main widget for the part. + setWidget(vBox); + + connect(m_jobListView, SIGNAL(selectionChanged(QListViewItem* )), + this, SLOT(slot_selectionChanged(QListViewItem* ))); + + // Fill the Job List View. + refreshJobListView(); + // Select first item (if any). + autoSelectInJobListView(); + + // Connect DCOP Signals emitted by KTTSD to our own DCOP methods. + connectDCOPSignal("kttsd", "KSpeech", + "kttsdStarted()", + "kttsdStarted()", + false); + connectDCOPSignal("kttsd", "KSpeech", + "markerSeen(QCString,QString)", + "markerSeen(QCString,QString)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "sentenceStarted(QCString,uint,uint)", + "sentenceStarted(QCString,uint,uint)", + false); + connectDCOPSignal(0, 0, + "sentenceFinished(QCString,uint,uint)", + "sentenceFinished(QCString,uint,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textSet(QCString,uint)", + "textSet(QCString,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textStarted(QCString,uint)", + "textStarted(QCString,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textFinished(QCString,uint)", + "textFinished(QCString,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textStopped(QCString,uint)", + "textStopped(QCString,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textPaused(QCString,uint)", + "textPaused(QCString,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textResumed(QCString,uint)", + "textResumed(QCString,uint)", + false); + connectDCOPSignal("kttsd", "KSpeech", + "textRemoved(QCString,uint)", + "textRemoved(QCString,uint)", + false); + + m_extension = new KttsJobMgrBrowserExtension(this); + + m_jobListView->show(); + + // Divide splitter in half. ListView gets half. Buttons and Current Sentence get half. + int halfSplitterSize = splitter->height()/2; + QValueList<int> splitterSizes; + splitterSizes.append(halfSplitterSize); + splitterSizes.append(halfSplitterSize); + splitter->setSizes(splitterSizes); +} + +KttsJobMgrPart::~KttsJobMgrPart() +{ + closeURL(); +} + +bool KttsJobMgrPart::openFile() +{ + return true; +} + +bool KttsJobMgrPart::closeURL() +{ + return true; +} + +/** +* This slot is connected to the Job List View selectionChanged signal. +*/ +void KttsJobMgrPart::slot_selectionChanged(QListViewItem*) +{ + // Enable job buttons. + enableJobActions(true); + enableJobPartActions((getCurrentJobPartCount() > 1)); +} + +/** +* Slots connected to buttons. +*/ +void KttsJobMgrPart::slot_job_hold() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + pauseText(jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_resume() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + resumeText(jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_restart() +{ + uint jobNum = getCurrentJobNum(); + // kdDebug() << "KttsJobMgrPart::slot_job_restart: jobNum = " << jobNum << endl; + if (jobNum) + { + startText(jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_prev_par() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + // Get current part number. + uint partNum = jumpToTextPart(0, jobNum); + if (partNum > 1) jumpToTextPart(--partNum, jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_prev_sen() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + moveRelTextSentence(-1, jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_next_sen() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + moveRelTextSentence(1, jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_next_par() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + // Get current part number. + uint partNum = jumpToTextPart(0, jobNum); + jumpToTextPart(++partNum, jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_job_remove() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + removeText(jobNum); + m_currentSentence->clear(); + } +} + +void KttsJobMgrPart::slot_job_move() +{ + uint jobNum = getCurrentJobNum(); + if (jobNum) + { + moveTextLater(jobNum); + refreshJobListView(); + // Select the job we just moved. + QListViewItem* item = findItemByJobNum(jobNum); + if (item) m_jobListView->setSelected(item, true); + } +} + +void KttsJobMgrPart::slot_job_change_talker() +{ + QListViewItem* item = m_jobListView->selectedItem(); + if (item) + { + QString talkerID = item->text(jlvcTalkerID); + QStringList talkerIDs = m_talkerCodesToTalkerIDs.values(); + int ndx = talkerIDs.findIndex(talkerID); + QString talkerCode; + if (ndx >= 0) talkerCode = m_talkerCodesToTalkerIDs.keys()[ndx]; + SelectTalkerDlg dlg(widget(), "selecttalkerdialog", i18n("Select Talker"), talkerCode, true); + int dlgResult = dlg.exec(); + if (dlgResult != KDialogBase::Accepted) return; + talkerCode = dlg.getSelectedTalkerCode(); + int jobNum = item->text(jlvcJobNum).toInt(); + changeTextTalker(talkerCode, jobNum); + refreshJob(jobNum); + } +} + +void KttsJobMgrPart::slot_speak_clipboard() +{ + // Get the clipboard object. + QClipboard *cb = kapp->clipboard(); + + + // Copy text from the clipboard. + QString text; + QMimeSource* data = cb->data(); + if (data) + { + if (data->provides("text/html")) + { + if (supportsMarkup(NULL, KSpeech::mtHtml)) + { + QByteArray d = data->encodedData("text/html"); + text = QString(d); + } + } + if (data->provides("text/ssml")) + { + if (supportsMarkup(NULL, KSpeech::mtSsml)) + { + QByteArray d = data->encodedData("text/ssml"); + text = QString(d); + } + } + } + if (text.isEmpty()) + text = cb->text(); + + // Speak it. + if ( !text.isEmpty() ) + { + uint jobNum = setText(text, NULL); + // kdDebug() << "KttsJobMgrPart::slot_speak_clipboard: started jobNum " << jobNum << endl; + startText(jobNum); + // Set flag so that the job we just created will be selected when textSet signal is received. + m_selectOnTextSet = true; + } +} + +void KttsJobMgrPart::slot_speak_file() +{ + KEncodingFileDialog dlg; + KEncodingFileDialog::Result result = dlg.getOpenFileNameAndEncoding(); + if (result.fileNames.count() == 1) + { + // kdDebug() << "KttsJobMgr::slot_speak_file: calling setFile with filename " << + // result.fileNames[0] << " and encoding " << result.encoding << endl; + setFile(result.fileNames[0], NULL, result.encoding); + } +} + +void KttsJobMgrPart::slot_refresh() +{ + // Clear TalkerID cache. + m_talkerCodesToTalkerIDs.clear(); + // Get current job number. + uint jobNum = getCurrentJobNum(); + refreshJobListView(); + // Select the previously-selected job. + if (jobNum) + { + QListViewItem* item = findItemByJobNum(jobNum); + if (item) m_jobListView->setSelected(item, true); + } +} + + +/** +* Convert a KTTSD job state integer into a display string. +* @param state KTTSD job state +* @return Display string for the state. +*/ +QString KttsJobMgrPart::stateToStr(int state) +{ + switch( state ) + { + case KSpeech::jsQueued: return i18n("Queued"); + case KSpeech::jsSpeakable: return i18n("Waiting"); + case KSpeech::jsSpeaking: return i18n("Speaking"); + case KSpeech::jsPaused: return i18n("Paused"); + case KSpeech::jsFinished: return i18n("Finished"); + default: return i18n("Unknown"); + } +} + +/** +* Get the Job Number of the currently-selected job in the Job List View. +* @return Job Number of currently-selected job. +* 0 if no currently-selected job. +*/ +uint KttsJobMgrPart::getCurrentJobNum() +{ + uint jobNum = 0; + QListViewItem* item = m_jobListView->selectedItem(); + if (item) + { + QString jobNumStr = item->text(jlvcJobNum); + jobNum = jobNumStr.toUInt(0, 10); + } + return jobNum; +} + +/** +* Get the number of parts in the currently-selected job in the Job List View. +* @return Number of parts in currently-selected job. +* 0 if no currently-selected job. +*/ +int KttsJobMgrPart::getCurrentJobPartCount() +{ + int partCount = 0; + QListViewItem* item = m_jobListView->selectedItem(); + if (item) + { + QString partCountStr = item->text(jlvcPartCount); + partCount = partCountStr.toUInt(0, 10); + } + return partCount; +} + +/** +* Given a Job Number, returns the Job List View item containing the job. +* @param jobNum Job Number. +* @return QListViewItem containing the job or 0 if not found. +*/ +QListViewItem* KttsJobMgrPart::findItemByJobNum(const uint jobNum) +{ + return m_jobListView->findItem(QString::number(jobNum), jlvcJobNum); +} + +/** +* Refresh display of a single job in the JobListView. +* @param jobNum Job Number. +*/ +void KttsJobMgrPart::refreshJob(uint jobNum) +{ + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talker; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talker; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + QString talkerID = cachedTalkerCodeToTalkerID(talker); + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcTalkerID, talkerID); + item->setText(jlvcState, stateToStr(state)); + item->setText(jlvcPosition, QString::number(seq)); + item->setText(jlvcSentences, QString::number(sentenceCount)); + item->setText(jlvcPartNum, QString::number(partNum)); + item->setText(jlvcPartCount, QString::number(partCount)); + } +} + +/** +* Fill the Job List View. +*/ +void KttsJobMgrPart::refreshJobListView() +{ + // kdDebug() << "KttsJobMgrPart::refreshJobListView: Running" << endl; + m_jobListView->clear(); + enableJobActions(false); + enableJobPartActions(false); + QString jobNumbers = getTextJobNumbers(); + // kdDebug() << "jobNumbers: " << jobNumbers << endl; + QStringList jobNums = QStringList::split(",", jobNumbers); + QListViewItem* lastItem = 0; + QStringList::ConstIterator endJobNums(jobNums.constEnd()); + for( QStringList::ConstIterator it = jobNums.constBegin(); it != endJobNums; ++it) + { + QString jobNumStr = *it; + // kdDebug() << "jobNumStr: " << jobNumStr << endl; + uint jobNum = jobNumStr.toUInt(0, 10); + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talkerCode; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talkerCode; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + QString talkerID = cachedTalkerCodeToTalkerID(talkerCode); + // Append to list. + if (lastItem) + lastItem = new QListViewItem(m_jobListView, lastItem, jobNumStr, appId, talkerID, + stateToStr(state), QString::number(seq), QString::number(sentenceCount), + QString::number(partNum), QString::number(partCount)); + else + lastItem = new QListViewItem(m_jobListView, jobNumStr, appId, talkerID, + stateToStr(state), QString::number(seq), QString::number(sentenceCount), + QString::number(partNum), QString::number(partCount)); + } +} + +/** +* If nothing selected in Job List View and list not empty, select top item. +* If nothing selected and list is empty, disable job buttons. +*/ +void KttsJobMgrPart::autoSelectInJobListView() +{ + // If something selected, nothing to do. + if (m_jobListView->selectedItem()) return; + // If empty, disable job buttons. + QListViewItem* item = m_jobListView->firstChild(); + if (!item) + { + enableJobActions(false); + enableJobPartActions(false); + } + else + // Select first item. Should fire itemSelected event which will enable job buttons. + m_jobListView->setSelected(item, true); +} + +/** +* Return the Talker ID corresponding to a Talker Code, retrieving from cached list if present. +* @param talkerCode Talker Code. +* @return Talker ID. +*/ +QString KttsJobMgrPart::cachedTalkerCodeToTalkerID(const QString& talkerCode) +{ + // If in the cache, return that. + if (m_talkerCodesToTalkerIDs.contains(talkerCode)) + return m_talkerCodesToTalkerIDs[talkerCode]; + else + { + // Otherwise, retrieve Talker ID from KTTSD and cache it. + QString talkerID = talkerCodeToTalkerId(talkerCode); + m_talkerCodesToTalkerIDs[talkerCode] = talkerID; + return talkerID; + } +} + +/** +* Enables or disables all the job-related buttons. +* @param enable True to enable the job-related butons. False to disable. +*/ +void KttsJobMgrPart::enableJobActions(bool enable) +{ + if (!m_buttonBox) return; + QObjectList *l = m_buttonBox->queryList( "QPushButton", "job_*", true, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + // for each found object... + ++it; + ((QPushButton*)obj)->setEnabled( enable ); + } + delete l; // delete the list, not the objects + + if (enable) + { + // Later button only enables if currently selected list item is not bottom of list. + QListViewItem* item = m_jobListView->selectedItem(); + if (item) + { + bool enableLater = item->nextSibling(); + + l = m_buttonBox->queryList( "QPushButton", "job_later", false, true ); + it = QObjectListIt( *l ); // iterate over the buttons + if ( (obj = it.current()) != 0 ) { + // for each found object... + ((QPushButton*)obj)->setEnabled( enableLater ); + } + delete l; // delete the list, not the objects + } + } +} + +/** +* Enables or disables all the job part-related buttons. +* @param enable True to enable the job par-related butons. False to disable. +*/ +void KttsJobMgrPart::enableJobPartActions(bool enable) +{ + if (!m_buttonBox) return; + QObjectList *l = m_buttonBox->queryList( "QPushButton", "part_*", true, true ); + QObjectListIt it( *l ); // iterate over the buttons + QObject *obj; + + while ( (obj = it.current()) != 0 ) { + // for each found object... + ++it; + ((QPushButton*)obj)->setEnabled( enable ); + } + delete l; // delete the list, not the objects +} + +/** DCOP Methods connected to DCOP Signals emitted by KTTSD. */ + +/** +* This signal is emitted when KTTSD starts or restarts after a call to reinit. +*/ +ASYNC KttsJobMgrPart::kttsdStarted() { slot_refresh(); } + +/** +* This signal is emitted when the speech engine/plugin encounters a marker in the text. +* @param appId DCOP application ID of the application that queued the text. +* @param markerName The name of the marker seen. +* @see markers +*/ +ASYNC KttsJobMgrPart::markerSeen(const QCString&, const QString&) +{ +} + +/** + * This signal is emitted whenever a sentence begins speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * @see getTextCount + */ +ASYNC KttsJobMgrPart::sentenceStarted(const QCString&, const uint jobNum, const uint seq) +{ + // kdDebug() << "KttsJobMgrPart::sentencedStarted: jobNum = " << jobNum << " seq = " << seq << endl; + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsSpeaking)); + item->setText(jlvcPosition, QString::number(seq)); + m_currentSentence->setText(getTextJobSentence(jobNum, seq)); + } +} + +/** +* This signal is emitted when a sentence has finished speaking. +* @param appId DCOP application ID of the application that queued the text. +* @param jobNum Job number of the text job. +* @param seq Sequence number of the text. +* @see getTextCount +*/ +ASYNC KttsJobMgrPart::sentenceFinished(const QCString& /*appId*/, const uint /*jobNum*/, const uint /*seq*/) +{ + // kdDebug() << "KttsJobMgrPart::sentencedFinished: jobNum = " << jobNum << endl; +/* + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsSpeaking)); + } +*/ +} + +/** +* This signal is emitted whenever a new text job is added to the queue. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textSet(const QCString&, const uint jobNum) +{ + QByteArray jobInfo = getTextJobInfo(jobNum); + QDataStream stream(jobInfo, IO_ReadOnly); + int state; + QCString appId; + QString talkerCode; + int seq; + int sentenceCount; + int partNum; + int partCount; + stream >> state; + stream >> appId; + stream >> talkerCode; + stream >> seq; + stream >> sentenceCount; + stream >> partNum; + stream >> partCount; + QString talkerID = cachedTalkerCodeToTalkerID(talkerCode); + QListViewItem* item = new QListViewItem(m_jobListView, m_jobListView->lastItem(), + QString::number(jobNum), appId, talkerID, + stateToStr(state), QString::number(seq), QString::number(sentenceCount), + QString::number(partNum), QString::number(partCount)); + // Should we select this job? + if (m_selectOnTextSet) + { + m_jobListView->setSelected(item, true); + m_selectOnTextSet = false; + } + // If a job not already selected, select this one. + autoSelectInJobListView(); +} + +/** +* This signal is emitted whenever a new part is appended to a text job. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +* @param partNum Part number of the new part. Parts are numbered starting +* at 1. +*/ +ASYNC KttsJobMgrPart::textAppended(const QCString& appId, const uint jobNum, const int /*partNum*/) +{ + textSet(appId, jobNum); +} + +/** +* This signal is emitted whenever speaking of a text job begins. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textStarted(const QCString&, const uint jobNum) +{ + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsSpeaking)); + item->setText(jlvcPosition, "1"); + } +} + +/** +* This signal is emitted whenever a text job is finished. The job has +* been marked for deletion from the queue and will be deleted when another +* job reaches the Finished state. (Only one job in the text queue may be +* in state Finished at one time.) If @ref startText or @ref resumeText is +* called before the job is deleted, it will remain in the queue for speaking. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textFinished(const QCString&, const uint jobNum) +{ + // kdDebug() << "KttsJobMgrPart::textFinished: jobNum = " << jobNum << endl; + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsFinished)); + // Update sentence pointer, since signal may not be emitted for final CR. + refreshJob(jobNum); + } + m_currentSentence->setText(QString::null); +} + +/** +* This signal is emitted whenever a speaking text job stops speaking. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textStopped(const QCString&, const uint jobNum) +{ + // kdDebug() << "KttsJobMgrPart::textStopped: jobNum = " << jobNum << endl; + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsQueued)); + item->setText(jlvcPosition, "1"); + } +} + +/** +* This signal is emitted whenever a speaking text job is paused. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textPaused(const QCString&, const uint jobNum) +{ + // kdDebug() << "KttsJobMgrPart::textPaused: jobNum = " << jobNum << endl; + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsPaused)); + } +} + +/** +* This signal is emitted when a text job, that was previously paused, resumes speaking. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textResumed(const QCString&, const uint jobNum) +{ + QListViewItem* item = findItemByJobNum(jobNum); + if (item) + { + item->setText(jlvcState, stateToStr(KSpeech::jsSpeaking)); + } +} + +/** +* This signal is emitted whenever a text job is deleted from the queue. +* The job is no longer in the queue when this signal is emitted. +* @param appId The DCOP senderId of the application that created the job. +* @param jobNum Job number of the text job. +*/ +ASYNC KttsJobMgrPart::textRemoved(const QCString&, const uint jobNum) +{ + QListViewItem* item = findItemByJobNum(jobNum); + delete item; + autoSelectInJobListView(); +} + +KttsJobMgrBrowserExtension::KttsJobMgrBrowserExtension(KttsJobMgrPart *parent) + : KParts::BrowserExtension(parent, "KttsJobMgrBrowserExtension") +{ +} + +KttsJobMgrBrowserExtension::~KttsJobMgrBrowserExtension() +{ +} diff --git a/kttsd/kttsjobmgr/kttsjobmgr.desktop b/kttsd/kttsjobmgr/kttsjobmgr.desktop new file mode 100644 index 0000000..f20da0c --- /dev/null +++ b/kttsd/kttsjobmgr/kttsjobmgr.desktop @@ -0,0 +1,49 @@ +[Desktop Entry] +Type=Service +Name=kttsjobmgrpart +Name[pt_BR]=Componente Ger. de Jobs do Ktts +Name[tr]=Kttsjobmgrpart +Comment=KDE Text-to-speech Job Manager +Comment[bg]=Мениджър на задачите за синтез на глас +Comment[ca]=Gestor de treballs de text a veu de KDE +Comment[cs]=Správce úloh zvukové syntézy KDE +Comment[da]=KDE's Tekst-til-tale Job-håndtering +Comment[de]=KDE-Auftragsverwaltung für Sprachausgabe +Comment[el]=KDE διαχειριστής εργασιών κειμένου-σε-ομιλία +Comment[es]=Administrador de trabajos del sintetizador de texto a voz de KDE +Comment[et]=KDE teksti kõneks muutmise tööde haldur +Comment[eu]=KDE-ren testutik-hizketarako lan-kudeatzailea +Comment[fa]=مدیر کار متن به گفتار KDE +Comment[fi]=KDE Teksti puheeksi -töidenhallintaohjelma +Comment[fr]=Gestionnaire de tâches de synthèse vocale pour KDE +Comment[ga]=Bainisteoir Jabanna Téacs-go-Caint KDE +Comment[gl]=Xestor de Traballos de Texto-para-Fala de KDE +Comment[hu]=KDE-s kezelőprogram szövegfelolvasáshoz +Comment[is]=KDE texti-í-tal verkstjóri +Comment[it]=Gestore dei processi di pronuncia di KDE +Comment[ja]=KDE テキスト読み上げジョブマネージャ +Comment[ka]=KDE ტექსტი-გახმოვანების ამოცანათა მმართველი +Comment[km]=ឧបករណ៍គ្រប់គ្រងការងារអត្ថបទដែលត្រូវនិយាយរបស់ KDE +Comment[mk]=Менаџер на задачи во KDE за текст-во-говор +Comment[ms]=Pengurus Kerja Teks-ke-tutur KDE +Comment[nb]=Jobbkontroll for KDE tekst-til-tale +Comment[nds]=KDE-Pleger för Vörleesopgaven +Comment[ne]=केडीई पाठ वाचक काम प्रबन्धक +Comment[nl]=KDE Tekst-tot-spraak-taakbeheer +Comment[pa]=KDE ਪਾਠ ਤੋਂ ਬੋਲੀ ਕੰਮ ਮੈਨੇਜਰ +Comment[pl]=Menedżer zadań odczytywania tekstu +Comment[pt]=Gestor de Trabalhos do Texto para Fala do KDE +Comment[pt_BR]=Gerenciador de Trabalhos de Texto para Fala do KDE +Comment[ru]=Управление заданиями по синтезу речи +Comment[sk]=Správca úloh KDE text-na-reč +Comment[sl]=Upravitelj opravil besedila v govor za KDE +Comment[sr]=KDE-ов менаџер послова за текст-у-говор +Comment[sr@Latn]=KDE-ov menadžer poslova za tekst-u-govor +Comment[sv]=KDE:s text-till-tal jobbhantering +Comment[ta]=கேடியி உரையில் இருந்து பேச்சு பணி மேலாளர் +Comment[tg]=Идоракунии фармоишҳо бо воситаи таҳлили овоз +Comment[tr]=KDE Metinden Konuşmaya Görev Yöneticisi +Comment[uk]=Менеджер завдань KDE для синтезу мовлення з тексту +Comment[vi]=Trình quản lý Tác vụ Văn bản sang Tiếng nói KDE +Comment[zh_TW]=KDE 文字轉語音工作管理員 +ServiceTypes=KParts/ReadOnlyPart diff --git a/kttsd/kttsjobmgr/kttsjobmgr.h b/kttsd/kttsjobmgr/kttsjobmgr.h new file mode 100644 index 0000000..6bb4898 --- /dev/null +++ b/kttsd/kttsjobmgr/kttsjobmgr.h @@ -0,0 +1,298 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + A KPart to display running jobs in KTTSD and permit user to stop, rewind, + advance, change Talker, etc. + ------------------- + Copyright : (C) 2004 by 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; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _KTTSJOBMGRPART_H_ +#define _KTTSJOBMGRPART_H_ + +// KDE includes. +#include <kparts/browserextension.h> +#include <klibloader.h> + +// KTTS includes. +#include "kspeech_stub.h" +#include "kspeechsink.h" + +class KAboutData; +class KInstance; +class KttsJobMgrBrowserExtension; +class KListView; +class QListViewItem; +class QVBox; +class KTextEdit; + +class KttsJobMgrFactory : public KLibFactory +{ + Q_OBJECT +public: + KttsJobMgrFactory() {}; + virtual ~KttsJobMgrFactory(); + + virtual QObject* createObject(QObject* parent = 0, const char* name = 0, + const char* classname = "QObject", + const QStringList &args = QStringList()); + + static KInstance *instance(); + static KAboutData *aboutData(); + +private: + static KInstance *s_instance; +}; + +class KttsJobMgrPart: + public KParts::ReadOnlyPart, + public KSpeech_stub, + virtual public KSpeechSink +{ + Q_OBJECT +public: + KttsJobMgrPart(QWidget *parent, const char *name); + virtual ~KttsJobMgrPart(); + +protected: + virtual bool openFile(); + virtual bool closeURL(); + + /** DCOP Methods connected to DCOP Signals emitted by KTTSD. */ + + /** + * This signal is emitted when KTTSD starts or restarts after a call to reinit. + */ + ASYNC kttsdStarted(); + /** + * This signal is emitted when the speech engine/plugin encounters a marker in the text. + * @param appId DCOP application ID of the application that queued the text. + * @param markerName The name of the marker seen. + * @see markers + */ + ASYNC markerSeen(const QCString& appId, const QString& markerName); + /** + * This signal is emitted whenever a sentence begins speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * @see getTextCount + */ + ASYNC sentenceStarted(const QCString& appId, const uint jobNum, const uint seq); + /** + * This signal is emitted when a sentence has finished speaking. + * @param appId DCOP application ID of the application that queued the text. + * @param jobNum Job number of the text job. + * @param seq Sequence number of the text. + * @see getTextCount + */ + ASYNC sentenceFinished(const QCString& appId, const uint jobNum, const uint seq); + + /** + * This signal is emitted whenever a new text job is added to the queue. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textSet(const QCString& appId, const uint jobNum); + + /** + * This signal is emitted whenever a new part is appended to a text job. + * @param appId The DCOP senderId of the application that created the job. + * @param jobNum Job number of the text job. + * @param partNum Part number of the new part. Parts are numbered starting + * at 1. + */ + ASYNC textAppended(const QCString& appId, const uint jobNum, const int partNum); + + /** + * This signal is emitted whenever speaking of a text job begins. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textStarted(const QCString& appId, const uint jobNum); + /** + * This signal is emitted whenever a text job is finished. The job has + * been marked for deletion from the queue and will be deleted when another + * job reaches the Finished state. (Only one job in the text queue may be + * in state Finished at one time.) If @ref startText or @ref resumeText is + * called before the job is deleted, it will remain in the queue for speaking. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textFinished(const QCString& appId, const uint jobNum); + /** + * This signal is emitted whenever a speaking text job stops speaking. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textStopped(const QCString& appId, const uint jobNum); + /** + * This signal is emitted whenever a speaking text job is paused. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textPaused(const QCString& appId, const uint jobNum); + /** + * This signal is emitted when a text job, that was previously paused, resumes speaking. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textResumed(const QCString& appId, const uint jobNum); + /** + * This signal is emitted whenever a text job is deleted from the queue. + * The job is no longer in the queue when this signal is emitted. + * @param appId The DCOP senderId of the application that created the job. NULL if kttsd. + * @param jobNum Job number of the text job. + */ + ASYNC textRemoved(const QCString& appId, const uint jobNum); + +private slots: + /** + * This slot is connected to the Job List View selectionChanged signal. + */ + void slot_selectionChanged(QListViewItem* item); + /** + * Slots connected to buttons. + */ + void slot_job_hold(); + void slot_job_resume(); + void slot_job_restart(); + void slot_job_remove(); + void slot_job_move(); + void slot_job_change_talker(); + void slot_speak_clipboard(); + void slot_speak_file(); + void slot_refresh(); + void slot_job_prev_par(); + void slot_job_prev_sen(); + void slot_job_next_sen(); + void slot_job_next_par(); + +private: + /** + * @enum JobListViewColumn + * Columns in the Job List View. + */ + enum JobListViewColumn + { + jlvcJobNum = 0, /**< Job Number. */ + jlvcOwner = 1, /**< AppId of job owner */ + jlvcTalkerID = 2, /**< Job Talker ID */ + jlvcState = 3, /**< Job State */ + jlvcPosition = 4, /**< Current sentence of job. */ + jlvcSentences = 5, /**< Number of sentences in job. */ + jlvcPartNum = 6, /**< Current part of the job. */ + jlvcPartCount = 7 /**< Number of parts in job. */ + }; + + /** + * Convert a KTTSD job state integer into a display string. + * @param state KTTSD job state + * @return Display string for the state. + */ + QString stateToStr(int state); + + /** + * Get the Job Number of the currently-selected job in the Job List View. + * @return Job Number of currently-selected job. + * 0 if no currently-selected job. + */ + uint getCurrentJobNum(); + + /** + * Get the number of parts in the currently-selected job in the Job List View. + * @return Number of parts in currently-selected job. + * 0 if no currently-selected job. + */ + int getCurrentJobPartCount(); + + /** + * Given a Job Number, returns the Job List View item containing the job. + * @param jobNum Job Number. + * @return QListViewItem containing the job or 0 if not found. + */ + QListViewItem* findItemByJobNum(const uint jobNum); + + /** + * Enables or disables all the job-related buttons. + * @param enable True to enable the job-related butons. False to disable. + */ + void enableJobActions(bool enable); + + /** + * Enables or disables all the job part-related buttons. + * @param enable True to enable the job par-related butons. False to disable. + */ + void enableJobPartActions(bool enable); + + /** + * Refresh display of a single job in the JobListView. + * @param jobNum Job Number. + */ + void refreshJob(uint jobNum); + + /** + * Fill the Job List View. + */ + void refreshJobListView(); + + /** + * If nothing selected in Job List View and list not empty, select top item. + * If nothing selected and list is empty, disable job buttons. + */ + void autoSelectInJobListView(); + + /** + * Return the Talker ID corresponding to a Talker Code, retrieving from cached list if present. + * @param talkerCode Talker Code. + * @return Talker ID. + */ + QString cachedTalkerCodeToTalkerID(const QString& talkerCode); + + /** + * Job ListView. + */ + KListView* m_jobListView; + KttsJobMgrBrowserExtension *m_extension; + + /** + * Current sentence box. + */ + KTextEdit* m_currentSentence; + + /** + * Box containing buttons. + */ + QVBox* m_buttonBox; + + /** + * This flag is set to True whenever we want to select the next job that + * is announced in a textSet signal. + */ + bool m_selectOnTextSet; + + /** + * Cache mapping Talker Codes to Talker IDs. + */ + QMap<QString,QString> m_talkerCodesToTalkerIDs; +}; + +class KttsJobMgrBrowserExtension : public KParts::BrowserExtension +{ + Q_OBJECT + friend class KttsJobMgrPart; +public: + KttsJobMgrBrowserExtension(KttsJobMgrPart *parent); + virtual ~KttsJobMgrBrowserExtension(); +}; + +#endif // _KTTSJOBMGRPART_H_ + diff --git a/kttsd/kttsmgr/Makefile.am b/kttsd/kttsmgr/Makefile.am new file mode 100644 index 0000000..747408d --- /dev/null +++ b/kttsd/kttsmgr/Makefile.am @@ -0,0 +1,34 @@ +# Include paths. +INCLUDES = \ + $(KTTS_KSPEECH_INCLUDE) \ + $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# APPLICATION SECTION +######################################################################### +# This is the program that gets installed. It's name is used for all +# of the other Makefile.am variables. +bin_PROGRAMS = kttsmgr + +kspeech_DIR = $(KTTS_KSPEECH_DIR) +kspeechsink_DIR = $(KTTS_KSPEECH_DIR) + +# The source, library search path, and link libraries. +kttsmgr_SOURCES = \ + kspeech.stub \ + kspeechsink.skel \ + kttsmgr.cpp + +kttsmgr_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kttsmgr_LDADD = \ + $(LIB_KUTILS) \ + $(LIB_KDEUI) + +# Header files that should not be installed. +noinst_HEADERS = kttsmgr.h + +# K Menu entry. +xdg_apps_DATA = kttsmgr.desktop diff --git a/kttsd/kttsmgr/kttsmgr.cpp b/kttsd/kttsmgr/kttsmgr.cpp new file mode 100644 index 0000000..80b66b9 --- /dev/null +++ b/kttsd/kttsmgr/kttsmgr.cpp @@ -0,0 +1,334 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KTTS Manager Program + -------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qimage.h> + +// KDE includes. +#include <kconfig.h> +#include <kuniqueapplication.h> +#include <kcmultidialog.h> +#include <kaboutdata.h> +#include <kcmdlineargs.h> +#include <kdebug.h> +#include <ksystemtray.h> +#include <kiconloader.h> +#include <kpopupmenu.h> +#include <kaboutapplication.h> +#include <dcopclient.h> +#include <kdeversion.h> + +// KTTSMgr includes. +#include "kspeech.h" +#include "kttsmgr.h" + +static const KCmdLineOptions options[] = +{ + { "s", 0, 0 }, + { "systray", I18N_NOOP("Start minimized in system tray"), 0 }, + { "a", 0, 0 }, + { "autoexit", I18N_NOOP("Exit when speaking is finished and minimized in system tray"), 0 }, + KCmdLineLastOption +}; + +int main (int argc, char *argv[]) +{ + KGlobal::locale()->setMainCatalogue("kttsd"); + + KAboutData aboutdata("kttsmgr", I18N_NOOP("KTTSMgr"), + "0.3.5.2", I18N_NOOP("Text-to-Speech Manager"), + KAboutData::License_GPL, "(C) 2002, José Pablo Ezequiel Fernández"); + aboutdata.addAuthor("José Pablo Ezequiel Fernández",I18N_NOOP("Original Author"),"pupeno@pupeno.com"); + aboutdata.addAuthor("Gary Cramblitt", I18N_NOOP("Maintainer"),"garycramblitt@comcast.net"); + aboutdata.addAuthor("Gunnar Schmi Dt", I18N_NOOP("Contributor"),"gunnar@schmi-dt.de"); + aboutdata.addAuthor("Olaf Schmidt", I18N_NOOP("Contributor"),"ojschmidt@kde.org"); + aboutdata.addAuthor("Paul Giannaros", I18N_NOOP("Contributor"), "ceruleanblaze@gmail.com"); + aboutdata.addCredit("Jorge Luis Arzola", I18N_NOOP("Testing"), "arzolacub@hotmail.com"); + aboutdata.addCredit("David Powell", I18N_NOOP("Testing"), "achiestdragon@gmail.com"); + KCmdLineArgs::init( argc, argv, &aboutdata ); + + KCmdLineArgs::addCmdLineOptions( options ); + + KUniqueApplication::addCmdLineOptions(); + + if(!KUniqueApplication::start()) + { + kdDebug() << "kttsmgr is already running" << endl; + return (0); + } + + KUniqueApplication app; + +#if KDE_VERSION >= KDE_MAKE_VERSION (3,3,90) + QPixmap icon = KGlobal::iconLoader()->loadIcon("kttsd", KIcon::Panel); + aboutdata.setProgramLogo(icon.convertToImage()); +#endif + + // The real work of KTTS Manager is done in the KControl Module kcmkttsd. + KCMultiDialog dlg(KCMultiDialog::Plain, i18n("KDE Text-to-Speech Manager"), 0, "kttsmgrdlg", false); + dlg.addModule("kcmkttsd"); + + dlg.setIcon(KGlobal::iconLoader()->loadIcon("kttsd", KIcon::Small)); + + // Get SysTray and ShowMainWindow options. + KConfig* config = new KConfig("kttsdrc"); + config->setGroup("General"); + bool embedInSysTray = config->readBoolEntry("EmbedInSysTray", true); + // Can only hide main window if we are in the system tray, otherwise, no way + // for user to do anything. + bool showMainWindowOnStartup = true; + if (embedInSysTray) + showMainWindowOnStartup = config->readBoolEntry("ShowMainWindowOnStartup", true); + + // If --systray option specified, start minimized in system tray. + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if (args->isSet("systray")) + { + embedInSysTray = true; + showMainWindowOnStartup = false; + } + + KttsMgrTray* tray = 0; + if (embedInSysTray) + { + tray = new KttsMgrTray(&dlg); + tray->show(); + } + else app.setMainWidget(&dlg); + + if (showMainWindowOnStartup) +#if KDE_VERSION < KDE_MAKE_VERSION (3,3,0) + dlg.show(); +#else + { + if (embedInSysTray) + tray->setActive(); + else + dlg.show(); + } +#endif + int result = app.exec(); + delete tray; + return result; +} + +/* KttsToolTip class */ + +KttsToolTip::KttsToolTip ( QWidget* parent ) : QToolTip(parent) +{ +} + +/*virtual*/ void KttsToolTip::maybeTip ( const QPoint & p ) +{ + Q_UNUSED(p); + + if (!parentWidget()->inherits("KttsMgrTray")) + return; + + KttsMgrTray* kttsMgrTray = dynamic_cast<KttsMgrTray*>(parentWidget()); + + QRect r(kttsMgrTray->geometry()); + if ( !r.isValid() ) + return; + + QString status = "<qt><b>KTTSMgr</b> - "; + status += i18n("<qt>Text-to-Speech Manager"); + status += "<br/><br/>"; + status += kttsMgrTray->getStatus(); + status += "</qt>"; + + tip(r, status); +} + +/* KttsMgrTray class */ + +KttsMgrTray::KttsMgrTray(QWidget *parent): + DCOPStub("kttsd", "KSpeech"), + DCOPObject("kkttsmgr_kspeechsink"), + KSystemTray(parent, "kttsmgrsystemtray") +{ + QPixmap icon = KGlobal::iconLoader()->loadIcon("kttsd", KIcon::Small); + setPixmap (icon); + + // QToolTip::add(this, i18n("Text-to-speech manager")); + m_toolTip = new KttsToolTip(this); + + int id; + id = contextMenu()->idAt(0); + if (id != -1) contextMenu()->changeTitle(id, icon, "KTTSMgr"); + + id = contextMenu()->insertItem (KGlobal::iconLoader()->loadIcon("klipper", KIcon::Small), + i18n("&Speak Clipboard Contents"), this, SLOT(speakClipboardSelected())); + id = contextMenu()->insertItem (KGlobal::iconLoader()->loadIcon("stop", KIcon::Small), + i18n("&Hold"), this, SLOT(holdSelected())); + id = contextMenu()->insertItem (KGlobal::iconLoader()->loadIcon("exec", KIcon::Small), + i18n("Resume"), this, SLOT(resumeSelected())); + id = contextMenu()->insertSeparator(); + id = contextMenu()->insertItem (KGlobal::iconLoader()->loadIcon("contents", KIcon::Small), + i18n("KTTS &Handbook"), this, SLOT(helpSelected())); + id = contextMenu()->insertItem (KGlobal::iconLoader()->loadIcon("kttsd", KIcon::Small), + i18n("&About KTTSMgr"), this, SLOT(aboutSelected())); + + connect(this, SIGNAL(quitSelected()), this, SLOT(quitSelected())); + // If --autoexit option given, exit when speaking stops. + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + if (args->isSet("autoexit")) + { + connectDCOPSignal("kttsd", "KSpeech", + "textFinished(QCString,uint)", + "textFinished(QCString,uint)", + false); + // Install an event filter so we can check when KTTSMgr becomes inconified to the systray. + parent->installEventFilter(this); + } +} + +KttsMgrTray::~KttsMgrTray() +{ + delete m_toolTip; +} + +void KttsMgrTray::textFinished(const QCString& /*appId*/, uint /*jobNum*/) +{ + // kdDebug() << "KttsMgrTray::textFinished: running" << endl; + exitWhenFinishedSpeaking(); +} + +/*virtual*/ bool KttsMgrTray::eventFilter( QObject* /*o*/, QEvent* e ) +{ + if ( e->type() == QEvent::Hide ) exitWhenFinishedSpeaking(); + return false; +} + +void KttsMgrTray::exitWhenFinishedSpeaking() +{ + // kdDebug() << "KttsMgrTray::exitWhenFinishedSpeaking: running" << endl; + if ( parentWidget()->isShown() ) return; + QString jobNums = getTextJobNumbers(); + QStringList jobNumsList = QStringList::split(jobNums, ","); + uint jobNumsListCount = jobNumsList.count(); + // Since there can only be 2 Finished jobs at a time, more than 2 jobs means at least + // one job is not Finished. + if (jobNumsListCount > 2) return; + // Exit if all jobs are Finished or there are no jobs. + for (uint ndx=0; ndx < jobNumsListCount; ++ndx) + { + if (getTextJobState(jobNumsList[ndx].toInt()) != KSpeech::jsFinished) return; + } + kapp->quit(); +} + +/** +* Convert a KTTSD job state integer into a display string. +* @param state KTTSD job state +* @return Display string for the state. +*/ +QString KttsMgrTray::stateToStr(int state) +{ + switch( state ) + { + case KSpeech::jsQueued: return i18n("Queued"); + case KSpeech::jsSpeakable: return i18n("Waiting"); + case KSpeech::jsSpeaking: return i18n("Speaking"); + case KSpeech::jsPaused: return i18n("Paused"); + case KSpeech::jsFinished: return i18n("Finished"); + default: return i18n("Unknown"); + } +} + +QString KttsMgrTray::getStatus() +{ + if (!isKttsdRunning()) return i18n("Text-to-Speech System is not running"); + uint jobCount = getTextJobCount(); + QString status = i18n("1 job", "%n jobs", jobCount); + if (jobCount > 0) + { + uint job = getCurrentTextJob(); + int jobState = 0; + if (job != 0) + { + // kdDebug() << "KttsMgrTray::getStatus: job = " << job << endl; + jobState = getTextJobState(job); + int sentenceCount = getTextCount(job); + uint seq = moveRelTextSentence(0, job); + status += i18n(", current job %1 at sentence %2 of %3 sentences" + ).arg(stateToStr(jobState)).arg(seq).arg(sentenceCount); + } + } + return status; +} + +void KttsMgrTray::speakClipboardSelected() +{ + if (!isKttsdRunning()) + { + QString error; + if (KApplication::startServiceByDesktopName("kttsd", QStringList(), &error) != 0) + kdError() << "Starting KTTSD failed with message " << error << endl; + } + speakClipboard(); +} + +void KttsMgrTray::aboutSelected() +{ + KAboutApplication aboutDlg(kapp->aboutData(), 0, "kttsmgraboutdlg", true); + aboutDlg.exec(); +} + +void KttsMgrTray::helpSelected() +{ + kapp->invokeHelp(QString::null,"kttsd"); +} + +void KttsMgrTray::quitSelected() +{ + // kdDebug() << "Running KttsMgrTray::quitSelected" << endl; + kapp->quit(); +} + +void KttsMgrTray::holdSelected() +{ + if (isKttsdRunning()) + { + uint jobNum = getCurrentTextJob(); + pauseText(jobNum); + } +} + +void KttsMgrTray::resumeSelected() +{ + if (isKttsdRunning()) + { + uint jobNum = getCurrentTextJob(); + resumeText(jobNum); + } +} + +bool KttsMgrTray::isKttsdRunning() +{ + DCOPClient *client = kapp->dcopClient(); + return (client->isApplicationRegistered("kttsd")); +} + +#include "kttsmgr.moc" diff --git a/kttsd/kttsmgr/kttsmgr.desktop b/kttsd/kttsmgr/kttsmgr.desktop new file mode 100644 index 0000000..318d7a3 --- /dev/null +++ b/kttsd/kttsmgr/kttsmgr.desktop @@ -0,0 +1,98 @@ +[Desktop Entry] +Type=Application +Name=kttsmgr +Name[pt_BR]=Gerenciador ktts +Name[tr]=Kttsmgr +GenericName=Text-to-Speech Manager +GenericName[bg]=Синтез на глас +GenericName[ca]=Gestor de text a veu +GenericName[cs]=Správce zvukové syntézy +GenericName[da]=Tekst-til-tale håndtering +GenericName[de]=Sprachausgabeverwaltung +GenericName[el]=Διαχειριστής κειμένου-σε-ομιλία +GenericName[es]=Administrador de texto a voz +GenericName[et]=Teksti kõneks muutmise haldur +GenericName[eu]=Testutik hizketarako kudeatzailea +GenericName[fa]=مدیر متن به گفتار +GenericName[fi]=Teksti puheeksi -hallintaohjelma +GenericName[fr]=Gestionnaire de synthèse vocale +GenericName[ga]=Bainisteoir Téacs-go-Caint +GenericName[gl]=Xestor de Texto-para-Fala +GenericName[he]=מנהל טקסט לדיבור +GenericName[hu]=Szövegfelolvasás-kezelő +GenericName[is]=Texti-í-tal stjóri +GenericName[it]=Gestore della pronuncia +GenericName[ja]=テキスト読み上げマネージャ +GenericName[ka]=ტექსტი-გახმოვანების მმართველი +GenericName[km]=ឧបករណ៍គ្រប់គ្រងអត្ថបទដែលត្រូវនិយាយ +GenericName[mk]=Менаџер за текст-во-говор +GenericName[ms]=Pengurus Teks-ke-Tutur +GenericName[mt]=Manaġer Test-għal-Vuċi +GenericName[nb]=Styring for tekst-til-tale +GenericName[nds]=Vörleespleger +GenericName[ne]=पाठ वाचक प्रबन्धक +GenericName[nl]=Tekst-tot-spraak-beheerder +GenericName[pa]=ਪਾਠ ਤੋਂ ਬੋਲੀ ਮੈਨੇਜਰ +GenericName[pl]=Menedżer odczytywania tekstu +GenericName[pt]=Gestor do Texto para Fala +GenericName[pt_BR]=Gerenciador de Conversão de Texto para Fala do KDE +GenericName[ru]=Управление синтезом речи +GenericName[sk]=Správca text-na-reč +GenericName[sl]=Upravitelj besedila v govor +GenericName[sr]=Менаџер за текст у говор +GenericName[sr@Latn]=Menadžer za tekst u govor +GenericName[sv]=Text-till-tal hantering +GenericName[ta]=உரையில் இருந்து பேச்சு மேலாளர் +GenericName[tg]=Идоракунии таҳлили овоз +GenericName[tr]=Metinden Konuşmaya Yöneticisi +GenericName[uk]=Менеджер для синтезу мовлення з тексту +GenericName[vi]=Trình quản lý Văn bản sang Tiếng nói +GenericName[zh_TW]=文字轉語音管理員 +Icon=kttsd +Exec=kttsmgr -caption "%c" %i %u +Comment=KDE Text-to-speech Manager +Comment[bg]=Мениджър на синтезатора на глас +Comment[ca]=Gestor de text a veu de KDE +Comment[cs]=Správce hlasové syntézy KDE +Comment[da]=KDE's Tekst-til-tale håndtering +Comment[de]=KDE Verwaltung für Sprachausgabe +Comment[el]=KDE διαχειριστής κειμένου-σε-ομιλία +Comment[es]=Administrador de texto a voz de KDE +Comment[et]=KDE teksti kõneks muutmise haldur +Comment[eu]=KDE-ren testutik hizketarako kudeatzailea +Comment[fa]=مدیر متن به گفتار KDE +Comment[fi]=KDE Teksti puheeksi -hallintaohjelma +Comment[fr]=Gestionnaire de synthèse vocale pour KDE +Comment[ga]=Bainisteoir Téacs-go-Caint KDE +Comment[gl]=Xestor de Texto-para-fala de KDE +Comment[he]=מנהל טקסט לדיבור של KDE +Comment[hu]=KDE-s szövegfelolvasás-kezelő +Comment[is]=KDE texti-í-tal stjóri +Comment[it]=Gestore della pronuncia di KDE +Comment[ja]=KDE テキスト読み上げマネージャ +Comment[ka]=KDE ტექსტი-გახმოვანების მმართველი +Comment[km]=ឧបករណ៍គ្រប់គ្រងអត្ថបទដែលត្រូវនិយាយរបស់ KDE +Comment[mk]=Менаџер на KDE за текст-во-говор +Comment[ms]=Pengurus Teks-ke-tutur KDE +Comment[nb]=Styring for KDEs tekst-til-tale +Comment[nds]=Vörleespleger för KDE +Comment[ne]=केडीई पाठ वाचक प्रबन्धक +Comment[nl]=KDE Tekst-tot-spraak-beheer +Comment[pa]=KDE ਪਾਠ ਤੋਂ ਬੋਲੀ ਮੈਨੇਜਰ +Comment[pl]=Menedżer odczytywania tekstu dla KDE +Comment[pt]=Gestor do Texto para Fala do KDE +Comment[pt_BR]=Gerenciador de Conversão de Texto para Fala do KDE +Comment[ru]=Менеджер движков синтеза речи +Comment[sk]=KDE text-na-reč manažér +Comment[sl]=Upravitelj besedila v govor v KDE +Comment[sr]=KDE-ов менаџер за текст у говор +Comment[sr@Latn]=KDE-ov menadžer za tekst u govor +Comment[sv]=KDE:s text-till-tal hantering +Comment[ta]=கேடியி உரையில் இருந்து பேச்சு மேலாளர் +Comment[tg]=Идоракунандаи микшерҳои таҳлили овоз +Comment[tr]=KDE Metinden Konuşmaya yöneticisi +Comment[uk]=Менеджер KDE для синтезу мовлення з тексту +Comment[vi]=Trình quản lý Văn bản sang Tiếng nói KDE +Comment[zh_TW]=KDE 文字轉語音管理員 +Terminal=false +Categories=Qt;KDE;Utility;Accessibility; diff --git a/kttsd/kttsmgr/kttsmgr.h b/kttsd/kttsmgr/kttsmgr.h new file mode 100644 index 0000000..e13233a --- /dev/null +++ b/kttsd/kttsmgr/kttsmgr.h @@ -0,0 +1,86 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + KTTS Manager Program + -------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef KTTSMGR_H +#define KTTSMGR_H + +// Qt includes. +#include <qevent.h> +#include <qtooltip.h> + +// KDE includes. +#include <ksystemtray.h> + +// KTTS includes. +#include "kspeech_stub.h" +#include "kspeechsink.h" + +class KttsToolTip: public QToolTip +{ + public: + KttsToolTip ( QWidget* parent ); + + protected: + virtual void maybeTip ( const QPoint & p ); +}; + +class KttsMgrTray: public KSystemTray, public KSpeech_stub, virtual public KSpeechSink +{ + Q_OBJECT + + public: + KttsMgrTray(QWidget *parent=0); + ~KttsMgrTray(); + + void setExitWhenFinishedSpeaking(); + QString getStatus(); + + protected: + // ASYNC textStarted(const QCString& appId, uint jobNum); + ASYNC textFinished(const QCString& appId, uint jobNum); + virtual bool eventFilter( QObject* o, QEvent* e ); + + private slots: + + void speakClipboardSelected(); + void holdSelected(); + void resumeSelected(); + void aboutSelected(); + void helpSelected(); + void quitSelected(); + + private: + /** + * Convert a KTTSD job state integer into a display string. + * @param state KTTSD job state + * @return Display string for the state. + */ + QString stateToStr(int state); + + bool isKttsdRunning(); + void exitWhenFinishedSpeaking(); + KttsToolTip* m_toolTip; +}; + +#endif // KTTSMGR_H diff --git a/kttsd/libkttsd/Makefile.am b/kttsd/libkttsd/Makefile.am new file mode 100644 index 0000000..3b2c099 --- /dev/null +++ b/kttsd/libkttsd/Makefile.am @@ -0,0 +1,45 @@ +# Include paths. +INCLUDES = $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# LIBRARY SECTION +######################################################################### +# This is the library that gets installed. It's name is used for all +# of the other Makefile.am variables. +lib_LTLIBRARIES = libkttsd.la + +# The source, library search path, and link libraries. +libkttsd_la_SOURCES = \ + pluginproc.cpp \ + pluginconf.cpp \ + testplayer.cpp \ + stretcher.cpp \ + talkercode.cpp \ + filterproc.cpp \ + filterconf.cpp \ + utils.cpp \ + selecttalkerdlg.cpp \ + selecttalkerwidget.ui \ + notify.cpp + +libkttsd_la_LDFLAGS = -version-info 1:0:0 $(all_libraries) -no-undefined +libkttsd_la_LIBADD = $(LIB_KIO) + +# Header files that should not be installed. +noinst_HEADERS = \ + pluginproc.h \ + pluginconf.h \ + player.h \ + testplayer.h \ + stretcher.h \ + talkercode.h \ + utils.h \ + kdeexportfix.h \ + selecttalkerdlg.h + +# This library is installed as a service. +servicetypes_DATA = kttsd_synthplugin.desktop +servicetypesdir = $(kde_servicetypesdir) diff --git a/kttsd/libkttsd/filterconf.cpp b/kttsd/libkttsd/filterconf.cpp new file mode 100644 index 0000000..43e62bc --- /dev/null +++ b/kttsd/libkttsd/filterconf.cpp @@ -0,0 +1,166 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Configuration class. + This is the interface definition for text filter configuration dialogs. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// C++ library includes. +#include <stdlib.h> +#include <sys/param.h> + +// Qt includes. +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> + +// PluginConf includes. +#include "filterconf.h" +#include "filterconf.moc" + +/** +* Constructor +*/ +KttsFilterConf::KttsFilterConf( QWidget *parent, const char *name) : QWidget(parent, name){ + // kdDebug() << "KttsFilterConf::KttsFilterConf: Running" << endl; + QString systemPath(getenv("PATH")); + // kdDebug() << "Path is " << systemPath << endl; + KGlobal::locale()->insertCatalogue("kttsd"); + m_path = QStringList::split(":", systemPath); +} + +/** +* Destructor. +*/ +KttsFilterConf::~KttsFilterConf(){ + // kdDebug() << "KttsFilterConf::~KttsFilterConf: Running" << endl; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void KttsFilterConf::load(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "KttsFilterConf::load: Running" << endl; +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void KttsFilterConf::save(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "KttsFilterConf::save: Running" << endl; +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void KttsFilterConf::defaults(){ + // kdDebug() << "KttsFilterConf::defaults: Running" << endl; +} + +/** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ +bool KttsFilterConf::supportsMultiInstance() { return false; } + +/** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ +QString KttsFilterConf::userPlugInName() { return QString::null; } + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * @return True if this filter is a SBD. + */ +bool KttsFilterConf::isSBD() { return false; } + +/** +* Return the full path to any program in the $PATH environmental variable +* @param name The name of the file to search for. +* @returns The path to the file on success, a blank QString +* if its not found. +*/ +QString KttsFilterConf::getLocation(const QString &name) { + // Iterate over the path and see if 'name' exists in it. Return the + // full path to it if it does. Else return an empty QString. + if (QFile::exists(name)) return name; + // kdDebug() << "KttsFilterConf::getLocation: Searching for " << name << " in the path.." << endl; + // kdDebug() << m_path << endl; + for(QStringList::iterator it = m_path.begin(); it != m_path.end(); ++it) { + QString fullName = *it; + fullName += "/"; + fullName += name; + // The user either has the directory of the file in the path... + if(QFile::exists(fullName)) { + // kdDebug() << "KttsFilterConf:getLocation: " << fullName << endl; + return fullName; + } + // ....Or the file itself + else if(QFileInfo(*it).baseName().append(QString(".").append(QFileInfo(*it).extension())) == name) { + // kdDebug() << "KttsFilterConf:getLocation: " << fullName << endl; + return fullName; + } + } + return ""; +} + +/*static*/ QString KttsFilterConf::realFilePath(const QString &filename) +{ + char realpath_buffer[MAXPATHLEN + 1]; + memset(realpath_buffer, 0, MAXPATHLEN + 1); + + /* If the path contains symlinks, get the real name */ + if (realpath( QFile::encodeName(filename).data(), realpath_buffer) != 0) { + //succes, use result from realpath + return QFile::decodeName(realpath_buffer); + } + return filename; +} diff --git a/kttsd/libkttsd/filterconf.h b/kttsd/libkttsd/filterconf.h new file mode 100644 index 0000000..42055d8 --- /dev/null +++ b/kttsd/libkttsd/filterconf.h @@ -0,0 +1,149 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Configuration class. + This is the interface definition for text filter configuration dialogs. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FILTERCONF_H_ +#define _FILTERCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> +#include <kdemacros.h> +#include "kdeexportfix.h" + +class KDE_EXPORT KttsFilterConf : public QWidget{ + Q_OBJECT + + public: + /** + * Constructor + */ + KttsFilterConf( QWidget *parent = 0, const char *name = 0); + + /** + * Destructor + */ + virtual ~KttsFilterConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can be configured. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * Returns the name of the plugin. Displayed in Filters tab of KTTSMgr. + * If there can be more than one instance of a filter, it should return + * a unique name for each instance. The name should be translated for + * the user if possible. If the plugin is not correctly configured, + * return an empty string. + * @return Filter instance name. + */ + virtual QString userPlugInName(); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + static QString realFilePath(const QString &filename); + + public slots: + /** + * This slot is used internally when the configuration is changed. It is + * typically connected to signals from the widgets of the configuration + * and should emit the @ref changed signal. + */ + void configChanged(){ + // kdDebug() << "KttsFilterConf::configChanged: Running"<< endl; + emit changed(true); + }; + + signals: + /** + * This signal indicates that the configuration has been changed. + * It should be emitted whenever user changes something in the configuration widget. + */ + void changed(bool); + + protected: + /** + * Searches the $PATH variable for any file. If that file exists in the PATH, or + * is contained in any directory in the PATH, it returns the full path to it. + * @param name The name of the file to search for. + * @returns The path to the file on success, a blank QString + * if its not found. + */ + QString getLocation(const QString &name); + + /// The system path in a QStringList. + QStringList m_path; +}; + +#endif //_FILTERCONF_H_ diff --git a/kttsd/libkttsd/filterproc.cpp b/kttsd/libkttsd/filterproc.cpp new file mode 100644 index 0000000..178dec2 --- /dev/null +++ b/kttsd/libkttsd/filterproc.cpp @@ -0,0 +1,153 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Processing class. + This is the interface definition for text filters. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// KDE includes. +// #include <kdebug.h> + +// FilterProc includes. +#include "filterproc.h" + +/** + * Constructor. + */ +KttsFilterProc::KttsFilterProc( QObject *parent, const char *name) : + QObject(parent, name) +{ + // kdDebug() << "KttsFilterProc::KttsFilterProc: Running" << endl; +} + +/** + * Destructor. + */ +KttsFilterProc::~KttsFilterProc() +{ + // kdDebug() << "KttsFilterProc::~KttsFilterProc: Running" << endl; +} + +/** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ +bool KttsFilterProc::init(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "PlugInProc::init: Running" << endl; + return false; +} + +/** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ +/*virtual*/ bool KttsFilterProc::isSBD() { return false; } + +/** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ +/*virtual*/ bool KttsFilterProc::supportsAsync() { return false; } + +/** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ +/*virtual*/ QString KttsFilterProc::convert(const QString& inputText, TalkerCode* /*talkerCode*/, + const QCString& /*appId*/) +{ + return inputText; +} + +/** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ +/*virtual*/ bool KttsFilterProc::asyncConvert(const QString& /*inputText*/, + TalkerCode* /*talkerCode*/, const QCString& /*appId*/) { return false; } + +/** + * Waits for a previous call to asyncConvert to finish. + */ +/*virtual*/ void KttsFilterProc::waitForFinished() { } + +/** + * Returns the state of the Filter. + */ +/*virtual*/ int KttsFilterProc::getState() { return fsIdle; } + +/** + * Returns the filtered output. + */ +/*virtual*/ QString KttsFilterProc::getOutput() { return QString::null; } + +/** + * Acknowledges the finished filtering. + */ +/*virtual*/ void KttsFilterProc::ackFinished() { } + +/** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ +/*virtual*/ void KttsFilterProc::stopFiltering() { } + +/** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ +/*virtual*/ bool KttsFilterProc::wasModified() { return true; } + +/** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ +/*virtual*/ void KttsFilterProc::setSbRegExp(const QString& /*re*/) { } + +#include "filterproc.moc" diff --git a/kttsd/libkttsd/filterproc.h b/kttsd/libkttsd/filterproc.h new file mode 100644 index 0000000..ef7972c --- /dev/null +++ b/kttsd/libkttsd/filterproc.h @@ -0,0 +1,179 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Filter Processing class. + This is the interface definition for text filters. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FILTERPROC_H_ +#define _FILTERPROC_H_ + +// Qt includes. +#include <qobject.h> +#include <qstringlist.h> + +// KDE includes. +#include <kdemacros.h> +#include "kdeexportfix.h" + +class TalkerCode; +class KConfig; + +class KDE_EXPORT KttsFilterProc : virtual public QObject +{ + Q_OBJECT + +public: + enum FilterState { + fsIdle = 0, // Not doing anything. Ready to filter. + fsFiltering = 1, // Filtering. + fsStopping = 2, // Stop of filtering is in progress. + fsFinished = 3 // Filtering finished. + }; + + /** + * Constructor. + */ + KttsFilterProc( QObject *parent, const char *name ); + + /** + * Destructor. + */ + virtual ~KttsFilterProc(); + + /** + * Initialize the filter. + * @param config Settings object. + * @param configGroup Settings Group. + * @return False if filter is not ready to filter. + * + * Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain + * separate configuration files of their own. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Returns True if this filter is a Sentence Boundary Detector. + * If so, the filter should implement @ref setSbRegExp() . + * @return True if this filter is a SBD. + */ + virtual bool isSBD(); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., supports asyncConvert method. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref filteringFinished when filtering is completed. + * If the plugin returns True, it must also implement @ref stopFiltering . + * It must also emit @ref filteringStopped when filtering has been stopped. + */ + virtual bool supportsAsync(); + + /** + * Convert input, returning output. Runs synchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + */ + virtual QString convert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Convert input. Runs asynchronously. + * @param inputText Input text. + * @param talkerCode TalkerCode structure for the talker that KTTSD intends to + * use for synthing the text. Useful for extracting hints about + * how to filter the text. For example, languageCode. + * @param appId The DCOP appId of the application that queued the text. + * Also useful for hints about how to do the filtering. + * @return False if the filter cannot perform the conversion. + * + * When conversion is completed, emits signal @ref filteringFinished. Calling + * program may then call @ref getOutput to retrieve converted text. Calling + * program must call @ref ackFinished to acknowledge the conversion. + */ + virtual bool asyncConvert(const QString& inputText, TalkerCode* talkerCode, const QCString& appId); + + /** + * Waits for a previous call to asyncConvert to finish. + */ + virtual void waitForFinished(); + + /** + * Returns the state of the Filter. + */ + virtual int getState(); + + /** + * Returns the filtered output. + */ + virtual QString getOutput(); + + /** + * Acknowledges the finished filtering. + */ + virtual void ackFinished(); + + /** + * Stops filtering. The filteringStopped signal will emit when filtering + * has in fact stopped and state returns to fsIdle; + */ + virtual void stopFiltering(); + + /** + * Did this filter do anything? If the filter returns the input as output + * unmolested, it should return False when this method is called. + */ + virtual bool wasModified(); + + /** + * Set Sentence Boundary Regular Expression. + * This method will only be called if the application overrode the default. + * + * @param re The sentence delimiter regular expression. + */ + virtual void setSbRegExp(const QString& re); + +signals: + /** + * Emitted when asynchronous filtering has completed. + */ + void filteringFinished(); + + /** + * Emitted when stopFiltering has been called and filtering has in fact stopped. + */ + void filteringStopped(); + + /** + * If an error occurs, Filter should signal the error and return input as output in + * convert method. If Filter should not be called in the future, perhaps because + * it could not find its configuration file, return False for keepGoing. + * @param keepGoing False if the filter should not be called in the future. + * @param msg Error message. + */ + void error(bool keepGoing, const QString &msg); +}; + +#endif // _FILTERPROC_H_ diff --git a/kttsd/libkttsd/kdeexportfix.h b/kttsd/libkttsd/kdeexportfix.h new file mode 100644 index 0000000..7c80d9c --- /dev/null +++ b/kttsd/libkttsd/kdeexportfix.h @@ -0,0 +1,27 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + kdelibs < 3.3.2 had a bug in the KDE_EXPORT macro. This file fixes this + by undefining it. + ------------------- + Copyright : (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _KDEEXPORTFIX_H_ +#define _KDEEXPORTFIX_H_ + +#include <kdeversion.h> +#if KDE_VERSION < KDE_MAKE_VERSION (3,3,2) +#undef KDE_EXPORT +#define KDE_EXPORT +#endif + +#endif // _KDEEXPORTFIX_H_ diff --git a/kttsd/libkttsd/kttsd_synthplugin.desktop b/kttsd/libkttsd/kttsd_synthplugin.desktop new file mode 100644 index 0000000..c6367af --- /dev/null +++ b/kttsd/libkttsd/kttsd_synthplugin.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=KTTSD +Name[zh_TW]=KTTSd +Type=ServiceType +X-KDE-ServiceType=KTTSD/SynthPlugin + +[PropertyDef::X-KDE-Languages] +Type=QStringList diff --git a/kttsd/libkttsd/notify.cpp b/kttsd/libkttsd/notify.cpp new file mode 100644 index 0000000..fd28587 --- /dev/null +++ b/kttsd/libkttsd/notify.cpp @@ -0,0 +1,177 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Notification Action constants and utility functions. + ------------------- + Copyright : (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + Current Maintainer: 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +// Qt includes. + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> + +// KDE includes. +#include <kconfig.h> +#include <klocale.h> +#include <kstaticdeleter.h> + +// KTTS includes. +#include "notify.h" + +static QStringList* s_actionNames = 0; +static KStaticDeleter<QStringList> s_actionNames_sd; + +static QStringList* s_actionDisplayNames = 0; +static KStaticDeleter<QStringList> s_actionDisplayNames_sd; + +static void notifyaction_init() +{ + if ( !s_actionNames ) + { + s_actionNames_sd.setObject(s_actionNames, new QStringList); + s_actionNames->append( "SpeakEventName" ); + s_actionNames->append( "SpeakMsg" ); + s_actionNames->append( "DoNotSpeak" ); + s_actionNames->append( "SpeakCustom" ); + + s_actionDisplayNames_sd.setObject(s_actionDisplayNames, new QStringList); + s_actionDisplayNames->append( i18n("Speak event name") ); + s_actionDisplayNames->append( i18n("Speak the notification message") ); + s_actionDisplayNames->append( i18n("Do not speak the notification") ); + s_actionDisplayNames->append( i18n("Speak custom text:") ); + } +} + +/*static*/ int NotifyAction::count() +{ + notifyaction_init(); + return s_actionNames->count(); +} + +/*static*/ QString NotifyAction::actionName( const int action ) +{ + notifyaction_init(); + return (*s_actionNames)[ action ]; +} + +/*static*/ int NotifyAction::action( const QString& actionName ) +{ + notifyaction_init(); + return s_actionNames->findIndex( actionName ); +} + +/*static*/ QString NotifyAction::actionDisplayName( const int action ) +{ + notifyaction_init(); + return (*s_actionDisplayNames)[ action ]; +} + +/*static*/ QString NotifyAction::actionDisplayName( const QString& actionName ) +{ + notifyaction_init(); + return (*s_actionDisplayNames)[ action( actionName ) ]; +} + +// -------------------------------------------------------------------- + +static QStringList* s_presentNames = 0; +static KStaticDeleter<QStringList> s_presentNames_sd; + +static QStringList* s_presentDisplayNames = 0; +static KStaticDeleter<QStringList> s_presentDisplayNames_sd; + +static void notifypresent_init() +{ + if ( !s_presentNames ) + { + s_presentNames_sd.setObject( s_presentNames, new QStringList ); + s_presentNames->append( "None" ); + s_presentNames->append( "Dialog" ); + s_presentNames->append( "Passive" ); + s_presentNames->append( "DialogAndPassive" ); + s_presentNames->append( "All" ); + + s_presentDisplayNames_sd.setObject( s_presentDisplayNames, new QStringList ); + s_presentDisplayNames->append( i18n("none") ); + s_presentDisplayNames->append( i18n("notification dialogs") ); + s_presentDisplayNames->append( i18n("passive popups") ); + s_presentDisplayNames->append( i18n("notification dialogs and passive popups") ); + s_presentDisplayNames->append( i18n("all notifications" ) ); + } +} + +/*static*/ int NotifyPresent::count() +{ + notifypresent_init(); + return s_presentNames->count(); +} + +/*static*/ QString NotifyPresent::presentName( const int present ) +{ + notifypresent_init(); + return (*s_presentNames)[ present ]; +} + +/*static*/ int NotifyPresent::present( const QString& presentName ) +{ + notifypresent_init(); + return s_presentNames->findIndex( presentName ); +} + +/*static*/ QString NotifyPresent::presentDisplayName( const int present ) +{ + notifypresent_init(); + return (*s_presentDisplayNames)[ present ]; +} + +/*static*/ QString NotifyPresent::presentDisplayName( const QString& presentName ) +{ + notifypresent_init(); + return (*s_presentDisplayNames)[ present( presentName ) ]; +} + +// -------------------------------------------------------------------- + +/** + * Retrieves the displayable name for an event source. + */ +/*static*/ QString NotifyEvent::getEventSrcName(const QString& eventSrc, QString& iconName) +{ + QString configFilename = eventSrc + QString::fromLatin1( "/eventsrc" ); + KConfig* config = new KConfig( configFilename, true, false, "data" ); + config->setGroup( QString::fromLatin1( "!Global!" ) ); + QString appDesc = config->readEntry( "Comment", i18n("No description available") ); + iconName = config->readEntry( "IconName" ); + delete config; + return appDesc; +} + +/** + * Retrieves the displayable name for an event from an event source. + */ +/*static*/ QString NotifyEvent::getEventName(const QString& eventSrc, const QString& event) +{ + QString eventName; + QString configFilename = eventSrc + QString::fromLatin1( "/eventsrc" ); + KConfig* config = new KConfig( configFilename, true, false, "data" ); + if ( config->hasGroup( event ) ) + { + config->setGroup( event ); + eventName = config->readEntry( QString::fromLatin1( "Comment" ), + config->readEntry( QString::fromLatin1( "Name" ))); + } + delete config; + return eventName; +} + diff --git a/kttsd/libkttsd/notify.h b/kttsd/libkttsd/notify.h new file mode 100644 index 0000000..2a8162d --- /dev/null +++ b/kttsd/libkttsd/notify.h @@ -0,0 +1,85 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Notification Action constants and utility functions. + ------------------- + Copyright : (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + Current Maintainer: 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _NOTIFYACTION_H +#define _NOTIFYACTION_H + +#include <kdemacros.h> + +class QString; + +class KDE_EXPORT NotifyAction +{ + +public: + + enum NotifyActions + { + SpeakEventName, + SpeakMsg, + DoNotSpeak, + SpeakCustom + }; + + static int count(); + static QString actionName( const int action ); + static int action( const QString& actionName ); + static QString actionDisplayName( const int action ); + static QString actionDisplayName( const QString& actionName ); +}; + +// -------------------------------------------------------------------- + +class KDE_EXPORT NotifyPresent +{ + +public: + + enum NotifyPresentations + { + None, + Dialog, + Passive, + DialogAndPassive, + All + }; + + static int count(); + static QString presentName( const int present ); + static int present( const QString& presentName ); + static QString presentDisplayName( const int present ); + static QString presentDisplayName( const QString& presentName ); +}; + +// -------------------------------------------------------------------- + +class KDE_EXPORT NotifyEvent +{ + +public: + /** + * Retrieves the displayable name for an event source. + */ + static QString getEventSrcName(const QString& eventSrc, QString& iconName); + + /** + * Retrieves the displayable name for an event from an event source. + */ + static QString getEventName(const QString& eventSrc, const QString& event); +}; + +#endif // _NOTIFYACTION_H diff --git a/kttsd/libkttsd/player.h b/kttsd/libkttsd/player.h new file mode 100644 index 0000000..ff4d240 --- /dev/null +++ b/kttsd/libkttsd/player.h @@ -0,0 +1,76 @@ +/*************************************************************************** + begin : Sun Feb 17 2002 + copyright : (C) 2002 - 2004 by Scott Wheeler + email : wheeler@kde.org +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef PLAYER_H +#define PLAYER_H + +#include <qobject.h> +#include <qstringlist.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include "kglobal.h" +#include "klocale.h" + +// #include "filehandle.h" + +class KDE_EXPORT Player : virtual public QObject +{ + Q_OBJECT + +public: + virtual ~Player() {} + +// virtual void play(const FileHandle &file = FileHandle::null()) = 0; + virtual void startPlay(const QString& file) = 0; + virtual void pause() = 0; + virtual void stop() = 0; + + virtual void setVolume(float volume = 1.0) = 0; + virtual float volume() const = 0; + + virtual bool playing() const = 0; + virtual bool paused() const = 0; + + virtual int totalTime() const = 0; + virtual int currentTime() const = 0; + virtual int position() const = 0; // in this case not really the percent + + virtual void seek(int seekTime) = 0; + virtual void seekPosition(int position) = 0; + + virtual QStringList getPluginList( const QCString& classname ) { + Q_UNUSED(classname); + return QStringList(); + } + virtual void setSinkName(const QString &sinkName) { Q_UNUSED(sinkName); } + virtual bool requireVersion(uint major, uint minor, uint micro) { + Q_UNUSED(major); + Q_UNUSED(minor); + Q_UNUSED(micro); + return true; + } + virtual void setDebugLevel(uint level) { Q_UNUSED(level); } + virtual void setPeriodSize(uint periodSize) { Q_UNUSED(periodSize); } + virtual void setPeriods(uint periods) {Q_UNUSED(periods); } + +protected: + Player(QObject* parent = 0, const char* name = 0, const QStringList& args=QStringList() ) : + QObject(parent, name) { + Q_UNUSED(args); + } + +}; + +#endif diff --git a/kttsd/libkttsd/pluginconf.cpp b/kttsd/libkttsd/pluginconf.cpp new file mode 100644 index 0000000..d2fb4b6 --- /dev/null +++ b/kttsd/libkttsd/pluginconf.cpp @@ -0,0 +1,245 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the templates for the configuration plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +// C++ library includes. +#include <stdlib.h> +#include <sys/param.h> + +// Qt includes. +#include <qfile.h> +#include <qfileinfo.h> +#include <qstring.h> + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> +#include <kstandarddirs.h> + +// PluginConf includes. +#include "pluginconf.h" +#include "pluginconf.moc" + +/** +* Constructor +*/ +PlugInConf::PlugInConf( QWidget *parent, const char *name) : QWidget(parent, name){ + kdDebug() << "PlugInConf::PlugInConf: Running" << endl; + KGlobal::locale()->insertCatalogue("kttsd"); + QString systemPath(getenv("PATH")); + // kdDebug() << "Path is " << systemPath << endl; + m_path = QStringList::split(":", systemPath); + m_player = 0; +} + +/** +* Destructor. +*/ +PlugInConf::~PlugInConf(){ + kdDebug() << "PlugInConf::~PlugInConf: Running" << endl; + delete m_player; +} + +/** +* This method is invoked whenever the module should read its +* configuration (most of the times from a config file) and update the +* user interface. This happens when the user clicks the "Reset" button in +* the control center, to undo all of his changes and restore the currently +* valid settings. Note that kttsmgr calls this when the plugin is +* loaded, so it not necessary to call it in your constructor. +* The plugin should read its configuration from the specified group +* in the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* loading your configuration. +*/ +void PlugInConf::load(KConfig* /*config*/, const QString& /*configGroup*/){ + kdDebug() << "PlugInConf::load: Running" << endl; +} + +/** +* This function gets called when the user wants to save the settings in +* the user interface, updating the config files or wherever the +* configuration is stored. The method is called when the user clicks "Apply" +* or "Ok". The plugin should save its configuration in the specified +* group of the specified config file. +* @param config Pointer to a KConfig object. +* @param configGroup Call config->setGroup with this argument before +* saving your configuration. +*/ +void PlugInConf::save(KConfig* /*config*/, const QString& /*configGroup*/){ + kdDebug() << "PlugInConf::save: Running" << endl; +} + +/** +* This function is called to set the settings in the module to sensible +* default values. It gets called when hitting the "Default" button. The +* default values should probably be the same as the ones the application +* uses when started without a config file. Note that defaults should +* be applied to the on-screen widgets; not to the config file. +*/ +void PlugInConf::defaults(){ + kdDebug() << "PlugInConf::defaults: Running" << endl; +} + +/** +* Indicates whether the plugin supports multiple instances. Return +* False if only one instance of the plugin can run at a time. +* @return True if multiple instances are possible. +* +* It is assumed that most plugins can support multiple instances. +* A plugin must override this method and return false if it +* cannot support multiple instances. +*/ +bool PlugInConf::supportsMultiInstance() { return true; } + +/** +* This function informs the plugin of the desired language to be spoken +* by the plugin. The plugin should attempt to adapt itself to the +* specified language code, choosing sensible defaults if necessary. +* If the passed-in code is QString::null, no specific language has +* been chosen. +* @param lang The desired language code or Null if none. +* +* If the plugin is unable to support the desired language, that is OK. +*/ +void PlugInConf::setDesiredLanguage(const QString& /*lang*/ ) { } + +/** +* Return fully-specified talker code for the configured plugin. This code +* uniquely identifies the configured instance of the plugin and distinquishes +* one instance from another. If the plugin has not been fully configured, +* i.e., cannot yet synthesize, return QString::null. +* @return Fully-specified talker code. +*/ +QString PlugInConf::getTalkerCode() { return QString::null; } + +/** +* Return a list of all the languages currently supported by the plugin. +* Note that as the user configures your plugin, the language choices may become +* narrower. For example, once the user has picked a voice file, the language +* may be determined. If your plugin has just been added and no configuration +* choices have yet been made, return a list of all possible languages the +* plugin might support. +* If your plugin cannot yet determine the languages supported, return Null. +* If your plugin can support any language, return Null. +* @return A QStringList of supported language codes, or Null if unknown. +*/ +QStringList PlugInConf::getSupportedLanguages() { return QStringList(); } + +/** +* Return the full path to any program in the $PATH environmental variable +* @param name The name of the file to search for. +* @returns The path to the file on success, a blank QString +* if its not found. +*/ +QString PlugInConf::getLocation(const QString &name) { + // Iterate over the path and see if 'name' exists in it. Return the + // full path to it if it does. Else return an empty QString. + + // If it's a file or a symlink pointing to a file, that's cool. + QFileInfo fileinfo(name); + if (fileinfo.isFile() || (fileinfo.isSymLink() && QFileInfo(fileinfo.readLink()).isFile())) + return name; + kdDebug() << "PluginConf::getLocation: Searching for " << name << " in the path.." << endl; + kdDebug() << m_path << endl; + for(QStringList::iterator it = m_path.begin(); it != m_path.end(); ++it) { + QString fullName = *it; + + fullName += "/"; + fullName += name; + fileinfo.setFile(fullName); + // The user either has the directory of the file in the path... + if(fileinfo.isFile() || (fileinfo.isSymLink() && QFileInfo(fileinfo.readLink()).isFile())) { + return fullName; +// kdDebug() << "PluginConf:getLocation: " << fullName << endl; + } + // ....Or the file itself in the path (slightly freaky but hey.) + else if(QFileInfo(*it).baseName().append(QString(".").append(QFileInfo(*it).extension())) == name) { + return fullName; +// kdDebug() << "PluginConf:getLocation: " << fullName << endl; + } + } + return ""; +} + +/** +* Breaks a language code into the language code and country code (if any). +* @param languageCode Language code. +* @return countryCode Just the country code part (if any). +* @return Just the language code part. +*/ +QString PlugInConf::splitLanguageCode(const QString& languageCode, QString& countryCode) +{ + QString locale = languageCode; + QString langCode; + QString charSet; + KGlobal::locale()->splitLocale(locale, langCode, countryCode, charSet); + return langCode; +} + +/*static*/ QString PlugInConf::realFilePath(const QString &filename) +{ + char realpath_buffer[MAXPATHLEN + 1]; + memset(realpath_buffer, 0, MAXPATHLEN + 1); + + /* If the path contains symlinks, get the real name */ + if (realpath( QFile::encodeName(filename).data(), realpath_buffer) != 0) { + // succes, use result from realpath + return QFile::decodeName(realpath_buffer); + } + return filename; +} + +/*static*/ QString PlugInConf::testMessage(const QString& languageCode) +{ + QString key = "Name[" + languageCode + "]"; + QString result; + QString def; + QFile file(locate("data", "kttsd/kcmkttsd_testmessage.desktop")); + if (file.open(IO_ReadOnly)) + { + QTextStream stream(&file); + stream.setEncoding(QTextStream::UnicodeUTF8); + while ( !stream.atEnd() ) { + QString line = stream.readLine(); // line of text excluding '\n' + QStringList keyAndValue = QStringList::split("=", line); + if (keyAndValue.count() == 2) + { + if (keyAndValue[0] == key) + { + result = keyAndValue[1]; + break; + } + if (keyAndValue[0] == "Name") def = keyAndValue[1]; + } + } + file.close(); + } + if (result.isEmpty()) + { + result = def; + if (result.isEmpty()) result = "The text-to-speech system seems to be functioning properly."; + } + return result; +} + +/** +* Player object that can be used by the plugin for testing playback of synthed files. +*/ +void PlugInConf::setPlayer(TestPlayer* player) { m_player = player; } +TestPlayer* PlugInConf::getPlayer() { return m_player; } + diff --git a/kttsd/libkttsd/pluginconf.h b/kttsd/libkttsd/pluginconf.h new file mode 100644 index 0000000..4c4647c --- /dev/null +++ b/kttsd/libkttsd/pluginconf.h @@ -0,0 +1,378 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the template for the configuration plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _PLUGINCONF_H_ +#define _PLUGINCONF_H_ + +// Qt includes. +#include <qwidget.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> +#include <kdemacros.h> +#include "kdeexportfix.h" + +// KTTS includes. +#include "testplayer.h" + +/** +* @interface PlugInConf +* +* pluginconf - the KDE Text-to-Speech Deamon Plugin Configuration API. +* +* @version 1.0 Draft 2 +* +* This class defines the interface that plugins to KTTSMGR must implement. +* +* @warning The pluginconf interface is still being developed and is likely +* to change in the future. +* +* A KTTSD Plugin interfaces between KTTSD and a speech engine. +* A PlugInConf provides an on-screen widget for configuring the plugin for use +* with KTTSD. +* +* @section guidelines General Guidelines +* +* - The configuration widget should be no larger than TODO pixels. +* - Do not supply Load, Save, Cancel, or OK buttons. Those are provided by KTTSMGR. +* - Try to supply a Test button so that users can test the configuration before +* saving it. +* - Your configuration widget will be running inside a KPart. +* - Whenever the user changes something in your on-screen widget, emit the +* @ref changed signal. +* - If a plugin can automatically configure itself, i.e., locate voice files, +* set default values, etc., it should do so when it is first added to KTTSMGR. +* +* @section multiinstance Multiple Instances +* +* If it is possible to run multiple simultaneous instances of your synthesis engine, +* return True from the @ref supportsMultiInstance method. The user will be able to +* configure multiple instances of your plugin, each with a different set of +* talker attributes. +* +* If you cannot run multiple simultaneous instances of your synthesis engine, +* or your plugin has a fixed set of talker attributes (only one language, voice, +* gender, volume, and speed), return False from @ref supportsMultiInstance. +* +* @section language Language Support +* +* Some plugins support only one language. For them, return the appropriate language +* code when @ref getSupportedLanguages is called. +* +* If your plugin can support multiple languages, your task is a little more +* complicated. The best way to handle this is to install a @e voices file with +* your plugin that lists all the supported languages, voice files, genders, etc. +* that are possible. When your plugin is added to KTTSMGR, +* @ref getSupportedLanguages will be called. Return a list of all the possible +* languages supported, even if the user hasn't yet picked a voice file in your +* configuration, or even told your where the voice files are. +* +* There are three ways that users and applications pick a language code for your +* plugin: +* - The user picks a code from among the languages you returned in +* @ref getSupportedLanguages, or +* - The user picks your plugin and uses your configuration widget to pick a voice +* file or other configuration option that determines the language, or +* - An application requests a plugin with specific language support. +* +* If possible, avoid making the user pick a language code in your plugin. +* +* In the first and third cases, the chosen language code will be passed to your +* plugin when @ref setDesiredLanguage is called. If you can satisfy this +* language code, good, but it is possible that once the user has begun +* configuring your plugin, you find that you cannot support the desired +* language. Perhaps a needed voice file is missing. That is OK. +* You'll inform KTTSMGR of the actual language code when KTTSMGR +* calls @ref getTalkerCode (see below). Note that you should not limit the +* users choices based on the @ref setDesiredLanguage. A user might start +* out configuring your plugin for one language, and then change his or her +* mind to a different language. +* +* Also note that language codes may also include an appended country code. +* For example, "en_GB" for British English. When @ref getSupportedLanguages is +* called, you should return as specific a list as possible. For example, +* if your plugin supports both American and British English, your returned +* list would include "en_GB" and "en_US". When @ref setDesiredLanguage is +* called, a country code may or may not be included. If included and your +* plugin supports the language, but not the specific country variant, +* your plugin should nevertheless attempt to satisfy the request, returning +* the actual supported language and country when @ref getTalkerCode is called. +* +* @section talkercodes Talker Codes +* +* Review the section on Talkers in kspeech.h. +* +* When your plugin is added to the KTTSMGR, @ref getSupportedLanguages +* will be called followed by @ref setDesiredLanguage and @ref load. +* Note that the configuration file will most likely be empty when +* @ref load is called. + +* Next, @ref getTalkerCode +* will be called. If your plugin can automatically configure itself to the desired +* language, it should do so and return a fully-specified talker code. If your +* plugin is not yet ready and requires user help, return QString::null. Note that +* @ref setDesiredLanguage may be Null, in which case, you should allow the +* user to configure your plugin to any of your supported languages. +* +* When your plugin has been configured enough to begin synthesis, return a +* fully-specified talker code in @ref getTalkerCode(). +* +* Here is guidance for what you should return for each of the talker attributes +* when @ref getTalkerCode is called: +* +* - @e lang. If user has completed configuring your plugin, i.e., it is +* ready to begin synthesizing, return the ISO 639-1 language code +* for the language it can synthesize. If your plugin is not yet +* fully configured, you should return QString::null for the entire +* talker code. If your plugin supports a specific national version +* of a language, that should also be included using the ISO 3166 +* country code separated from the language code by underscore (_). +* For example, if your plugin synthesizes American English, you would +* return "en_US". If British English, "en_BR". And if +* non-specific English, just "en". +* - @e synthesizer. The name of your plugin. Keep short, but long enough to +* distinquish different implementations. For example, +* Festival Int, Flite, Hadifix. Use only letters, numbers +* spaces, and underscores (_) in your plugin name. +* - @e gender. May be "male", "female", or "neutral". +* - @e name. The voice code. If your plugin does not support voices, +* return "fixed". +* - @e volume. May be "medium", "loud", or "soft". If your plugin does not support +* configurable volume, return "medium". +* - @e rate. May be "medium", "fast", or "slow". If your plugin does not support +* configurable speed, return "medium". +* +* The order of the attributes you return does not matter. Here is an example of +* a fully-specified talker code. +* +* lang="en" name="Kal" gender="male" volume="soft" rate="fast" +* synthesizer="Festival Interactive" +* +* Do not return translated values for the Talker Code attributes. All English. +* +* Each time your plugin emits the @ref changed signal, @ref getTalkerCode will be called. +* The configuration dialog OK button will be disabled until you return a non-null +* Talker Code. +* +* It is possible that your plugin does not know the language supported. The generic +* Command plugin is example of such a case, since the user enters an arbitrary command. +* In this case, return the value from the @ref setDesiredLanguage call. It is possible +* that @ref setDesiredLanguage is Null. That is OK. In this case, KTTSMGR will prompt +* the user for the language code. +* +* @section loadandsavemethods Load and Save Methods +* +* The @ref load and @ref save methods are called by KTTSMGR so that your plugin +* can load and save configuration options from the configuration file. +* These methods have two parameters, a @e config object and a @e configGroup string. +* +* Plugins that do not support multiple instances (return False from +* @ref supportsMultiInstance), should simply call config->setGroup(configGroup) +* before loading or saving their configuration. +* +* If your plugin supports multiple instances, it is slightly more complicated. +* Typically, there will be configuration options that apply to all instances +* of the plugin and there will be options that apply only to the specific +* configured instance of the plugin. To load or save the instance-specific +* options, call config->setGroup(configGroup). For options that apply +* to all instances of the plugin, call config->setGroup() with a group name +* that contains your plugin's name. For example, +* config->setGroup("Festival Defaults"). +* +* For example, when first added to KTTSMGR, the Festival plugin needs to know the path to +* the directory containing all the installed voice files. It is best for a plugin +* to try to locate these resources automatically, but if it can't find them, +* when the user has told it where they are, it is a good idea to save this information +* in the all-instances group. In this way, the next time the plugin +* is added to KTTSMGR, or another instance is added, it will be able to find them +* automatically. +* +* @ref setDesiredLanguage is always called just prior to @ref load, therefore +* it is not necessary to save the language code, unless your plugin needs it in +* order to synthesize speech. +*/ + +class KDE_EXPORT PlugInConf : public QWidget{ + Q_OBJECT + + public: + /** + * Constructor + */ + PlugInConf( QWidget *parent = 0, const char *name = 0); + + /** + * Destructor + */ + virtual ~PlugInConf(); + + /** + * This method is invoked whenever the module should read its + * configuration (most of the times from a config file) and update the + * user interface. This happens when the user clicks the "Reset" button in + * the control center, to undo all of his changes and restore the currently + * valid settings. Note that KTTSMGR calls this when the plugin is + * loaded, so it not necessary to call it in your constructor. + * The plugin should read its configuration from the specified group + * in the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * loading your configuration. + * + * When a plugin is first added to KTTSMGR, @e load will be called with + * a Null @e configGroup. In this case, the plugin will not have + * any instance-specific parameters to load, but it may still wish + * to load parameters that apply to all instances of the plugin. + * + * @see loadandsavemethods + */ + virtual void load(KConfig *config, const QString &configGroup); + + /** + * This function gets called when the user wants to save the settings in + * the user interface, updating the config files or wherever the + * configuration is stored. The method is called when the user clicks "Apply" + * or "Ok". The plugin should save its configuration in the specified + * group of the specified config file. + * @param config Pointer to a KConfig object. + * @param configGroup Call config->setGroup with this argument before + * saving your configuration. + * + * @ref setDesiredLanguage is always called just prior to @ref load, therefore + * it is not necessary to save the language code, unless your plugin needs it in + * order to synthesize speech. + */ + virtual void save(KConfig *config, const QString &configGroup); + + /** + * This function is called to set the settings in the module to sensible + * default values. It gets called when hitting the "Default" button. The + * default values should probably be the same as the ones the application + * uses when started without a config file. Note that defaults should + * be applied to the on-screen widgets; not to the config file. + */ + virtual void defaults(); + + /** + * Indicates whether the plugin supports multiple instances. Return + * False if only one instance of the plugin can run at a time, or + * if your plugin is limited to a single language, voice, gender, volume, + * and speed. + * @return True if multiple instances are possible. + */ + virtual bool supportsMultiInstance(); + + /** + * This function informs the plugin of the desired language to be spoken + * by the plugin. The plugin should attempt to adapt itself to the + * specified language code, choosing sensible defaults if necessary. + * If the passed-in code is QString::null, no specific language has + * been chosen. + * @param lang The desired language code or Null if none. + * + * If the plugin is unable to support the desired language, that is OK. + * Language codes are given by ISO 639-1 and are in lowercase. + * The code may also include an ISO 3166 country code in uppercase + * separated from the language code by underscore (_). For + * example, en_GB. If your plugin supports the given language, but + * not the given country, treat it as though the country + * code were not specified, i.e., adapt to the given language. + */ + virtual void setDesiredLanguage(const QString &lang); + + /** + * Return fully-specified talker code for the configured plugin. This code + * uniquely identifies the configured instance of the plugin and distinquishes + * one instance from another. If the plugin has not been fully configured, + * i.e., cannot yet synthesize, return QString::null. + * @return Fully-specified talker code. + */ + virtual QString getTalkerCode(); + + /** + * Return a list of all the languages possibly supported by the plugin. + * If your plugin can support any language, return Null. + * @return A QStringList of supported language and optional country + * codes, or Null if any. + * + * The languge codes are given in ISO 639-1. Lowercase should be used. + * If your plugin supports various national forms of a language, ISO 3166 + * country codes should also be include in upperase and separated from + * the language code with underscore (_). Examples: + * en + * en_US + * en_GB + * es + * es_CL + * The list you return should be as specific as practicable. + */ + virtual QStringList getSupportedLanguages(); + + /** + * Player object that can be used by the plugin for testing playback of synthed files. + */ + void setPlayer(TestPlayer* player); + TestPlayer* getPlayer(); + + static QString realFilePath(const QString &filename); + + static QString testMessage(const QString& languageCode); + + public slots: + /** + * This slot is used internally when the configuration is changed. It is + * typically connected to signals from the widgets of the configuration + * and should emit the @ref changed signal. + */ + void configChanged(){ + kdDebug() << "PlugInConf::configChanged: Running"<< endl; + emit changed(true); + }; + + signals: + /** + * This signal indicates that the configuration has been changed. + * It should be emitted whenever user changes something in the configuration widget. + */ + void changed(bool); + + protected: + /** + * Searches the $PATH variable for any file. If that file exists in the PATH, or + * is contained in any directory in the PATH, it returns the full path to it. + * @param name The name of the file to search for. + * @returns The path to the file on success, a blank QString + * if its not found. + */ + QString getLocation(const QString &name); + + /** + * Breaks a language code into the language code and country code (if any). + * @param languageCode Language code. + * @return countryCode Just the country code part (if any). + * @return Just the language code part. + */ + QString splitLanguageCode(const QString& languageCode, QString& countryCode); + + /// The system path in a QStringList. + QStringList m_path; + + TestPlayer* m_player; +}; + +#endif //_PLUGINCONF_H_ diff --git a/kttsd/libkttsd/pluginproc.cpp b/kttsd/libkttsd/pluginproc.cpp new file mode 100644 index 0000000..5c835a6 --- /dev/null +++ b/kttsd/libkttsd/pluginproc.cpp @@ -0,0 +1,292 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the template for the processing plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qstring.h> +#include <qtextcodec.h> + +// KDE includes. +#include <kdebug.h> +#include <kstandarddirs.h> +#include <klocale.h> + +// PlugInProc includes. +#include "pluginproc.h" +#include "pluginproc.moc" + +/** +* Constructor +*/ +PlugInProc::PlugInProc( QObject *parent, const char *name) : QObject(parent, name){ + // kdDebug() << "PlugInProc::PlugInProc: Running" << endl; +} + +/** +* Destructor +*/ +PlugInProc::~PlugInProc(){ + // kdDebug() << "PlugInProc::~PlugInProc: Running" << endl; +} + +/** +* Initializate the speech plugin. +*/ +bool PlugInProc::init(KConfig* /*config*/, const QString& /*configGroup*/){ + // kdDebug() << "PlugInProc::init: Running" << endl; + return false; +} + +/** +* Say a text. Synthesize and audibilize it. +* @param text The text to be spoken. +* +* If the plugin supports asynchronous operation, it should return immediately. +*/ +void PlugInProc::sayText(const QString& /*text*/){ + // kdDebug() << "PlugInProc::sayText: Running" << endl; +} + +/** +* Synthesize text into an audio file, but do not send to the audio device. +* @param text The text to be synthesized. +* @param suggestedFilename Full pathname of file to create. The plugin +* may ignore this parameter and choose its own +* filename. KTTSD will query the generated +* filename using getFilename(). +* +* If the plugin supports asynchronous operation, it should return immediately. +*/ +void PlugInProc::synthText(const QString& /*text*/, const QString& /*suggestedFilename*/) { } + +/** +* Get the generated audio filename from synthText. +* @return Name of the audio file the plugin generated. +* Null if no such file. +* +* The plugin must not re-use the filename. +*/ +QString PlugInProc::getFilename() { return QString::null; } + +/** +* Stop current operation (saying or synthesizing text). +* This function only makes sense in asynchronus modes. +* The plugin should return to the psIdle state. +*/ +void PlugInProc::stopText(){ + // kdDebug() << "PlugInProc::stopText: Running" << endl; +} + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @ref pluginState +*/ +pluginState PlugInProc::getState() { return psIdle; } + +/** +* Acknowledges a finished state and resets the plugin state to psIdle. +* +* If the plugin is not in state psFinished, nothing happens. +* The plugin may use this call to do any post-processing cleanup, +* for example, blanking the stored filename (but do not delete the file). +* Calling program should call getFilename prior to ackFinished. +*/ +void PlugInProc::ackFinished() { } + +/** +* Returns True if the plugin supports asynchronous processing, +* i.e., returns immediately from sayText or synthText. +* @return True if this plugin supports asynchronous processing. +*/ +bool PlugInProc::supportsAsync() { return false; } + +/** +* Returns True if the plugin supports synthText method, +* i.e., is able to synthesize text to a sound file without +* audibilizing the text. +* @return True if this plugin supports synthText method. +*/ +bool PlugInProc::supportsSynth() { return false; } + +/** +* Returns the name of an XSLT stylesheet that will convert a valid SSML file +* into a format that can be processed by the synth. For example, +* The Festival plugin returns a stylesheet that will convert SSML into +* SABLE. Any tags the synth cannot handle should be stripped (leaving +* their text contents though). The default stylesheet strips all +* tags and converts the file to plain text. +* @return Name of the XSLT file. +*/ +QString PlugInProc::getSsmlXsltFilename() +{ + return KGlobal::dirs()->resourceDirs("data").last() + "kttsd/xslt/SSMLtoPlainText.xsl"; +} + +/** +* Given the name of a codec, returns the QTextCodec for the name. +* Handles the following "special" codec names: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +* @param codecName Name of desired codec. +* @return The codec object. Calling program must not delete this object +* as it is a reference to an existing QTextCodec object. +* +* Caution: Do not pass translated codec names to this routine. +*/ +/*static*/ QTextCodec* PlugInProc::codecNameToCodec(const QString &codecName) +{ + QTextCodec* codec = 0; + if (codecName == "Local") + codec = QTextCodec::codecForLocale(); + else if (codecName == "Latin1") + codec = QTextCodec::codecForName("ISO8859-1"); + else if (codecName == "Unicode") + codec = QTextCodec::codecForName("utf16"); + else + codec = QTextCodec::codecForName(codecName.latin1()); + if (!codec) + { + kdDebug() << "PluginProc::codecNameToCodec: Invalid codec name " << codecName << endl; + kdDebug() << "PluginProc::codecNameToCodec: Defaulting to ISO 8859-1" << endl; + codec = QTextCodec::codecForName("ISO8859-1"); + } + return codec; +} + +/** +* Builds a list of codec names, suitable for display in a QComboBox. +* The list includes the 3 special codec names (translated) at the top: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +*/ +/*static*/ QStringList PlugInProc::buildCodecList() +{ + // kdDebug() << "PlugInConf::buildCodecList: Running" << endl; + QStringList codecList; + QString local = i18n("Local")+" ("; + local += QTextCodec::codecForLocale()->name(); + local += ")"; + codecList.append(local); + codecList.append(i18n("Latin1")); + codecList.append(i18n("Unicode")); + for (int i = 0; (QTextCodec::codecForIndex(i)); ++i ) + codecList.append(QTextCodec::codecForIndex(i)->name()); + return codecList; +} + +/** +* Given the name of a codec, returns index into the codec list. +* Handles the following "special" codec names: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +* @param codecName Name of the codec. +* @param codecList List of codec names. The first 3 entries may be translated names. +* @return QTextCodec object. Caller must not delete this object. +* +* Caution: Do not pass translated codec names to this routine in codecName parameter. +*/ +/*static*/ int PlugInProc::codecNameToListIndex(const QString &codecName, const QStringList &codecList) +{ + int codec; + if (codecName == "Local") + codec = PlugInProc::Local; + else if (codecName == "Latin1") + codec = PlugInProc::Latin1; + else if (codecName == "Unicode") + codec = PlugInProc::Unicode; + else { + codec = PlugInProc::Local; + const uint codecListCount = codecList.count(); + for (uint i = PlugInProc::UseCodec; i < codecListCount; ++i ) + if (codecName == codecList[i]) + codec = i; + } + return codec; +} + +/** +* Given index into codec list, returns the codec object. +* @param codecNum Index of the codec. +* @param codecList List of codec names. The first 3 entries may be translated names. +* @return QTextCodec object. Caller must not delete this object. +*/ +/*static*/ QTextCodec* PlugInProc::codecIndexToCodec(int codecNum, const QStringList &codecList) +{ + QTextCodec* codec = 0; + switch (codecNum) { + case PlugInProc::Local: + codec = QTextCodec::codecForLocale(); + break; + case PlugInProc::Latin1: + codec = QTextCodec::codecForName("ISO8859-1"); + break; + case PlugInProc::Unicode: + codec = QTextCodec::codecForName("utf16"); + break; + default: + codec = QTextCodec::codecForName(codecList[codecNum].latin1()); + break; + } + if (!codec) + { + kdDebug() << "PlugInProc::codecIndexToCodec: Invalid codec index " << codecNum << endl; + kdDebug() << "PlugInProc::codecIndexToCodec: Defaulting to ISO 8859-1" << endl; + codec = QTextCodec::codecForName("ISO8859-1"); + } + return codec; +} + +/** +* Given index into codec list, returns the codec Name. +* Handles the following "special" codec names: +* Local The user's current Locale codec. +* Latin1 Latin1 (ISO 8859-1) +* Unicode UTF-16 +* @param codecNum Index of the codec. +* @param codecList List of codec names. The first 3 entries may be translated names. +* @return Untranslated name of the codec. +*/ +/*static*/ QString PlugInProc::codecIndexToCodecName(int codecNum, const QStringList &codecList) +{ + QString codecName; + switch (codecNum) { + case PlugInProc::Local: + codecName = "Local"; + break; + case PlugInProc::Latin1: + codecName = "Latin1"; + break; + case PlugInProc::Unicode: + codecName = "Unicode"; + break; + default: + if ((uint)codecNum < codecList.count()) + codecName = codecList[codecNum]; + else + { + kdDebug() << "PlugInProc::codecIndexToCodec: Invalid codec index " << codecNum << endl; + kdDebug() << "PlugInProc::codecIndexToCodec: Defaulting to ISO 8859-1" << endl; + codecName = "ISO8859-1"; + } + } + return codecName; +} diff --git a/kttsd/libkttsd/pluginproc.h b/kttsd/libkttsd/pluginproc.h new file mode 100644 index 0000000..c271ad3 --- /dev/null +++ b/kttsd/libkttsd/pluginproc.h @@ -0,0 +1,466 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + This file is the template for the processing plug ins. + ------------------- + Copyright : (C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández + Copyright : (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org> + Current Maintainer: Gary Cramblitt <garycramblitt@comcast.net> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _PLUGINPROC_H_ +#define _PLUGINPROC_H_ + +#include <qobject.h> +#include <qstring.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include <kconfig.h> + +/** +* @interface PlugInProc +* +* pluginproc - the KDE Text-to-Speech Deamon Plugin API. +* +* @version 1.0 Draft 1 +* +* This class defines the interface that plugins to KTTSD must implement. +* +* @warning The pluginproc interface is still being developed and is likely +* to change in the future. +* +* A KTTSD Plugin interfaces between KTTSD and a speech engine. It provides the methods +* used by KTTSD to synthesize and/or audibilize text into speech. +* +* @section Goals +* +* The ideal plugin has the following features (listed most important to least important): +* - It can synthesize text into an audio file without sending the audio to +* the audio device. If the plugin can do this, the @ref supportsSynth function +* should return True. +* - When @ref stopText is called, is able to immediately stop an in-progress +* synthesis or saying operation. +* - It can operate asynchronously, i.e., returns immediately from a +* @ref sayText or @ref synthText call and emits signals @ref sayFinished or +* @ref synthFinished when completed. If the plugin can do this, the @ref supportsAsync +* function should return True. +* +* As a plugin author, your goal is to provide all 3 of these features. However, +* the speech engine you are working with might not be able to support all three +* features. +* +* If a plugin cannot do all 3 of the features above, the next best combinations +* are (from best to worst): +* +* - @ref supportsSynth returns True, @ref supportsAsync returns False, and +* @stopText is able to immediately stop synthesis. +* - @ref supportsSynth returns True, @ref supportsAsync returns False, and +* @stopText returns immediately without stopping synthesis. +* - @ref supportsAsync returns True, @ref supportsSynth returns False, and +* @ref stopText is able to immediately stop saying. +* - Both @ref supportsSynth and @ref supportsAsync both return False, and +* @ref stopText is able to immediately stop saying. +* - @ref supportsAsync returns True, @ref supportsSynth returns False, and +* @ref stopText returns immediately without stopping saying. +* - Both @ref supportsSynth and @ref supportsAsync both return False, and +* @ref stopText returns immediately without stopping saying. +* +* Notice that aynchronous support is not essential because KTTSD is able to +* provide aynchronous support by running the plugin in a separate thread. +* The ability to immediately stop audio output (or support separate synthesis +* only) is more important. +* +* @section Minimum Implementations +* +* All plugins should implement @ref init in order to initialize the speech engine, +* set language codes, etc. +* +* If @ref supportsSynth return False, a plugin must implement @ref sayText . +* +* If @ref supportsSynth returns True, a plugin must implement the following methods: +* - @ref synthText +* - @ref getFilename +* - @ref ackFinished +* The plugin need not implement @ref sayText . +* +* If @ref supportsAsync returns True, the plugin must implement @ref getState . +* +* If @ref supportsAsync returns True, and @ref supportsSynth returns True, +* a plugin must emit @ref synthFinished signal when synthesis is completed. +* +* If @ref supportsAsync returns True, and @ref supportsSynth returns False, +* a plugin must emit @ref sayFinished signal when saying is completed. +* +* If @ref supportsAsync returns False, do not emit signals @ref sayFinished +* or @ref synthFinished . +* +* @section Implementation Guidelines +* +* In no case, will a plugin need to perform more than one @ref sayText or +* @ref synthText at a time. In other words, in asynchronous mode, KTTSD will +* not call @ref sayText or @ref synthText again until @ref @getState returns +* psFinished. +* +* If @ref supportsAsync returns False, KTTSD will run the plugin in a separate +* QThread. As a consequence, the plugin must not make use of the KDE Library, +* when @ref sayText or @ref synthText is called, +* with the exception of KProcess and family (KProcIO, KShellProcess). +* This restriction comes about because the KDE Libraries make use of the +* main Qt event loop, which unfortunately, runs only in the main thread. +* This restriction will likely be lifted in Qt 4 and later. +* +* Since the KDE library is not available from the @ref sayText and @ref synthText methods, +* it is best if the plugin reads configuration settings in the @ref init method. +* The KConfig object is passed as an argument to @ref init . +* +* If the synthesis engine requires a long initialization time (more than a second), +* it is best if the plugin loads the speech engine from the @ref init method. +* Otherwise, it will be more memory efficient to wait until @ref sayText or +* @ref synthText is called, because it is possible that the plugin will be created +* and initialized, but never used. +* +* All plugins, whether @ref supportsAsync returns True or not, should try to +* implement @ref stopText . If a plugin returns False from @ref supportsAsync, +* @ref stopText will be called from the main thread, while @ref sayText and/or +* @ref synthText will be called from a separate thread. Hence, it will be +* possible for @ref stopText to be called while @ref sayText or @ref synthText is +* running. Keep this in mind when implementing the code. +* +* If the plugin returns True from @ref supportsAsync, you will of course +* need to deal with similar issues. If you have to use QThreads +* to implement asynchronous support, do not be concerned about emitting +* the @ref sayFinished or @ref synthFinished signals from your threads, since +* KTTSD will convert the received signals into postEvents and +* return immediately. +* +* If it is not possible for @ref stopText to stop an in-progress operation, it +* must not wait for the operation to complete, since this would block KTTSD. +* Instead, simply return immediately. Usually, KTTSD will perform other operations +* while waiting for the plugin to complete its operation. (See threadedplugin.cpp.) +* +* If the @ref stopText implementation returns before the operation has actually +* completed, it must emit the @ref stopped() signal when it is actually completed. +* +* If a plugin returns True from @ref supportsAsync, and @ref stopText is called, +* when the plugin has stopped or completed the operation, it must return psIdle +* on the next call to @ref getState ; not psFinished. The following state diagram +* might be helpful to understand this: +* + @verbatim + psIdle <<---------------------------------------------------------- + / \ ^ + psSaying psSynthing --- stopText called and operation completed -->> ^ + \ / ^ + psFinished --- ackFinished called ------------------------------->> ^ + @endverbatim +* +* If your plugin can't immediately stop an in-progress operation, the easiest +* way to handle this is to set a flag when stopText is called, and then in your +* getState() implementation, if the operation has completed, change the +* psFinished state to psIdle, if the flag is set. See the flite plugin for +* example code. +* +* If a plugin returns True from @ref supportsSynth, KTTSD will pass a suggested +* filename in the @ref synthText call. The plugin should synthesize the text +* into an audio file with the suggested name. If the synthesis engine does not +* permit this, i.e., it will pick a filename of its own, that is OK. In either +* case, the actual filename produced should be returned in @ref getFilename . +* In no case may the plugin re-use this filename once @ref getFilename has been +* called. If for some reason the synthesis engine cannot support this, the +* plugin should copy the file to the suggested filename. The file must not be +* locked when @ref getFilename is called. The file will be deleted when +* KTTSD is finished using it. +* +* The preferred audio file format is wave, since this is the only format +* guaranteed to be supported by KDE (aRts). Other formats may or may not be +* supported on a user's machine. +* +* The plugin destructor should take care of terminating the speech engine. +* +* @section Error-handling Error Handling +* +* Plugins may emit the @ref error signal when an error occurs. +* +* When an error occurs, plugins should attempt to recover as best they can and +* continue accepting @ref sayText or @ref synthText calls. For example, +* if a speech engine emits an error in response to certain characters embedded +* in synthesizing text, the plugin should discard the text and +* emit signal @ref error with True as the first argument and the speech +* engine's error message as the second argument. The plugin should then +* treat the operation as a completed operation, i.e., return psFinished when +* @ref getState is called. +* +* If the speech engine crashes, the plugin should emit signal @ref error with +* True as the first argument and then attempt to restart the speech engine. +* The plugin will need to implement some protection against an infinite +* restart loop and emit the @ref error signal with False as the first argument +* if this occurs. +* +* If a plugin emits the @ref error signal with False as the first argument, +* KTTSD will no longer call the plugin. +* +* @section PlugInConf +* +* The plugin should implement a configuration dialog where the user can specify +* options specific to the speech engine. This dialog is displayed in the KDE +* Control Center and also in the KTTS Manager (kttsmgr). See pluginconf.h. +* +* If the user changes any of the settings while the plugin is created, +* the plugin will be destroyed and re-created. +*/ + +class QTextCodec; + +enum pluginState +{ + psIdle = 0, /**< Plugin is not doing anything. */ + psSaying = 1, /**< Plugin is synthesizing and audibilizing. */ + psSynthing = 2, /**< Plugin is synthesizing. */ + psFinished = 3 /**< Plugin has finished synthesizing. Audio file is ready. */ +}; + +class KDE_EXPORT PlugInProc : virtual public QObject{ + Q_OBJECT + + public: + enum CharacterCodec { + Local = 0, + Latin1 = 1, + Unicode = 2, + UseCodec = 3 + }; + + /** + * Constructor. + */ + PlugInProc( QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + * Plugin must terminate the speech engine. + */ + virtual ~PlugInProc(); + + /** + * Initialize the speech engine. + * @param config Settings object. + * @param configGroup Settings Group. + * + * Sample code for reading configuration: + * + @verbatim + config->setGroup(configGroup); + m_fliteExePath = config->readEntry("FliteExePath", "flite"); + kdDebug() << "FliteProc::init: path to flite: " << m_fliteExePath << endl; + config->setGroup(configGroup); + @endverbatim + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Say a text. Synthesize and audibilize it. + * @param text The text to be spoken. + * + * If the plugin supports asynchronous operation, it should return immediately + * and emit sayFinished signal when synthesis and audibilizing is finished. + * It must also implement the @ref getState method, which must return + * psFinished, when saying is completed. + */ + virtual void sayText(const QString &text); + + /** + * Synthesize text into an audio file, but do not send to the audio device. + * @param text The text to be synthesized. + * @param suggestedFilename Full pathname of file to create. The plugin + * may ignore this parameter and choose its own + * filename. KTTSD will query the generated + * filename using getFilename(). + * + * If the plugin supports asynchronous operation, it should return immediately + * and emit @ref synthFinished signal when synthesis is completed. + * It must also implement the @ref getState method, which must return + * psFinished, when synthesis is completed. + */ + virtual void synthText(const QString &text, const QString &suggestedFilename); + + /** + * Get the generated audio filename from call to @ref synthText. + * @return Name of the audio file the plugin generated. + * Null if no such file. + * + * The plugin must not re-use or delete the filename. The file may not + * be locked when this method is called. The file will be deleted when + * KTTSD is finished using it. + */ + virtual QString getFilename(); + + /** + * Stop current operation (saying or synthesizing text). + * Important: This function may be called from a thread different from the + * one that called sayText or synthText. + * If the plugin cannot stop an in-progress @ref sayText or + * @ref synthText operation, it must not block waiting for it to complete. + * Instead, return immediately. + * + * If a plugin returns before the operation has actually been stopped, + * the plugin must emit the @ref stopped signal when the operation has + * actually stopped. + * + * The plugin should change to the psIdle state after stopping the + * operation. + */ + virtual void stopText(); + + /** + * Return the current state of the plugin. + * This function only makes sense in asynchronous mode. + * @return The pluginState of the plugin. + * + * @see pluginState + */ + virtual pluginState getState(); + + /** + * Acknowledges a finished state and resets the plugin state to psIdle. + * + * If the plugin is not in state psFinished, nothing happens. + * The plugin may use this call to do any post-processing cleanup, + * for example, blanking the stored filename (but do not delete the file). + * Calling program should call getFilename prior to ackFinished. + */ + virtual void ackFinished(); + + /** + * Returns True if the plugin supports asynchronous processing, + * i.e., returns immediately from sayText or synthText. + * @return True if this plugin supports asynchronous processing. + * + * If the plugin returns True, it must also implement @ref getState . + * It must also emit @ref sayFinished or @ref synthFinished signals when + * saying or synthesis is completed. + */ + virtual bool supportsAsync(); + + /** + * Returns True if the plugin supports synthText method, + * i.e., is able to synthesize text to a sound file without + * audibilizing the text. + * @return True if this plugin supports synthText method. + * + * If the plugin returns True, it must also implement the following methods: + * - @ref synthText + * - @ref getFilename + * - @ref ackFinished + * + * If the plugin returns True, it need not implement @ref sayText . + */ + virtual bool supportsSynth(); + + /** + * Returns the name of an XSLT stylesheet that will convert a valid SSML file + * into a format that can be processed by the synth. For example, + * The Festival plugin returns a stylesheet that will convert SSML into + * SABLE. Any tags the synth cannot handle should be stripped (leaving + * their text contents though). The default stylesheet strips all + * tags and converts the file to plain text. + * @return Name of the XSLT file. + */ + virtual QString getSsmlXsltFilename(); + + /** + * Given the name of a codec, returns the QTextCodec for the name. + * Handles the following "special" codec names: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + * @param codecName Name of desired codec. + * @return The codec object. Calling program must not delete this object + * as it is a reference to an existing QTextCodec object. + * + * Caution: Do not pass translated codec names to this routine. + */ + static QTextCodec* codecNameToCodec(const QString &codecName); + + /** + * Builds a list of codec names, suitable for display in a QComboBox. + * The list includes the 3 special codec names (translated) at the top: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + */ + static QStringList buildCodecList(); + + /** + * Given the name of a codec, returns index into the codec list. + * Handles the following "special" codec names: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + * @param codecName Name of the codec. + * @param codecList List of codec names. The first 3 entries may be translated names. + * @return QTextCodec object. Caller must not delete this object. + * + * Caution: Do not pass translated codec names to this routine in codecName parameter. + */ + static int codecNameToListIndex(const QString &codecName, const QStringList &codecList); + + /** + * Given index into codec list, returns the codec object. + * @param codecNum Index of the codec. + * @param codecList List of codec names. The first 3 entries may be translated names. + * @return QTextCodec object. Caller must not delete this object. + */ + static QTextCodec* codecIndexToCodec(int codecNum, const QStringList &codecList); + + /** + * Given index into codec list, returns the codec Name. + * Handles the following "special" codec names: + * Local The user's current Locale codec. + * Latin1 Latin1 (ISO 8859-1) + * Unicode UTF-16 + * @param codecNum Index of the codec. + * @param codecList List of codec names. The first 3 entries may be translated names. + * @return Untranslated name of the codec. + */ + static QString codecIndexToCodecName(int codecNum, const QStringList &codecList); + + signals: + /** + * Emitted when synthText() finishes and plugin supports asynchronous mode. + */ + void synthFinished(); + /** + * Emitted when sayText() finishes and plugin supports asynchronous mode. + */ + void sayFinished(); + /** + * Emitted when stopText() has been called and plugin stops asynchronously. + */ + void stopped(); + /** + * Emitted if an error occurs. + * @param keepGoing True if the plugin can continue processing. + * False if the plugin cannot continue, for example, + * the speech engine could not be started. + * @param msg Error message. + * + * When an error occurs, plugins should attempt to recover as best they can + * and continue accepting @ref sayText or @ref synthText calls. For example, + * if the speech engine emits an error while synthesizing text, the plugin + * should return True along with error message. + * + * @see Error-handling + * + */ + void error(bool keepGoing, const QString &msg); +}; + +#endif // _PLUGINPROC_H_ diff --git a/kttsd/libkttsd/selecttalkerdlg.cpp b/kttsd/libkttsd/selecttalkerdlg.cpp new file mode 100644 index 0000000..d74339e --- /dev/null +++ b/kttsd/libkttsd/selecttalkerdlg.cpp @@ -0,0 +1,365 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + A dialog for user to select a Talker, either by specifying + selected Talker attributes, or by specifying all attributes + of an existing configured Talker. + + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qcheckbox.h> +#include <qradiobutton.h> +#include <qhbox.h> +#include <qgroupbox.h> + +// KDE includes. +#include <kcombobox.h> +#include <ktrader.h> +#include <kpushbutton.h> +#include <klistview.h> +#include <klineedit.h> +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "utils.h" +#include "selecttalkerdlg.h" +#include "selecttalkerdlg.moc" + +SelectTalkerDlg::SelectTalkerDlg( + QWidget* parent, + const char* name, + const QString& caption, + const QString& talkerCode, + bool runningTalkers) : + + KDialogBase( + parent, + name, + true, + caption, + KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Ok) +{ + m_widget = new SelectTalkerWidget( this ); + // TODO: How do I do this in a general way and still get KDialogBase to properly resize? + m_widget->setMinimumSize( QSize(700,500) ); + // setInitialSize( QSize(700,600) ); + setMainWidget( m_widget ); + m_runningTalkers = runningTalkers; + m_talkerCode = TalkerCode( talkerCode, false ); + + // Fill combo boxes. + KComboBox* cb = m_widget->genderComboBox; + cb->insertItem( QString::null ); + cb->insertItem( TalkerCode::translatedGender("male") ); + cb->insertItem( TalkerCode::translatedGender("female") ); + cb->insertItem( TalkerCode::translatedGender("neutral") ); + + cb = m_widget->volumeComboBox; + cb->insertItem( QString::null ); + cb->insertItem( TalkerCode::translatedVolume("medium") ); + cb->insertItem( TalkerCode::translatedVolume("loud") ); + cb->insertItem( TalkerCode::translatedVolume("soft") ); + + cb = m_widget->rateComboBox; + cb->insertItem( QString::null ); + cb->insertItem( TalkerCode::translatedRate("medium") ); + cb->insertItem( TalkerCode::translatedRate("fast") ); + cb->insertItem( TalkerCode::translatedRate("slow") ); + + cb = m_widget->synthComboBox; + cb->insertItem( QString::null ); + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin"); + for(unsigned int i=0; i < offers.count() ; ++i) + cb->insertItem(offers[i]->name()); + + // Fill List View with list of Talkers. + m_widget->talkersListView->setSorting( -1 ); + loadTalkers( m_runningTalkers ); + + // Set initial radio button state. + if ( talkerCode.isEmpty() ) + m_widget->useDefaultRadioButton->setChecked(true); + else + { + QString dummy; + if (talkerCode == TalkerCode::normalizeTalkerCode(talkerCode, dummy)) + m_widget->useSpecificTalkerRadioButton->setChecked(true); + else + m_widget->useClosestMatchRadioButton->setChecked(true); + } + + applyTalkerCodeToControls(); + enableDisableControls(); + + connect(m_widget->useDefaultRadioButton, SIGNAL(clicked()), + this, SLOT(configChanged())); + connect(m_widget->useClosestMatchRadioButton, SIGNAL(clicked()), + this, SLOT(configChanged())); + connect(m_widget->useSpecificTalkerRadioButton, SIGNAL(clicked()), + this, SLOT(configChanged())); + + connect(m_widget->languageBrowseButton, SIGNAL(clicked()), + this, SLOT(slotLanguageBrowseButton_clicked())); + + connect(m_widget->synthComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->genderComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->volumeComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->rateComboBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + + connect(m_widget->synthCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->genderCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->volumeCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + connect(m_widget->rateCheckBox, SIGNAL(toggled(bool)), + this, SLOT(configChanged())); + + connect(m_widget->talkersListView, SIGNAL(selectionChanged()), + this, SLOT(slotTalkersListView_selectionChanged())); + + m_widget->talkersListView->setMinimumHeight( 120 ); +} + +SelectTalkerDlg::~SelectTalkerDlg() { } + +QString SelectTalkerDlg::getSelectedTalkerCode() +{ + return m_talkerCode.getTalkerCode(); +} + +QString SelectTalkerDlg::getSelectedTranslatedDescription() +{ + return m_talkerCode.getTranslatedDescription(); +} + +void SelectTalkerDlg::slotLanguageBrowseButton_clicked() +{ + // Create a QHBox to host KListView. + QHBox* hBox = new QHBox(m_widget, "SelectLanguage_hbox"); + // Create a KListView and fill with all known languages. + KListView* langLView = new KListView(hBox, "SelectLanguage_lview"); + langLView->addColumn(i18n("Language")); + langLView->addColumn(i18n("Code")); + langLView->setSelectionMode(QListView::Single); + QStringList allLocales = KGlobal::locale()->allLanguagesTwoAlpha(); + QString locale; + QString language; + // Blank line so user can select no language. + // Note: Don't use QString::null, which gets displayed at bottom of list, rather than top. + QListViewItem* item = new KListViewItem(langLView, "", ""); + if (m_talkerCode.languageCode().isEmpty()) item->setSelected(true); + int allLocalesCount = allLocales.count(); + for (int ndx=0; ndx < allLocalesCount; ++ndx) + { + locale = allLocales[ndx]; + language = TalkerCode::languageCodeToLanguage(locale); + item = new KListViewItem(langLView, language, locale); + if (m_talkerCode.fullLanguageCode() == locale) item->setSelected(true); + } + // Sort by language. + langLView->setSorting(0); + langLView->sort(); + // Display the box in a dialog. + KDialogBase* dlg = new KDialogBase( + KDialogBase::Swallow, + i18n("Select Languages"), + KDialogBase::Help|KDialogBase::Ok|KDialogBase::Cancel, + KDialogBase::Cancel, + m_widget, + "SelectLanguage_dlg", + true, + true); + dlg->setMainWidget(hBox); + dlg->setHelp("", "kttsd"); + dlg->setInitialSize(QSize(300, 500), false); + // TODO: This isn't working. Furthermore, item appears selected but is not. + langLView->ensureItemVisible(langLView->selectedItem()); + int dlgResult = dlg->exec(); + language = QString::null; + if (dlgResult == QDialog::Accepted) + { + if (langLView->selectedItem()) + { + language = langLView->selectedItem()->text(0); + m_talkerCode.setFullLanguageCode( langLView->selectedItem()->text(1) ); + } + } + delete dlg; + m_widget->languageLineEdit->setText(language); + m_widget->languageCheckBox->setChecked( !language.isEmpty() ); + configChanged(); +} + +void SelectTalkerDlg::slotTalkersListView_selectionChanged() +{ + QListViewItem* item = m_widget->talkersListView->selectedItem(); + if ( !item ) return; + if (!m_widget->useSpecificTalkerRadioButton->isChecked()) return; + configChanged(); +} + +void SelectTalkerDlg::configChanged() +{ + applyControlsToTalkerCode(); + applyTalkerCodeToControls(); + enableDisableControls(); +} + +void SelectTalkerDlg::applyTalkerCodeToControls() +{ + bool preferred = false; + QString code = m_talkerCode.getTalkerCode(); + + // TODO: Need to display translated Synth names. + KttsUtils::setCbItemFromText(m_widget->synthComboBox, + TalkerCode::stripPrefer( m_talkerCode.plugInName(), preferred) ); + m_widget->synthCheckBox->setEnabled( !m_talkerCode.plugInName().isEmpty() ); + m_widget->synthCheckBox->setChecked( preferred ); + + KttsUtils::setCbItemFromText(m_widget->genderComboBox, + TalkerCode::translatedGender( TalkerCode::stripPrefer( m_talkerCode.gender(), preferred ) ) ); + m_widget->genderCheckBox->setEnabled( !m_talkerCode.gender().isEmpty() ); + m_widget->genderCheckBox->setChecked( preferred ); + + KttsUtils::setCbItemFromText(m_widget->volumeComboBox, + TalkerCode::translatedVolume( TalkerCode::stripPrefer( m_talkerCode.volume(), preferred ) ) ); + m_widget->volumeCheckBox->setEnabled( !m_talkerCode.volume().isEmpty() ); + m_widget->volumeCheckBox->setChecked( preferred ); + + KttsUtils::setCbItemFromText(m_widget->rateComboBox, + TalkerCode::translatedRate( TalkerCode::stripPrefer( m_talkerCode.rate(), preferred ) ) ); + m_widget->rateCheckBox->setEnabled( !m_talkerCode.rate().isEmpty() ); + m_widget->rateCheckBox->setChecked( preferred ); + + // Select closest matching specific Talker. + int talkerIndex = TalkerCode::findClosestMatchingTalker(m_talkers, m_talkerCode.getTalkerCode(), false); + KListView* lv = m_widget->talkersListView; + QListViewItem* item = lv->firstChild(); + if ( item ) + { + while ( talkerIndex > 0 ) + { + item = item->nextSibling(); + --talkerIndex; + } + lv->setSelected( item, true ); + } +} + +void SelectTalkerDlg::applyControlsToTalkerCode() +{ + if ( m_widget->useDefaultRadioButton->isChecked() ) + m_talkerCode = TalkerCode(QString::null, false); + else if ( m_widget->useClosestMatchRadioButton->isChecked() ) + { + // Language already stored in talker code. + + QString t = m_widget->synthComboBox->currentText(); + if ( !t.isEmpty() && m_widget->synthCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setPlugInName( t ); + + t = TalkerCode::untranslatedGender( m_widget->genderComboBox->currentText() ); + if ( !t.isEmpty() && m_widget->genderCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setGender( t ); + + t = TalkerCode::untranslatedVolume( m_widget->volumeComboBox->currentText() ); + if ( !t.isEmpty() && m_widget->volumeCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setVolume( t ); + + t = TalkerCode::untranslatedRate( m_widget->rateComboBox->currentText() ); + if ( !t.isEmpty() && m_widget->rateCheckBox->isChecked() ) t.prepend("*"); + m_talkerCode.setRate( t ); + } + else if (m_widget->useSpecificTalkerRadioButton->isChecked() ) + { + QListViewItem* item = m_widget->talkersListView->selectedItem(); + if ( item ) + { + int itemIndex = -1; + while ( item ) + { + item = item->itemAbove(); + itemIndex++; + } + m_talkerCode = TalkerCode( &(m_talkers[itemIndex]), false ); + } + } +} + +void SelectTalkerDlg::loadTalkers(bool /*runningTalkers*/) +{ + m_talkers.clear(); + KListView* lv = m_widget->talkersListView; + lv->clear(); + QListViewItem* item; + KConfig* config = new KConfig("kttsdrc"); + config->setGroup("General"); + QStringList talkerIDsList = config->readListEntry("TalkerIDs", ','); + if (!talkerIDsList.isEmpty()) + { + QStringList::ConstIterator itEnd(talkerIDsList.constEnd()); + for( QStringList::ConstIterator it = talkerIDsList.constBegin(); it != itEnd; ++it ) + { + QString talkerID = *it; + config->setGroup("Talker_" + talkerID); + QString talkerCode = config->readEntry("TalkerCode", QString::null); + // Parse and normalize the talker code. + TalkerCode talker = TalkerCode(talkerCode, true); + m_talkers.append(talker); + QString desktopEntryName = config->readEntry("DesktopEntryName", QString::null); + QString synthName = TalkerCode::TalkerDesktopEntryNameToName(desktopEntryName); + // Display in List View using translated strings. + item = new KListViewItem(lv, item); + QString fullLanguageCode = talker.fullLanguageCode(); + QString language = TalkerCode::languageCodeToLanguage(fullLanguageCode); + item->setText(tlvcLanguage, language); + // Don't update the Synthesizer name with plugInName. The former is a translated + // name; the latter an English name. + // if (!plugInName.isEmpty()) talkerItem->setText(tlvcSynthName, plugInName); + if (!synthName.isEmpty()) + item->setText(tlvcSynthName, synthName); + if (!talker.voice().isEmpty()) + item->setText(tlvcVoice, talker.voice()); + if (!talker.gender().isEmpty()) + item->setText(tlvcGender, TalkerCode::translatedGender(talker.gender())); + if (!talker.volume().isEmpty()) + item->setText(tlvcVolume, TalkerCode::translatedVolume(talker.volume())); + if (!talker.rate().isEmpty()) + item->setText(tlvcRate, TalkerCode::translatedRate(talker.rate())); + } + } + delete config; +} + +void SelectTalkerDlg::enableDisableControls() +{ + bool enableClosest = ( m_widget->useClosestMatchRadioButton->isChecked() ); + bool enableSpecific = ( m_widget->useSpecificTalkerRadioButton->isChecked() ); + m_widget->closestMatchGroupBox->setEnabled( enableClosest ); + m_widget->talkersListView->setEnabled( enableSpecific ); +} diff --git a/kttsd/libkttsd/selecttalkerdlg.h b/kttsd/libkttsd/selecttalkerdlg.h new file mode 100644 index 0000000..bc236e2 --- /dev/null +++ b/kttsd/libkttsd/selecttalkerdlg.h @@ -0,0 +1,110 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + A dialog for user to select a Talker, either by specifying + selected Talker attributes, or by specifying all attributes + of an existing configured Talker. + + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _SELECTTALKERDLG_H_ +#define _SELECTTALKERDLG_H_ + +// Qt includes. +#include <qvaluelist.h> + +// KDE includes. +#include <kdialogbase.h> +#include <klocale.h> +#include <kdemacros.h> + +// KTTS includes. +#include "talkercode.h" +#include "selecttalkerwidget.h" + +class KDE_EXPORT SelectTalkerDlg : public KDialogBase +{ + Q_OBJECT + + public: + /** + * Constructor. + * @param parent The parent for this dialog. + * @param name Name for this dialog. + * @param caption Displayed title for this dialog. + * @param talkerCode A suggested starting Talker Code. + * @param runningTalkers If true, lists configured and Applied Talkers in the running + * KTTSD. If false, lists Talkers in the KTTSMgr Talker tab + * (which may not yet have been Applied). + */ + SelectTalkerDlg( + QWidget* parent = 0, + const char* name = "selecttalkerdialog", + const QString& caption = i18n("Select Talker"), + const QString& talkerCode = QString::null, + bool runningTalkers = false); + + /** + * Destructor. + */ + ~SelectTalkerDlg(); + + /** + * Returns the Talker Code user chose. QString::null if default Talker chosen. + * Note that if user did not choose a specific Talker, this will be a partial Talker Code. + */ + QString getSelectedTalkerCode(); + /** + * Returns the Talker user chose in a translated displayable format. + */ + QString getSelectedTranslatedDescription(); + + private slots: + void slotLanguageBrowseButton_clicked(); + void slotTalkersListView_selectionChanged(); + void configChanged(); + + private: + enum TalkerListViewColumn + { + tlvcLanguage, + tlvcSynthName, + tlvcVoice, + tlvcGender, + tlvcVolume, + tlvcRate + }; + + void applyTalkerCodeToControls(); + void applyControlsToTalkerCode(); + void loadTalkers(bool runningTalkers); + void enableDisableControls(); + + // Main dialog widget. + SelectTalkerWidget* m_widget; + // True if list of Talkers should be taken from config file. + bool m_runningTalkers; + // Current Talker Code. + TalkerCode m_talkerCode; + // List of parsed talker codes for the configured Talkers. + TalkerCode::TalkerCodeList m_talkers; +}; + +#endif // _SELECTTALKERDLG_H_ diff --git a/kttsd/libkttsd/selecttalkerwidget.ui b/kttsd/libkttsd/selecttalkerwidget.ui new file mode 100644 index 0000000..adcc8a9 --- /dev/null +++ b/kttsd/libkttsd/selecttalkerwidget.ui @@ -0,0 +1,572 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>SelectTalkerWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>SelectTalkerWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>558</width> + <height>447</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Select Talker</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>buttonGroup1</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QRadioButton"> + <property name="name"> + <cstring>useDefaultRadioButton</cstring> + </property> + <property name="text"> + <string>&Use default Talker</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, will use the default Talker, which is the topmost Talker listed in the Talkers tab.</string> + </property> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>useClosestMatchRadioButton</cstring> + </property> + <property name="text"> + <string>Use closest &matching Talker having</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, will use a configured Talker most closely matching the attributes you choose. Attributes with checks next to them will be preferred over unchecked attributes. Language is always preferred.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer15</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QGroupBox"> + <property name="name"> + <cstring>closestMatchGroupBox</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="title"> + <string></string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLabel" row="3" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>preferredLabel</cstring> + </property> + <property name="text"> + <string>Checked items are preferred over unchecked items.</string> + </property> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout11_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>synthLabel</cstring> + </property> + <property name="text"> + <string>&Synthesizer:</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter|AlignLeft</set> + </property> + <property name="buddy" stdset="0"> + <cstring>synthComboBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>synthComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>synthCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="0" column="2"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>genderLabel</cstring> + </property> + <property name="text"> + <string>&Gender:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>genderCheckBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>genderComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>genderCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="Line" row="0" column="1" rowspan="2" colspan="1"> + <property name="name"> + <cstring>line1</cstring> + </property> + <property name="frameShape"> + <enum>VLine</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + <property name="lineWidth"> + <number>2</number> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>volumeLabel</cstring> + </property> + <property name="text"> + <string>&Volume:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>volumeCheckBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>volumeComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>volumeCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="2"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>rateLabel</cstring> + </property> + <property name="text"> + <string>&Rate:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>rateCheckBox</cstring> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>rateComboBox</cstring> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>rateCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="2" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>languageLabel</cstring> + </property> + <property name="text"> + <string>&Language:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>languageLineEdit</cstring> + </property> + </widget> + <widget class="KLineEdit"> + <property name="name"> + <cstring>languageLineEdit</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="paletteForegroundColor"> + <color> + <red>0</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="whatsThis" stdset="0"> + <string>This filter is applied to text jobs of the specified language. You may select more than one language by clicking the browse button and Ctrl-clicking on more than one in the list. If blank, the filter applies to all text jobs of any language.</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>languageBrowseButton</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to select one or more languages. This filter will be applied to text jobs of those languages.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>languageCheckBox</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + </hbox> + </widget> + <widget class="QRadioButton"> + <property name="name"> + <cstring>useSpecificTalkerRadioButton</cstring> + </property> + <property name="text"> + <string>Use specific &Talker</string> + </property> + <property name="whatsThis" stdset="0"> + <string>When checked, will use the specific Talker (if it is still configured), otherwise the Talker most closely matching.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer15_2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>30</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KListView"> + <column> + <property name="text"> + <string>Language</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Speech Synthesizer</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Voice</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Gender</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Volume</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Rate</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>talkersListView</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>3</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="resizeMode"> + <enum>LastColumn</enum> + </property> + <property name="whatsThis" stdset="0"> + <string>This is a list of all the configured Talkers. A Talker is a speech synthesizer that has been configured with a language, voice, gender, speaking rate, and volume. Talkers higher in the list have higher priority. The topmost Talker will be used when no talker attributes have been specified by an application.</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>useDefaultRadioButton</tabstop> + <tabstop>useClosestMatchRadioButton</tabstop> + <tabstop>synthComboBox</tabstop> + <tabstop>synthCheckBox</tabstop> + <tabstop>genderComboBox</tabstop> + <tabstop>genderCheckBox</tabstop> + <tabstop>volumeComboBox</tabstop> + <tabstop>volumeCheckBox</tabstop> + <tabstop>rateComboBox</tabstop> + <tabstop>rateCheckBox</tabstop> + <tabstop>languageLineEdit</tabstop> + <tabstop>languageBrowseButton</tabstop> + <tabstop>languageCheckBox</tabstop> + <tabstop>useSpecificTalkerRadioButton</tabstop> + <tabstop>talkersListView</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="0"/> +<includehints> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>klistview.h</includehint> +</includehints> +</UI> diff --git a/kttsd/libkttsd/stretcher.cpp b/kttsd/libkttsd/stretcher.cpp new file mode 100644 index 0000000..d3a3984 --- /dev/null +++ b/kttsd/libkttsd/stretcher.cpp @@ -0,0 +1,99 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + Speeds up or slows down an audio file by stretching the audio stream. + Uses the sox program to do the stretching. + + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// KDE includes. +#include <kprocess.h> +#include <kdebug.h> + +// Stretcher includes. +#include "stretcher.h" +#include "stretcher.moc" + +/** + * Constructor. + */ +Stretcher::Stretcher(QObject *parent, const char *name) : + QObject(parent, name) +{ + m_state = 0; + m_stretchProc = 0; +} + +/** + * Destructor. + */ +Stretcher::~Stretcher() +{ + delete m_stretchProc; +} + +/** + * Stretch the given input file to an output file. + * @param inFilename Name of input audio file. + * @param outFilename Name of output audio file. + * @param stretchFactor Amount to stretch. 2.0 is twice as slow. 0.5 is twice as fast. + * @return False if an error occurs. + */ +bool Stretcher::stretch(const QString &inFilename, const QString &outFilename, float stretchFactor) +{ + if (m_stretchProc) return false; + m_outFilename = outFilename; + m_stretchProc = new KProcess; + QString stretchStr = QString("%1").arg(stretchFactor, 0, 'f', 3); + *m_stretchProc << "sox" << inFilename << outFilename << "stretch" << stretchStr; + connect(m_stretchProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + if (!m_stretchProc->start(KProcess::NotifyOnExit, KProcess::NoCommunication)) + { + kdDebug() << "Stretcher::stretch: Error starting audio stretcher process. Is sox installed?" << endl; + return false; + } + m_state = ssStretching; + return true; +} + +void Stretcher::slotProcessExited(KProcess*) +{ + m_stretchProc->deleteLater(); + m_stretchProc = 0; + m_state = ssFinished; + emit stretchFinished(); +} + +/** + * Returns the state of the Stretcher. + */ +int Stretcher::getState() { return m_state; } + +/** + * Returns the output filename (as given in call to stretch). + */ +QString Stretcher::getOutFilename() { return m_outFilename; } + +/** + * Acknowledges the finished stretching. + */ +void Stretcher::ackFinished() { m_state = ssIdle; } + diff --git a/kttsd/libkttsd/stretcher.h b/kttsd/libkttsd/stretcher.h new file mode 100644 index 0000000..e4d8686 --- /dev/null +++ b/kttsd/libkttsd/stretcher.h @@ -0,0 +1,97 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Description: + Speeds up or slows down an audio file by stretching the audio stream. + Uses the sox program to do the stretching. + + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _STRETCHER_H_ +#define _STRETCHER_H_ + +#include <kdemacros.h> +#include "kdeexportfix.h" + +class KProcess; + +class KDE_EXPORT Stretcher : public QObject{ + Q_OBJECT + + public: + /** + * Constructor. + */ + Stretcher(QObject *parent = 0, const char *name = 0); + + /** + * Destructor. + */ + ~Stretcher(); + + enum StretcherState { + ssIdle = 0, // Not doing anything. Ready to stretch. + ssStretching = 1, // Stretching. + ssFinished = 2 // Stretching finished. + }; + + /** + * Stretch the given input file to an output file. + * @param inFilename Name of input audio file. + * @param outFilename Name of output audio file. + * @param stretchFactor Amount to stretch. 2.0 is twice as slow. 0.5 is twice as fast. + */ + bool stretch(const QString &inFilename, const QString &outFilename, float stretchFactor); + + /** + * Returns the state of the Stretcher. + */ + int getState(); + + /** + * Returns the output filename (as given in call to stretch). + */ + QString getOutFilename(); + + /** + * Acknowledges the finished stretching. + */ + void ackFinished(); + + signals: + /** + * Emitted whenever stretching is completed. + */ + void stretchFinished(); + + private slots: + void slotProcessExited(KProcess* proc); + + private: + // Stretcher state. + int m_state; + + // Sox process. + KProcess* m_stretchProc; + + // Output file name. + QString m_outFilename; +}; + +#endif // _STRETCHER_H_ diff --git a/kttsd/libkttsd/talkercode.cpp b/kttsd/libkttsd/talkercode.cpp new file mode 100644 index 0000000..d25cf7f --- /dev/null +++ b/kttsd/libkttsd/talkercode.cpp @@ -0,0 +1,517 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Convenience object for manipulating Talker Codes. + For an explanation of what a Talker Code is, see kspeech.h. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// KDE includes. +#include <kglobal.h> +#include <klocale.h> +#include <ktrader.h> +#include <kdebug.h> + +// TalkerCode includes. +#include "talkercode.h" + +/** + * Constructor. + */ +TalkerCode::TalkerCode(const QString &code/*=QString::null*/, bool normal /*=false*/) +{ + if (!code.isEmpty()) + parseTalkerCode(code); + if (normal) normalize(); +} + +/** + * Copy Constructor. + */ +TalkerCode::TalkerCode(TalkerCode* talker, bool normal /*=false*/) +{ + m_languageCode = talker->languageCode(); + m_countryCode = talker->countryCode(); + m_voice = talker->voice(); + m_gender = talker->gender(); + m_volume = talker->volume(); + m_rate = talker->rate(); + m_plugInName = talker->plugInName(); + if (normal) normalize(); +} + +/** + * Destructor. + */ +TalkerCode::~TalkerCode() { } + +/** + * Properties. + */ +QString TalkerCode::languageCode() const { return m_languageCode; } +QString TalkerCode::countryCode() const { return m_countryCode; } +QString TalkerCode::voice() const { return m_voice; } +QString TalkerCode::gender() const { return m_gender; } +QString TalkerCode::volume() const { return m_volume; } +QString TalkerCode::rate() const { return m_rate; } +QString TalkerCode::plugInName() const { return m_plugInName; } + +void TalkerCode::setLanguageCode(const QString &languageCode) { m_languageCode = languageCode; } +void TalkerCode::setCountryCode(const QString &countryCode) { m_countryCode = countryCode; } +void TalkerCode::setVoice(const QString &voice) { m_voice = voice; } +void TalkerCode::setGender(const QString &gender) { m_gender = gender; } +void TalkerCode::setVolume(const QString &volume) { m_volume = volume; } +void TalkerCode::setRate(const QString &rate) { m_rate = rate; } +void TalkerCode::setPlugInName(const QString plugInName) { m_plugInName = plugInName; } + +/** + * Sets the language code and country code (if given). + */ +void TalkerCode::setFullLanguageCode(const QString &fullLanguageCode) +{ + splitFullLanguageCode(fullLanguageCode, m_languageCode, m_countryCode); +} + +/** + * Returns the language code plus country code (if any). + */ +QString TalkerCode::fullLanguageCode() const +{ + if (!m_countryCode.isEmpty()) + return m_languageCode + "_" + m_countryCode; + else + return m_languageCode; +} + +/** + * The Talker Code returned in XML format. + */ +QString TalkerCode::getTalkerCode() const +{ + QString code; + QString languageCode = m_languageCode; + if (!m_countryCode.isEmpty()) languageCode += "_" + m_countryCode; + if (!languageCode.isEmpty()) code = "lang=\"" + languageCode + "\" "; + if (!m_voice.isEmpty()) code += "name=\"" + m_voice + "\" "; + if (!m_gender.isEmpty()) code += "gender=\"" + m_gender + "\" "; + if (!code.isEmpty()) code = "<voice " + code + "/>"; + QString prosody; + if (!m_volume.isEmpty()) prosody = "volume=\"" + m_volume + "\" "; + if (!m_rate.isEmpty()) prosody += "rate=\"" + m_rate + "\" "; + if (!prosody.isEmpty()) code += "<prosody " + prosody + "/>"; + if (!m_plugInName.isEmpty()) code += "<kttsd synthesizer=\"" + m_plugInName + "\" />"; + return code; +} + +/** + * The Talker Code translated for display. + */ +QString TalkerCode::getTranslatedDescription() const +{ + QString code; + bool prefer; + QString fullLangCode = fullLanguageCode(); + if (!fullLangCode.isEmpty()) code = languageCodeToLanguage( fullLangCode ); + // TODO: The PlugInName is always English. Need a way to convert this to a translated + // name (possibly via DesktopEntryNameToName, but to do that, we need the desktopEntryName + // from the config file). + if (!m_plugInName.isEmpty()) code += " " + stripPrefer(m_plugInName, prefer); + if (!m_voice.isEmpty()) code += " " + stripPrefer(m_voice, prefer); + if (!m_gender.isEmpty()) code += " " + translatedGender(stripPrefer(m_gender, prefer)); + if (!m_volume.isEmpty()) code += " " + translatedVolume(stripPrefer(m_volume, prefer)); + if (!m_rate.isEmpty()) code += " " + translatedRate(stripPrefer(m_rate, prefer)); + code = code.stripWhiteSpace(); + if (code.isEmpty()) code = i18n("default"); + return code; +} + +/** + * Normalizes the Talker Code by filling in defaults. + */ +void TalkerCode::normalize() +{ + if (m_voice.isEmpty()) m_voice = "fixed"; + if (m_gender.isEmpty()) m_gender = "neutral"; + if (m_volume.isEmpty()) m_volume = "medium"; + if (m_rate.isEmpty()) m_rate = "medium"; +} + +/** + * Given a talker code, normalizes it into a standard form and also returns + * the language code. + * @param talkerCode Unnormalized talker code. + * @return fullLanguageCode Language code from the talker code (including country code if any). + * @return Normalized talker code. + */ +/*static*/ QString TalkerCode::normalizeTalkerCode(const QString &talkerCode, QString &fullLanguageCode) +{ + TalkerCode tmpTalkerCode(talkerCode); + tmpTalkerCode.normalize(); + fullLanguageCode = tmpTalkerCode.fullLanguageCode(); + return tmpTalkerCode.getTalkerCode(); +} + +/** + * Given a language code that might contain a country code, splits the code into + * the two letter language code and country code. + * @param fullLanguageCode Language code to be split. + * @return languageCode Just the language part of the code. + * @return countryCode The country code part (if any). + * + * If the input code begins with an asterisk, it is ignored and removed from the returned + * languageCode. + */ +/*static*/ void TalkerCode::splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode) +{ + QString language = lang; + if (language.left(1) == "*") language = language.mid(1); + QString charSet; + KGlobal::locale()->splitLocale(language, languageCode, countryCode, charSet); +} + +/** + * Given a full language code and plugin name, returns a normalized default talker code. + * @param fullLanguageCode Language code. + * @param plugInName Name of the Synthesizer plugin. + * @return Full normalized talker code. + * + * Example returned from defaultTalkerCode("en", "Festival") + * <voice lang="en" name="fixed" gender="neutral"/> + * <prosody volume="medium" rate="medium"/> + * <kttsd synthesizer="Festival" /> + */ +/*static*/ QString TalkerCode::defaultTalkerCode(const QString &fullLanguageCode, const QString &plugInName) +{ + TalkerCode tmpTalkerCode; + tmpTalkerCode.setFullLanguageCode(fullLanguageCode); + tmpTalkerCode.setPlugInName(plugInName); + tmpTalkerCode.normalize(); + return tmpTalkerCode.getTalkerCode(); +} + +/** + * Converts a language code plus optional country code to language description. + */ +/*static*/ QString TalkerCode::languageCodeToLanguage(const QString &languageCode) +{ + QString twoAlpha; + QString countryCode; + QString language; + if (languageCode == "other") + language = i18n("Other"); + else + { + splitFullLanguageCode(languageCode, twoAlpha, countryCode); + language = KGlobal::locale()->twoAlphaToLanguageName(twoAlpha); + } + if (!countryCode.isEmpty()) + { + QString countryName = KGlobal::locale()->twoAlphaToCountryName(countryCode); + // Some abbreviations to save screen space. + if (countryName == i18n("full country name", "United States of America")) + countryName = i18n("abbreviated country name", "USA"); + if (countryName == i18n("full country name", "United Kingdom")) + countryName = i18n("abbreviated country name", "UK"); + language += " (" + countryName + ")"; + } + return language; +} + +/** + * These functions return translated Talker Code attributes. + */ +/*static*/ QString TalkerCode::translatedGender(const QString &gender) +{ + if (gender == "male") + return i18n("male"); + else if (gender == "female") + return i18n("female"); + else if (gender == "neutral") + return i18n("neutral gender", "neutral"); + else return gender; +} +/*static*/ QString TalkerCode::untranslatedGender(const QString &gender) +{ + if (gender == i18n("male")) + return "male"; + else if (gender == i18n("female")) + return "female"; + else if (gender == i18n("neutral gender", "neutral")) + return "neutral"; + else return gender; +} +/*static*/ QString TalkerCode::translatedVolume(const QString &volume) +{ + if (volume == "medium") + return i18n("medium sound", "medium"); + else if (volume == "loud") + return i18n("loud sound", "loud"); + else if (volume == "soft") + return i18n("soft sound", "soft"); + else return volume; +} +/*static*/ QString TalkerCode::untranslatedVolume(const QString &volume) +{ + if (volume == i18n("medium sound", "medium")) + return "medium"; + else if (volume == i18n("loud sound", "loud")) + return "loud"; + else if (volume == i18n("soft sound", "soft")) + return "soft"; + else return volume; +} +/*static*/ QString TalkerCode::translatedRate(const QString &rate) +{ + if (rate == "medium") + return i18n("medium speed", "medium"); + else if (rate == "fast") + return i18n("fast speed", "fast"); + else if (rate == "slow") + return i18n("slow speed", "slow"); + else return rate; +} +/*static*/ QString TalkerCode::untranslatedRate(const QString &rate) +{ + if (rate == i18n("medium speed", "medium")) + return "medium"; + else if (rate == i18n("fast speed", "fast")) + return "fast"; + else if (rate == i18n("slow speed", "slow")) + return "slow"; + else return rate; +} + +/** + * Given a talker code, parses out the attributes. + * @param talkerCode The talker code. + */ +void TalkerCode::parseTalkerCode(const QString &talkerCode) +{ + QString fullLanguageCode; + if (talkerCode.contains("\"")) + { + fullLanguageCode = talkerCode.section("lang=", 1, 1); + fullLanguageCode = fullLanguageCode.section('"', 1, 1); + } + else + fullLanguageCode = talkerCode; + QString languageCode; + QString countryCode; + splitFullLanguageCode(fullLanguageCode, languageCode, countryCode); + m_languageCode = languageCode; + if (fullLanguageCode.left(1) == "*") countryCode = "*" + countryCode; + m_countryCode = countryCode; + m_voice = talkerCode.section("name=", 1, 1); + m_voice = m_voice.section('"', 1, 1); + m_gender = talkerCode.section("gender=", 1, 1); + m_gender = m_gender.section('"', 1, 1); + m_volume = talkerCode.section("volume=", 1, 1); + m_volume = m_volume.section('"', 1, 1); + m_rate = talkerCode.section("rate=", 1, 1); + m_rate = m_rate.section('"', 1, 1); + m_plugInName = talkerCode.section("synthesizer=", 1, 1); + m_plugInName = m_plugInName.section('"', 1, 1); +} + +/** + * Given a list of parsed talker codes and a desired talker code, finds the closest + * matching talker in the list. + * @param talkers The list of parsed talker codes. + * @param talker The desired talker code. + * @param assumeDefaultLang If true, and desired talker code lacks a language code, + * the default language is assumed. + * @return Index into talkers of the closest matching talker. + */ +/*static*/ int TalkerCode::findClosestMatchingTalker( + const TalkerCodeList& talkers, + const QString& talker, + bool assumeDefaultLang) +{ + // kdDebug() << "TalkerCode::findClosestMatchingTalker: matching on talker code " << talker << endl; + // If nothing to match on, winner is top in the list. + if (talker.isEmpty()) return 0; + // Parse the given talker. + TalkerCode parsedTalkerCode(talker); + // If no language code specified, use the language code of the default talker. + if (assumeDefaultLang) + { + if (parsedTalkerCode.languageCode().isEmpty()) parsedTalkerCode.setLanguageCode( + talkers[0].languageCode()); + } + // The talker that matches on the most priority attributes wins. + int talkersCount = int(talkers.count()); + QMemArray<int> priorityMatch(talkersCount); + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + priorityMatch[ndx] = 0; + // kdDebug() << "Comparing language code " << parsedTalkerCode.languageCode() << " to " << m_loadedPlugIns[ndx].parsedTalkerCode.languageCode() << endl; + if (parsedTalkerCode.languageCode() == talkers[ndx].languageCode()) + { + ++priorityMatch[ndx]; + // kdDebug() << "TalkerCode::findClosestMatchingTalker: Match on language " << parsedTalkerCode.languageCode() << endl; + } + if (parsedTalkerCode.countryCode().left(1) == "*") + if (parsedTalkerCode.countryCode().mid(1) == + talkers[ndx].countryCode()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.voice().left(1) == "*") + if (parsedTalkerCode.voice().mid(1) == talkers[ndx].voice()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.gender().left(1) == "*") + if (parsedTalkerCode.gender().mid(1) == talkers[ndx].gender()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.volume().left(1) == "*") + if (parsedTalkerCode.volume().mid(1) == talkers[ndx].volume()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.rate().left(1) == "*") + if (parsedTalkerCode.rate().mid(1) == talkers[ndx].rate()) + ++priorityMatch[ndx]; + if (parsedTalkerCode.plugInName().left(1) == "*") + if (parsedTalkerCode.plugInName().mid(1) == + talkers[ndx].plugInName()) + ++priorityMatch[ndx]; + } + // Determine the maximum number of priority attributes that were matched. + int maxPriority = -1; + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + if (priorityMatch[ndx] > maxPriority) maxPriority = priorityMatch[ndx]; + } + // Find the talker(s) that matched on most priority attributes. + int winnerCount = 0; + int winner = -1; + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + if (priorityMatch[ndx] == maxPriority) + { + ++winnerCount; + winner = ndx; + } + } + // kdDebug() << "Priority phase: winnerCount = " << winnerCount + // << " winner = " << winner + // << " maxPriority = " << maxPriority << endl; + // If a tie, the one that matches on the most priority and preferred attributes wins. + // If there is still a tie, the one nearest the top of the kttsmgr display + // (first configured) will be chosen. + if (winnerCount > 1) + { + QMemArray<int> preferredMatch(talkersCount); + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + preferredMatch[ndx] = 0; + if (priorityMatch[ndx] == maxPriority) + { + if (parsedTalkerCode.countryCode().left(1) != "*") + if (!talkers[ndx].countryCode().isEmpty()) + if (parsedTalkerCode.countryCode() == talkers[ndx].countryCode()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.voice().left(1) != "*") + if (parsedTalkerCode.voice() == talkers[ndx].voice()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.gender().left(1) != "*") + if (parsedTalkerCode.gender() == talkers[ndx].gender()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.volume().left(1) != "*") + if (parsedTalkerCode.volume() == talkers[ndx].volume()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.rate().left(1) != "*") + if (parsedTalkerCode.rate() == talkers[ndx].rate()) + ++preferredMatch[ndx]; + if (parsedTalkerCode.plugInName().left(1) != "*") + if (parsedTalkerCode.plugInName() == + talkers[ndx].plugInName()) + ++preferredMatch[ndx]; + } + } + // Determine the maximum number of preferred attributes that were matched. + int maxPreferred = -1; + for (int ndx = 0; ndx < talkersCount; ++ndx) + { + if (preferredMatch[ndx] > maxPreferred) maxPreferred = preferredMatch[ndx]; + } + winner = -1; + winnerCount = 0; + // Find the talker that matched on most priority and preferred attributes. + // Work bottom to top so topmost wins in a tie. + for (int ndx = talkersCount-1; ndx >= 0; --ndx) + { + if (priorityMatch[ndx] == maxPriority) + { + if (preferredMatch[ndx] == maxPreferred) + { + ++winnerCount; + winner = ndx; + } + } + } + // kdDebug() << "Preferred phase: winnerCount = " << winnerCount + // << " winner = " << winner + // << " maxPreferred = " << maxPreferred << endl; + } + // If no winner found, use the first talker. + if (winner < 0) winner = 0; + // kdDebug() << "TalkerCode::findClosestMatchingTalker: returning winner = " << winner << endl; + return winner; +} + +/*static*/ QString TalkerCode::stripPrefer( const QString& code, bool& preferred) +{ + if ( code.left(1) == "*" ) + { + preferred = true; + return code.mid(1); + } else { + preferred = false; + return code; + } +} + +/** +* Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName. +* @param name The translated plugin name. From Name= line in .desktop file. +* @return DesktopEntryName. The name of the .desktop file (less .desktop). +* QString::null if not found. +*/ +/*static*/ QString TalkerCode::TalkerNameToDesktopEntryName(const QString& name) +{ + if (name.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin"); + for (uint ndx = 0; ndx < offers.count(); ++ndx) + if (offers[ndx]->name() == name) return offers[ndx]->desktopEntryName(); + return QString::null; +} + +/** +* Uses KTrader to convert a DesktopEntryName into a translated Synth Plugin Name. +* @param desktopEntryName The DesktopEntryName. +* @return The translated Name of the plugin, from Name= line in .desktop file. +*/ +/*static*/ QString TalkerCode::TalkerDesktopEntryNameToName(const QString& desktopEntryName) +{ + if (desktopEntryName.isEmpty()) return QString::null; + KTrader::OfferList offers = KTrader::self()->query("KTTSD/SynthPlugin", + QString("DesktopEntryName == '%1'").arg(desktopEntryName)); + + if (offers.count() == 1) + return offers[0]->name(); + else + return QString::null; +} + diff --git a/kttsd/libkttsd/talkercode.h b/kttsd/libkttsd/talkercode.h new file mode 100644 index 0000000..45469af --- /dev/null +++ b/kttsd/libkttsd/talkercode.h @@ -0,0 +1,197 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Object containing a Talker Code and providing convenience + functions for manipulating Talker Codes. + For an explanation of what a Talker Code is, see speech.h. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _TALKERCODE_H_ +#define _TALKERCODE_H_ + +// Qt includes. +#include <qstring.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include <qvaluelist.h> + +class KDE_EXPORT TalkerCode +{ + public: + /** + * Constructor. + */ + TalkerCode(const QString &code=QString::null, bool normal=false); + /** + * Copy Constructor. + */ + TalkerCode(TalkerCode* talker, bool normal=false); + + /** + * Destructor. + */ + ~TalkerCode(); + + typedef QValueList<TalkerCode> TalkerCodeList; + + /** + * Properties. + */ + QString languageCode() const; /* lang="xx" */ + QString countryCode() const; /* lang="yy_xx */ + QString voice() const; /* name="xxx" */ + QString gender() const; /* gender="xxx" */ + QString volume() const; /* volume="xxx" */ + QString rate() const; /* rate="xxx" */ + QString plugInName() const; /* synthesizer="xxx" */ + + /** + * Returns the language code plus country code (if any). + */ + QString fullLanguageCode() const; + + void setLanguageCode(const QString &languageCode); + void setCountryCode(const QString &countryCode); + void setVoice(const QString &voice); + void setGender(const QString &gender); + void setVolume(const QString &volume); + void setRate(const QString &rate); + void setPlugInName(const QString plugInName); + + /** + * Sets the language code and country code (if given). + */ + void setFullLanguageCode(const QString &fullLanguageCode); + + /** + * The Talker Code returned in XML format. + */ + QString getTalkerCode() const; + + /** + * The Talker Code translated for display. + */ + QString getTranslatedDescription() const; + + /** + * Normalizes the Talker Code by filling in defaults. + */ + void normalize(); + + /** + * Given a talker code, normalizes it into a standard form and also returns + * the full language code. + * @param talkerCode Unnormalized talker code. + * @return fullLanguageCode Language code from the talker code (including country code if any). + * @return Normalized talker code. + */ + static QString normalizeTalkerCode(const QString &talkerCode, QString &fullLanguageCode); + + /** + * Given a language code that might contain a country code, splits the code into + * the two letter language code and country code. + * @param fullLanguageCode Language code to be split. + * @return languageCode Just the language part of the code. + * @return countryCode The country code part (if any). + * + * If the input code begins with an asterisk, it is ignored and removed from the returned + * languageCode. + */ + static void splitFullLanguageCode(const QString &lang, QString &languageCode, QString &countryCode); + + /** + * Given a language code and plugin name, returns a normalized default talker code. + * @param fullLanguageCode Language code. + * @param plugInName Name of the Synthesizer plugin. + * @return Full normalized talker code. + * + * Example returned from defaultTalkerCode("en", "Festival") + * <voice lang="en" name="fixed" gender="neutral"/> + * <prosody volume="medium" rate="medium"/> + * <kttsd synthesizer="Festival" /> + */ + static QString defaultTalkerCode(const QString &fullLanguageCode, const QString &plugInName); + + /** + * Converts a language code plus optional country code to language description. + */ + static QString languageCodeToLanguage(const QString &languageCode); + + /** + * These functions return translated Talker Code attributes. + */ + static QString translatedGender(const QString &gender); + static QString translatedVolume(const QString &volume); + static QString translatedRate(const QString &rate); + static QString untranslatedGender(const QString &gender); + static QString untranslatedVolume(const QString &volume); + static QString untranslatedRate(const QString &rate); + + /** + * Given a list of parsed talker codes and a desired talker code, finds the closest + * matching talker in the list. + * @param talkers The list of parsed talker codes. + * @param talker The desired talker code. + * @param assumeDefaultLang If true, and desired talker code lacks a language code, + * the default language is assumed. + * @return Index into talkers of the closest matching talker. + */ + static int findClosestMatchingTalker( + const TalkerCodeList& talkers, + const QString& talker, + bool assumeDefaultLang = true); + + /** + * Strips leading * from a code. + */ + static QString stripPrefer( const QString& code, bool& preferred); + + /** + * Uses KTrader to convert a translated Synth Plugin Name to DesktopEntryName. + * @param name The translated plugin name. From Name= line in .desktop file. + * @return DesktopEntryName. The name of the .desktop file (less .desktop). + * QString::null if not found. + */ + static QString TalkerNameToDesktopEntryName(const QString& name); + + /** + * Uses KTrader to convert a DesktopEntryName into a translated Synth Plugin Name. + * @param desktopEntryName The DesktopEntryName. + * @return The translated Name of the plugin, from Name= line in .desktop file. + */ + static QString TalkerDesktopEntryNameToName(const QString& desktopEntryName); + + private: + /** + * Given a talker code, parses out the attributes. + * @param talkerCode The talker code. + */ + void parseTalkerCode(const QString &talkerCode); + + QString m_languageCode; /* lang="xx" */ + QString m_countryCode; /* lang="yy_xx */ + QString m_voice; /* name="xxx" */ + QString m_gender; /* gender="xxx" */ + QString m_volume; /* volume="xxx" */ + QString m_rate; /* rate="xxx" */ + QString m_plugInName; /* synthesizer="xxx" */ +}; + +#endif // _TALKERCODE_H_ diff --git a/kttsd/libkttsd/testplayer.cpp b/kttsd/libkttsd/testplayer.cpp new file mode 100644 index 0000000..06d05a7 --- /dev/null +++ b/kttsd/libkttsd/testplayer.cpp @@ -0,0 +1,209 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Player Object for playing synthesized audio files. Plays them + synchronously. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qfile.h> + +// KDE includes. +#include <kapplication.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kparts/componentfactory.h> +#include <ktrader.h> +#include <kdebug.h> + +// KTTS includes. +#include "player.h" +#include "stretcher.h" +#include "pluginconf.h" + +// TestPlayer includes. +#include "testplayer.h" + +/** + * Constructor. + */ +TestPlayer::TestPlayer(QObject *parent, const char *name, + const int playerOption, const float audioStretchFactor, const QString &sinkName) : + QObject(parent, name) +{ + m_playerOption = playerOption; + m_audioStretchFactor = audioStretchFactor; + m_stretcher = 0; + m_player = 0; + m_sinkName = sinkName; +} + +/** + * Destructor. + */ +TestPlayer::~TestPlayer() +{ + delete m_stretcher; + delete m_player; +} + +/** + * Sets which audio player to use. + * 0 = aRts + * 1 = gstreamer + * 2 = ALSA + * 3 = aKode + */ +void TestPlayer::setPlayerOption(const int playerOption) { m_playerOption = playerOption; } + +/** + * Sets the audio stretch factor (Speed adjustment). + * 1.0 = normal + * 0.5 = twice as fast + * 2.0 = twice as slow + */ +void TestPlayer::setAudioStretchFactor(const float audioStretchFactor) + { m_audioStretchFactor = audioStretchFactor; } + +void TestPlayer::setSinkName(const QString &sinkName) { m_sinkName = sinkName; } + +/** + * Plays the specifified audio file and waits for completion. + * The audio file speed is adjusted according to the stretch factor. + * @param waveFile Name of the audio file to play. + */ +void TestPlayer::play(const QString &waveFile) +{ + // kdDebug() << "TestPlayer::play: running" << endl; + // Create a Stretcher object to adjust the audio Speed. + QString playFile = waveFile; + QString tmpFile; + if (m_audioStretchFactor != 1.0) + { + tmpFile = makeSuggestedFilename(); + // kdDebug() << "TestPlayer::play: stretching file " << playFile << " by " << m_audioStretchFactor + // << " to file " << tmpFile << endl; + m_stretcher = new Stretcher(); + if (m_stretcher->stretch(playFile, tmpFile, m_audioStretchFactor)) + { + while (m_stretcher->getState() != Stretcher::ssFinished) qApp->processEvents(); + playFile = m_stretcher->getOutFilename(); + } + delete m_stretcher; + m_stretcher = 0; + } + + // Create player object based on player option. + // kdDebug() << "TestPlayer::play: creating Player object with playerOption " << m_playerOption << endl; + m_player = createPlayerObject(m_playerOption); + // If player object could not be created, avoid crash is the best we can do! + if (!m_player) return; + // kdDebug() << "TestPlayer::play: starting playback." << endl; + m_player->startPlay(playFile); + + // TODO: The following hunk of code would ideally be unnecessary. We would just + // return at this point and let take care of + // cleaning up the play object. However, because we've been called from DCOP, + // this seems to be necessary. The call to processEvents is problematic because + // it can cause re-entrancy. + while (m_player->playing()) qApp->processEvents(); + // kdDebug() << "TestPlayer::play: stopping playback." << endl; + m_player->stop(); + delete m_player; + m_player = 0; + if (!tmpFile.isEmpty()) QFile::remove(tmpFile); +} + +/** + * Creates and returns a player object based on user option. + */ +Player* TestPlayer::createPlayerObject(int playerOption) +{ + Player* player = 0; + QString plugInName; + switch(playerOption) + { + case 1 : + { + plugInName = "kttsd_gstplugin"; + break; + } + case 2 : + { + plugInName = "kttsd_alsaplugin"; + break; + } + case 3 : + { + plugInName = "kttsd_akodeplugin"; + break; + } + default: + { + plugInName = "kttsd_artsplugin"; + break; + } + } + KTrader::OfferList offers = KTrader::self()->query( + "KTTSD/AudioPlugin", QString("DesktopEntryName == '%1'").arg(plugInName)); + + if(offers.count() == 1) + { + // kdDebug() << "TestPlayer::createPlayerObject: Loading " << offers[0]->library() << endl; + KLibFactory *factory = KLibLoader::self()->factory(offers[0]->library().latin1()); + if (factory) + player = + KParts::ComponentFactory::createInstanceFromLibrary<Player>( + offers[0]->library().latin1(), this, offers[0]->library().latin1()); + else + kdDebug() << "TestPlayer::createPlayerObject: Could not create factory." << endl; + } + if (player == 0) + kdDebug() << "TestPlayer::createPlayerObject: Could not load " + plugInName + + ". Is KDEDIRS set correctly?" << endl; + else + // Must have GStreamer >= 0.8.7. + if (playerOption == 1) + { + if (!player->requireVersion(0, 8, 7)) + { + delete player; + player = 0; + } + } + if (player) player->setSinkName(m_sinkName); + return player; +} + +/** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ +QString TestPlayer::makeSuggestedFilename() +{ + KTempFile tempFile (locateLocal("tmp", "kttsmgr-"), ".wav"); + QString waveFile = tempFile.file()->name(); + tempFile.close(); + QFile::remove(waveFile); + // kdDebug() << "TestPlayer::makeSuggestedFilename: Suggesting filename: " << waveFile << endl; + return PlugInConf::realFilePath(waveFile); +} + diff --git a/kttsd/libkttsd/testplayer.h b/kttsd/libkttsd/testplayer.h new file mode 100644 index 0000000..2e7339c --- /dev/null +++ b/kttsd/libkttsd/testplayer.h @@ -0,0 +1,121 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Player Object for playing synthesized audio files. Plays them + synchronously. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _TESTPLAYER_H_ +#define _TESTPLAYER_H_ + +#include <kdemacros.h> +#include "kdeexportfix.h" + +class Player; +class Stretcher; + +// TODO: Make this work asynchronously. + +class KDE_EXPORT TestPlayer : public QObject{ + public: + /** + * Constructor. + * @param playerOption + * @param audioStretchFactor + */ + TestPlayer(QObject *parent = 0, const char *name = 0, + const int playerOption = 0, const float audioStretchFactor = 1.0, + const QString &sinkName = QString::null); + + /** + * Destructor. + */ + ~TestPlayer(); + + /** + * Sets which audio player to use. + * 0 = aRts + * 1 = gstreamer + */ + void setPlayerOption(const int playerOption); + + /** + * Sets the audio stretch factor (Speed adjustment). + * 1.0 = normal + * 0.5 = twice as fast + * 2.0 = twice as slow + */ + void setAudioStretchFactor(const float audioStretchFactor); + + /** + * Plays the specifified audio file and waits for completion. + * The audio file speed is adjusted according to the stretch factor. + * @param waveFile Name of the audio file to play. + */ + void play(const QString &waveFile); + + /** + * Sets the GStreamer Sink Name. Examples: "alsasink", "osssink", "nassink". + */ + void setSinkName(const QString &sinkName); + + /** + * Creates and returns a player object based on user option. + */ + Player* createPlayerObject(int playerOption); + + private: + + /** + * Constructs a temporary filename for plugins to use as a suggested filename + * for synthesis to write to. + * @return Full pathname of suggested file. + */ + QString makeSuggestedFilename(); + + /** + * Which audio player to use. + * 0 = aRts + * 1 = gstreamer + */ + int m_playerOption; + + /** + * Audio stretch factor (Speed). + */ + float m_audioStretchFactor; + + /** + * GStreamer sink name. + */ + QString m_sinkName; + + /** + * Stretcher object. + */ + Stretcher* m_stretcher; + + /** + * Player object. + */ + Player* m_player; +}; + +#endif // _TESTPLAYER_H_ diff --git a/kttsd/libkttsd/utils.cpp b/kttsd/libkttsd/utils.cpp new file mode 100644 index 0000000..dd9f98c --- /dev/null +++ b/kttsd/libkttsd/utils.cpp @@ -0,0 +1,132 @@ +/*************************************************************************** + Class of utility functions. + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ****************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#include <qstring.h> +#include <kdebug.h> +#include <qcombobox.h> + +#include "utils.h" + +KttsUtils::KttsUtils() { +} + + +KttsUtils::~KttsUtils() { +} + +/** + * Check if an XML document has a certain root element. + * @param xmldoc The document to check for the element. + * @param elementName The element to check for in the document. + * @returns True if the root element exists in the document, false otherwise. +*/ +bool KttsUtils::hasRootElement(const QString &xmldoc, const QString &elementName) { + // Strip all whitespace and go from there. + QString doc = xmldoc.simplifyWhiteSpace(); + // Take off the <?xml...?> if it exists + if(doc.startsWith("<?xml")) { + // Look for ?> and strip everything off from there to the start - effectively removing + // <?xml...?> + int xmlStatementEnd = doc.find("?>"); + if(xmlStatementEnd == -1) { + kdDebug() << "KttsUtils::hasRootElement: Bad XML file syntax\n"; + return false; + } + xmlStatementEnd += 2; // len '?>' == 2 + doc = doc.right(doc.length() - xmlStatementEnd); + } + // Take off leading comments, if they exist. + while(doc.startsWith("<!--") || doc.startsWith(" <!--")) { + int commentStatementEnd = doc.find("-->"); + if(commentStatementEnd == -1) { + kdDebug() << "KttsUtils::hasRootElement: Bad XML file syntax\n"; + return false; + } + commentStatementEnd += 3; // len '>' == 2 + doc = doc.right(doc.length() - commentStatementEnd); + } + // Take off the doctype statement if it exists. + while(doc.startsWith("<!DOCTYPE") || doc.startsWith(" <!DOCTYPE")) { + int doctypeStatementEnd = doc.find(">"); + if(doctypeStatementEnd == -1) { + kdDebug() << "KttsUtils::hasRootElement: Bad XML file syntax\n"; + return false; + } + doctypeStatementEnd += 1; // len '>' == 2 + doc = doc.right(doc.length() - doctypeStatementEnd); + } + // We should (hopefully) be left with the root element. + return (doc.startsWith("<" + elementName) || doc.startsWith(" <" + elementName)); +} + +/** + * Check if an XML document has a certain DOCTYPE. + * @param xmldoc The document to check for the doctype. + * @param name The doctype name to check for. Pass QString::null to not check the name. + * @param publicId The public ID to check for. Pass QString::null to not check the ID. + * @param systemId The system ID to check for. Pass QString::null to not check the ID. + * @returns True if the parameters match the doctype, false otherwise. +*/ +bool KttsUtils::hasDoctype(const QString &xmldoc, const QString &name/*, const QString &publicId, const QString &systemId*/) { + // Strip all whitespace and go from there. + QString doc = xmldoc.stripWhiteSpace(); + // Take off the <?xml...?> if it exists + if(doc.startsWith("<?xml")) { + // Look for ?> and strip everything off from there to the start - effectively removing + // <?xml...?> + int xmlStatementEnd = doc.find("?>"); + if(xmlStatementEnd == -1) { + kdDebug() << "KttsUtils::hasDoctype: Bad XML file syntax\n"; + return false; + } + xmlStatementEnd += 2; // len '?>' == 2 + doc = doc.right(doc.length() - xmlStatementEnd); + doc = doc.stripWhiteSpace(); + } + // Take off leading comments, if they exist. + while(doc.startsWith("<!--")) { + int commentStatementEnd = doc.find("-->"); + if(commentStatementEnd == -1) { + kdDebug() << "KttsUtils::hasDoctype: Bad XML file syntax\n"; + return false; + } + commentStatementEnd += 3; // len '>' == 2 + doc = doc.right(doc.length() - commentStatementEnd); + doc = doc.stripWhiteSpace(); + } + // Match the doctype statement if it exists. + // kdDebug() << "KttsUtils::hasDoctype: searching " << doc.left(20) << "... for " << "<!DOCTYPE " << name << endl; + return (doc.startsWith("<!DOCTYPE " + name)); +} + +/** + * Sets the current item in the given combobox to the item with the given text. + * If item with the text not found, does nothing. + */ +/*static*/ void KttsUtils::setCbItemFromText(QComboBox* cb, const QString& text) +{ + const int itemCount = cb->count(); + for (int ndx = 0; ndx < itemCount; ++ndx) + { + if (cb->text(ndx) == text) + { + cb->setCurrentItem(ndx); + return; + } + } +} + diff --git a/kttsd/libkttsd/utils.h b/kttsd/libkttsd/utils.h new file mode 100644 index 0000000..63e95bc --- /dev/null +++ b/kttsd/libkttsd/utils.h @@ -0,0 +1,61 @@ +/*************************************************************************** + Class of utility functions. + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ****************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef UTILS_H +#define UTILS_H + +#include <kdemacros.h> +#include "kdeexportfix.h" + +class QString; +class QComboBox; + +class KDE_EXPORT KttsUtils { + +public: + /// Constructor + KttsUtils(); + /// Destructor + ~KttsUtils(); + + /** + * Check if an XML document has a certain root element. + * @param xmldoc The document to check for the element. + * @param elementName The element to check for in the document. + * @returns true if the root element exists in the document, false otherwise. + */ + static bool hasRootElement(const QString &xmldoc, const QString &elementName); + + /** + * Check if an XML document has a certain DOCTYPE. + * @param xmldoc The document to check for the doctype. + * @param name The doctype name to check for. Pass QString::null to not check the name. + * @param publicId The public ID to check for. Pass QString::null to not check the ID. + * @param systemId The system ID to check for. Pass QString::null to not check the ID. + * @returns true if the parameters match the doctype, false otherwise. + */ + static bool hasDoctype(const QString &xmldoc, const QString &name/*, const QString &publicId, const QString &systemId*/); + + /** + * Sets the current item in the given combobox to the item with the given text. + * If item with the text not found, does nothing. + */ + static void setCbItemFromText(QComboBox* cb, const QString& text); + +}; + +#endif diff --git a/kttsd/players/Makefile.am b/kttsd/players/Makefile.am new file mode 100644 index 0000000..f400d82 --- /dev/null +++ b/kttsd/players/Makefile.am @@ -0,0 +1,27 @@ +#kdevelop: artsplayer_DIR = artsplayer +if include_kttsd_artsplayer + artsplayer_DIR = artsplayer +endif + +#kdevelop: gstplayer_DIR = gstplayer +if include_kttsd_gstplayer + gstplayer_DIR = gstplayer +endif + +#kdevelop: alsaplayer_DIR = alsaplayer +if include_kttsd_alsaplayer + alsaplayer_DIR = alsaplayer +endif + +#kdevelop: akodeplayer_DIR = akodeplayer +if include_kttsd_akodeplayer + akodeplayer_DIR = akodeplayer +endif + +SUBDIRS = \ + $(artsplayer_DIR) \ + $(alsaplayer_DIR) \ + $(gstplayer_DIR) \ + $(akodeplayer_DIR) + +kde_servicetypes_DATA = kttsd_audioplugin.desktop diff --git a/kttsd/players/akodeplayer/Makefile.am b/kttsd/players/akodeplayer/Makefile.am new file mode 100644 index 0000000..f5039f6 --- /dev/null +++ b/kttsd/players/akodeplayer/Makefile.am @@ -0,0 +1,28 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(akode_includes) \ + $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# LIBRARY SECTION +######################################################################### +# This is the library that gets installed. It's name is used for all +# of the other Makefile.am variables. +kde_module_LTLIBRARIES = libkttsd_akodeplugin.la + +libkttsd_akodeplugin_la_SOURCES = \ + akodeplugin.cpp \ + akodeplayer.cpp +libkttsd_akodeplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) -no-undefined +libkttsd_akodeplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la $(akode_libs) + +# Header files that should not be installed. +noinst_HEADERS = \ + akodeplayer.h + +# This library is installed as a plugin. +services_DATA = kttsd_akodeplugin.desktop +servicesdir = $(kde_servicesdir) diff --git a/kttsd/players/akodeplayer/README b/kttsd/players/akodeplayer/README new file mode 100644 index 0000000..a45864f --- /dev/null +++ b/kttsd/players/akodeplayer/README @@ -0,0 +1,19 @@ +This is an aKode audio plugin for KTTS. + +There are two problems right now (19 Jun 2005) that prevent this from being usable +for KTTS: + +1. There was a bug in aKode that prevents it from playing .wav files. Carewolf + applied a patch on 13 Jun 2005 to decoderpluginhandler.cpp for this, + so you may need to update your kdemultimedia modules. + +2. aKode doesn't instantly stop when calling pause() or stop(). + See bug:107135. + +If you want to try this plugin, add --enable-kttsd-akode to ./configure command. + +If the problems above aren't addressed, this plugin should probably be removed +from KTTS. + +-- Gary Cramblitt (PhantomsDad) + diff --git a/kttsd/players/akodeplayer/akodeplayer.cpp b/kttsd/players/akodeplayer/akodeplayer.cpp new file mode 100644 index 0000000..de88773 --- /dev/null +++ b/kttsd/players/akodeplayer/akodeplayer.cpp @@ -0,0 +1,179 @@ +/*************************************************************************** + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include <config.h> + +#include <kdebug.h> + +#include <qfile.h> + +#include "akode/player.h" +#include "akode/decoder.h" + +#include "akodeplayer.h" + +using namespace aKode; + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +aKodePlayer::aKodePlayer(QObject* parent, const char* name, const QStringList& args) : + Player(parent, name, args), + m_player(0) +{} + +aKodePlayer::~aKodePlayer() +{ + delete m_player; +} + +//void aKodePlayer::play(const FileHandle &file) +void aKodePlayer::startPlay(const QString &file) +{ + kdDebug() << k_funcinfo << endl; + + if (file.isNull()) { // null FileHandle file means unpause + if (paused()) + // m_player->resume(); + m_player->play(); + else + stop(); + return; + } + + // QString filename = file.absFilePath(); + + kdDebug() << "Opening: " << file << endl; + + if (m_player) + m_player->stop(); + else { + m_player = new aKode::Player(); + if (!m_player->open(m_sinkName.ascii())) { + kdDebug() << k_funcinfo << "Unable to open aKode " << m_sinkName << " sink. " + << "Falling back to auto." << endl; + m_player->open("auto"); + } + } + + if (m_player->load(QFile::encodeName(file))) + m_player->play(); + +} + +void aKodePlayer::pause() +{ + if (m_player) + m_player->pause(); +} + +void aKodePlayer::stop() +{ + if (m_player) { + m_player->stop(); + m_player->unload(); + } +} + +void aKodePlayer::setVolume(float volume) +{ + if (m_player) + m_player->setVolume(volume); +} + +float aKodePlayer::volume() const +{ + if (m_player) + return m_player->volume(); + // 1.0 is full volume + return 1.0; +} + +///////////////////////////////////////////////////////////////////////////////// +// m_player status functions +///////////////////////////////////////////////////////////////////////////////// + +bool aKodePlayer::playing() const +{ + if (m_player && m_player->decoder()) + return !m_player->decoder()->eof(); + else + return false; +} + +bool aKodePlayer::paused() const +{ + return m_player && (m_player->state() == aKode::Player::Paused); +} + +int aKodePlayer::totalTime() const +{ + if (m_player) { + Decoder *d = m_player->decoder(); + if (d) + return d->length() / 1000; + } + return -1; +} + +int aKodePlayer::currentTime() const +{ + if (m_player) { + Decoder *d = m_player->decoder(); + if (d) + return d->position() / 1000; + } + return -1; +} + +int aKodePlayer::position() const +{ + if (m_player) { + Decoder *d = m_player->decoder(); + if (d && d->length()) + return (d->position()*1000)/(d->length()); + else + return -1; + } + else + return -1; +} + +///////////////////////////////////////////////////////////////////////////////// +// m_player seek functions +///////////////////////////////////////////////////////////////////////////////// + +void aKodePlayer::seek(int seekTime) +{ + // seek time in seconds? + if (m_player) + m_player->decoder()->seek(seekTime*1000); +} + +void aKodePlayer::seekPosition(int position) +{ + // position unit is 1/1000th + if (m_player) + m_player->decoder()->seek((position * m_player->decoder()->length())/1000); +} + +QStringList aKodePlayer::getPluginList( const QCString& /*classname*/ ) +{ + return QStringList::split("|", "auto|polyp|alsa|jack|oss"); +} + +void aKodePlayer::setSinkName(const QString& sinkName) { m_sinkName = sinkName; } + +#include "akodeplayer.moc" diff --git a/kttsd/players/akodeplayer/akodeplayer.h b/kttsd/players/akodeplayer/akodeplayer.h new file mode 100644 index 0000000..bbfb3ea --- /dev/null +++ b/kttsd/players/akodeplayer/akodeplayer.h @@ -0,0 +1,67 @@ +/*************************************************************************** + copyright : (C) 2004 by Allan Sandfeld Jensen + email : kde@carewolf.com +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#ifndef AKODEPLAYER_H +#define AKODEPLAYER_H + +#include <config.h> +#include "kdeexportfix.h" + +#include <qstring.h> + +#include "player.h" +#include <kdemacros.h> +namespace aKode { + class File; + class Player; +} + +class KDE_EXPORT aKodePlayer : public Player +{ + Q_OBJECT + +public: + aKodePlayer(QObject* parent = 0, const char* name = 0, const QStringList& args=QStringList()); + virtual ~aKodePlayer(); + + // virtual void play(const FileHandle &file = FileHandle::null()); + virtual void startPlay(const QString &file); + + virtual void setVolume(float volume = 1.0); + virtual float volume() const; + + virtual bool playing() const; + virtual bool paused() const; + + virtual int totalTime() const; + virtual int currentTime() const; + virtual int position() const; + + virtual void seek(int seekTime); + virtual void seekPosition(int position); + + virtual QStringList getPluginList( const QCString& classname ); + virtual void setSinkName(const QString &sinkName); + +public slots: + void pause(); + void stop(); + +private: + aKode::Player *m_player; + QString m_sinkName; +}; + +#endif diff --git a/kttsd/players/akodeplayer/akodeplugin.cpp b/kttsd/players/akodeplayer/akodeplugin.cpp new file mode 100644 index 0000000..9ef0deb --- /dev/null +++ b/kttsd/players/akodeplayer/akodeplugin.cpp @@ -0,0 +1,29 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so aKode can be used as an audio plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "akodeplayer.h" + +K_EXPORT_COMPONENT_FACTORY( libkttsd_akodeplugin, KGenericFactory<aKodePlayer>("kttsd_akode") ); + diff --git a/kttsd/players/akodeplayer/kttsd_akodeplugin.desktop b/kttsd/players/akodeplayer/kttsd_akodeplugin.desktop new file mode 100644 index 0000000..cc2c4bf --- /dev/null +++ b/kttsd/players/akodeplayer/kttsd_akodeplugin.desktop @@ -0,0 +1,89 @@ +[Desktop Entry] +Name=KTTSD aKode Plugin +Name[br]=Lugent aKode evit KTTSD +Name[bs]=KTTSD aKode dodatak +Name[ca]=Connector aKode pel KTTSD +Name[cs]=KTTSD aKode modul +Name[da]=KTTSD aKode-plugin +Name[de]=KTTSD aKode-Modul +Name[el]=KTTSD πρόσθετο aKode +Name[es]=Complemento aKode de KTTSD +Name[et]=KTTSD aKode plugin +Name[eu]=KTTSD-ren aKode plugina +Name[fa]=وصلۀ KTTSD aKode +Name[fi]=KTTSD aKode -liitännäinen +Name[fr]=Module aKode pour KTTSD +Name[ga]=Breiseán KTTSD aKode +Name[gl]=Plugin aKode de KTTSD +Name[he]=תוסף aKode עבור KTTSD +Name[hu]=KTTSD aKode hangmodul +Name[is]=KTTSD aKode íforrit +Name[it]=Plugin per aKode di KTTSD +Name[ja]=KTTSD aKode プラグイン +Name[ka]=KTTSD aKode მოდული +Name[km]= កម្មវិធីជំនួយ aKode សម្រាប់ KTTSD +Name[mk]=aKode-приклучок за KTTSD +Name[nb]=Programtillegg for KTTSD aKode +Name[nds]=aKode-Moduul för KTTSD +Name[ne]=KTTSD aKode प्लगइन +Name[nl]=KTTSD aKode-plugin +Name[pa]=KTTSD aKode ਪਲੱਗਿੰਨ +Name[pl]=Wtyczka aKode KTTSD +Name[pt]='Plugin' aKode do KTTSD +Name[pt_BR]=Plug-in aKode para o KTTSD +Name[ru]=Модуль aKode для KTTSD +Name[sk]=Modul KTTSD aKode +Name[sl]=Vstavek KTTSD za aKode +Name[sr]=aKode прикључак за KTTSD +Name[sr@Latn]=aKode priključak za KTTSD +Name[sv]=KTTSD-insticksprogram för aKode +Name[tg]=Модули KTTSD aKode +Name[tr]=KTTSD aKode Eklentisi +Name[uk]=Втулок aKode для KTTSD +Name[vi]=Trình bổ sung KTTSD aKode +Name[zh_TW]=KTTSD aKode 外掛程式 +Comment=KTTSD aKode audio plugin +Comment[bg]=Аудио приставка на KTTSD за aKode +Comment[br]=Lugent klevet aKode evit KTTSD +Comment[ca]=Connector d'àudio aKode pel KTTSD +Comment[cs]=KTTSD aKode zvukový modul +Comment[da]=KTTSD aKode lyd-plugin +Comment[de]=KTTSD aKode-Audio-Modul +Comment[el]=KTTSD πρόσθετο ήχου aKode +Comment[es]=Complemento de audio aKode de KTTSD +Comment[et]=KTTSD aKode audioplugin +Comment[eu]=KTTSD-ren aKode audio-plugina +Comment[fa]=وصلۀ صوتی KTTSD aKode +Comment[fi]=KTTSD aKode ääniliitännäinen +Comment[fr]=Module audio aKode pour KTTSD +Comment[ga]=Breiseán fuaime KTTSD aKode +Comment[gl]=Plugin de áudio aKode para KTTSD +Comment[hu]=KTTSD aKode hangmodul +Comment[is]=KTTSD aKode hljóð íforrit +Comment[it]=Plugin audio per aKode di KTTSD +Comment[ja]=KTTSD aKode オーディオプラグイン +Comment[ka]=KTTSD aKode აუდიო მოდული +Comment[km]=កម្មវិធីជំនួយអូឌីយ៉ូ aKode សម្រាប់ KTTSD +Comment[mk]=aKode-аудиоприклучок за KTTSD +Comment[nb]=aKodes lyd-programtillegg for KTTSD +Comment[nds]=aKode-Audiomoduul för KTTSD +Comment[ne]=KTTSD aKode अडियो प्लगइन +Comment[nl]=KTTSD aKode geluidsplugin +Comment[pa]=KTTSD aKode ਆਡੀਓ ਪਲੱਗਿੰਨ +Comment[pl]=Wtyczka dźwięku aKode dla KTTSD +Comment[pt]='Plugin' de áudio aKode do KTTSD +Comment[pt_BR]=Plug-in de áudio KTTSD aKode +Comment[ru]=Модуль вывода звука KTTSD через aKode +Comment[sk]=Modul KTTSD aKode audio +Comment[sl]=Vstavek KTTSD za zvok v aKode +Comment[sr]=aKode аудио прикључак за KTTSD +Comment[sr@Latn]=aKode audio priključak za KTTSD +Comment[sv]=KTTSD-ljudinsticksprogram för aKode +Comment[tg]=Модули KTTSD aKode барои аудио +Comment[tr]=KTTSD aRts ses eklentisi +Comment[uk]=Втулок аудіо aKode для KTTSD +Comment[vi]=Trình bổ sung âm thanh KTTSD aKode +Comment[zh_TW]=KTTSd aKode 語音外掛程式 +Type=Service +ServiceTypes=KTTSD/AudioPlugin +X-KDE-Library=libkttsd_akodeplugin diff --git a/kttsd/players/alsaplayer/Makefile.am b/kttsd/players/alsaplayer/Makefile.am new file mode 100644 index 0000000..9653629 --- /dev/null +++ b/kttsd/players/alsaplayer/Makefile.am @@ -0,0 +1,30 @@ +# Include paths. +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) +KDE_CXXFLAGS = -D_LARGEFILE64_SOURCE -D_GNU_SOURCE + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# LIBRARY SECTION +######################################################################### +# This is the library that gets installed. It's name is used for all +# of the other Makefile.am variables. +kde_module_LTLIBRARIES = libkttsd_alsaplugin.la + +libkttsd_alsaplugin_la_SOURCES = \ + alsaplugin.cpp \ + alsaplayer.cpp +libkttsd_alsaplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) $(LIBASOUND) -no-undefined +libkttsd_alsaplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +# Header files that should not be installed. +noinst_HEADERS = \ + alsaplayer.h \ + formats.h + +# This library is installed as a plugin. +services_DATA = kttsd_alsaplugin.desktop +servicesdir = $(kde_servicesdir) diff --git a/kttsd/players/alsaplayer/alsaplayer.cpp b/kttsd/players/alsaplayer/alsaplayer.cpp new file mode 100644 index 0000000..d3eabe8 --- /dev/null +++ b/kttsd/players/alsaplayer/alsaplayer.cpp @@ -0,0 +1,1729 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + ALSA player. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + Portions based on aplay.c in alsa-utils + Copyright (c) by Jaroslav Kysela <perex@suse.cz> + Based on vplay program by Michael Beck + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// #include <sys/wait.h> +// System includes. +#include <config.h> +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +// Qt includes. +#include <qdir.h> +#include <qapplication.h> +#include <qcstring.h> + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kmessagebox.h> +#include <klocale.h> + +// AlsaPlayer includes. +#include "alsaplayer.h" + +#if !defined(__GNUC__) || __GNUC__ >= 3 +#define ERR(...) do {\ + QString dbgStr;\ + QString s = dbgStr.sprintf( "%s:%d: ERROR ", __FUNCTION__, __LINE__); \ + s += dbgStr.sprintf( __VA_ARGS__); \ + kdDebug() << timestamp() << "AlsaPlayer::" << s << endl; \ +} while (0) +#else +#define ERR(args...) do {\ + QString dbgStr;\ + QString s = dbgStr.sprintf( "%s:%d: ERROR ", __FUNCTION__, __LINE__); \ + s += dbgStr.sprintf( ##args ); \ + kdDebug() << timestamp() << "AlsaPlayer::" << s << endl; \ +} while (0) +#endif + +#if !defined(__GNUC__) || __GNUC__ >= 3 +#define MSG(...) do {\ + if (m_debugLevel >= 1) {\ + QString dbgStr; \ + QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \ + s += dbgStr.sprintf( __VA_ARGS__); \ + kdDebug() << timestamp() << "AlsaPlayer::" << s << endl; \ + }; \ +} while (0) +#else +#define MSG(args...) do {\ + if (m_debugLevel >= 1) {\ + QString dbgStr; \ + QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \ + s += dbgStr.sprintf( ##args ); \ + kdDebug() << timestamp() << "AlsaPlayer::" << s << endl; \ + }; \ +} while (0) +#endif + +#if !defined(__GNUC__) || __GNUC__ >= 3 +#define DBG(...) do {\ + if (m_debugLevel >= 2) {\ + QString dbgStr; \ + QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \ + s += dbgStr.sprintf( __VA_ARGS__); \ + kdDebug() << timestamp() << "AlsaPlayer::" << s << endl; \ + }; \ +} while (0) +#else +#define DBG(args...) do {\ + if (m_debugLevel >= 2) {\ + QString dbgStr; \ + QString s = dbgStr.sprintf( "%s:%d: ", __FUNCTION__, __LINE__); \ + s += dbgStr.sprintf( ##args ); \ + kdDebug() << timestamp() << "AlsaPlayer::" << s << endl; \ + }; \ +} while (0) +#endif + +QString AlsaPlayer::timestamp() const +{ + time_t t; + struct timeval tv; + char *tstr; + t = time(NULL); + tstr = strdup(ctime(&t)); + tstr[strlen(tstr)-1] = 0; + gettimeofday(&tv,NULL); + QString ts; + ts.sprintf(" %s [%d] ",tstr, (int) tv.tv_usec); + free(tstr); + return ts; +} + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +AlsaPlayer::AlsaPlayer(QObject* parent, const char* name, const QStringList& args) : + Player(parent, name, args), + m_currentVolume(1.0), + m_pcmName("default"), + m_defPeriodSize(128), + m_defPeriods(8), + m_debugLevel(1), + m_simulatedPause(false) +{ + init(); +} + +AlsaPlayer::~AlsaPlayer() +{ + if (running()) { + stop(); + wait(); + } +} + +//void AlsaPlayer::play(const FileHandle &file) +void AlsaPlayer::startPlay(const QString &file) +{ + if (running()) { + if (paused()) { + if (canPause) + snd_pcm_pause(handle, false); + else + m_simulatedPause = false; + } + return; + } + audiofile.setName(file); + audiofile.open(IO_ReadOnly); + fd = audiofile.handle(); + // Start thread running. + start(); +} + +/*virtual*/ void AlsaPlayer::run() +{ + QString pName = m_pcmName.section(" ", 0, 0); + DBG("pName = %s", pName.ascii()); + pcm_name = qstrdup(pName.ascii()); + int err; + snd_pcm_info_t *info; + + m_simulatedPause = false; + + snd_pcm_info_alloca(&info); + + err = snd_output_stdio_attach(&log, stderr, 0); + assert(err >= 0); + + rhwdata.format = DEFAULT_FORMAT; + rhwdata.rate = DEFAULT_SPEED; + rhwdata.channels = 1; + + err = snd_pcm_open(&handle, pcm_name, stream, open_mode); + if (err < 0) { + ERR("audio open error on pcm device %s: %s", pcm_name, snd_strerror(err)); + return; + } + + if ((err = snd_pcm_info(handle, info)) < 0) { + ERR("info error: %s", snd_strerror(err)); + return; + } + + chunk_size = 1024; + hwdata = rhwdata; + + audioBuffer.resize(1024); + // audiobuf = (char *)malloc(1024); + audiobuf = audioBuffer.data(); + if (audiobuf == NULL) { + ERR("not enough memory"); + return; + } + + if (mmap_flag) { + writei_func = snd_pcm_mmap_writei; + readi_func = snd_pcm_mmap_readi; + writen_func = snd_pcm_mmap_writen; + readn_func = snd_pcm_mmap_readn; + } else { + writei_func = snd_pcm_writei; + readi_func = snd_pcm_readi; + writen_func = snd_pcm_writen; + readn_func = snd_pcm_readn; + } + + playback(fd); + cleanup(); + return; +} + +void AlsaPlayer::pause() +{ + if (running()) { + DBG("Pause requested"); + m_mutex.lock(); + if (handle) { + // Some hardware can pause; some can't. canPause is set in set_params. + if (canPause) { + m_simulatedPause = false; + snd_pcm_pause(handle, true); + m_mutex.unlock(); + } else { + // Set a flag and cause wait_for_poll to sleep. When resumed, will get + // an underrun. + m_simulatedPause = true; + m_mutex.unlock(); + } + } + } +} + +void AlsaPlayer::stop() +{ + if (running()) { + DBG("STOP! Locking mutex"); + m_mutex.lock(); + m_simulatedPause = false; + if (handle) { + /* This constant is arbitrary */ + char buf = 42; + DBG("Request for stop, device state is %s", + snd_pcm_state_name(snd_pcm_state(handle))); + write(alsa_stop_pipe[1], &buf, 1); + } + DBG("unlocking mutex"); + m_mutex.unlock(); + /* Wait for thread to exit */ + DBG("waiting for thread to exit"); + wait(); + DBG("cleaning up"); + } + cleanup(); +} + +/* + * Stop playback, cleanup and exit thread. + */ +void AlsaPlayer::stopAndExit() +{ + // if (handle) snd_pcm_drop(handle); + cleanup(); + exit(); +} + +void AlsaPlayer::setVolume(float volume) +{ + m_currentVolume = volume; +} + +float AlsaPlayer::volume() const +{ + return m_currentVolume; +} + +///////////////////////////////////////////////////////////////////////////////// +// player status functions +///////////////////////////////////////////////////////////////////////////////// + +bool AlsaPlayer::playing() const +{ + bool result = false; + if (running()) { + m_mutex.lock(); + if (handle) { + if (canPause) { + snd_pcm_status_t *status; + snd_pcm_status_alloca(&status); + int res; + if ((res = snd_pcm_status(handle, status)) < 0) + ERR("status error: %s", snd_strerror(res)); + else { + result = (SND_PCM_STATE_RUNNING == snd_pcm_status_get_state(status)) + || (SND_PCM_STATE_DRAINING == snd_pcm_status_get_state(status)); + DBG("state = %s", snd_pcm_state_name(snd_pcm_status_get_state(status))); + } + } else + result = !m_simulatedPause; + } + m_mutex.unlock(); + } + return result; +} + +bool AlsaPlayer::paused() const +{ + bool result = false; + if (running()) { + m_mutex.lock(); + if (handle) { + if (canPause) { + snd_pcm_status_t *status; + snd_pcm_status_alloca(&status); + int res; + if ((res = snd_pcm_status(handle, status)) < 0) + ERR("status error: %s", snd_strerror(res)); + else { + result = (SND_PCM_STATE_PAUSED == snd_pcm_status_get_state(status)); + DBG("state = %s", snd_pcm_state_name(snd_pcm_status_get_state(status))); + } + } else + result = m_simulatedPause; + } + m_mutex.unlock(); + } + return result; +} + +int AlsaPlayer::totalTime() const +{ + int total = 0; + int rate = hwdata.rate; + int channels = hwdata.channels; + if (rate > 0 && channels > 0) { + total = int((double(pbrec_count) / rate) / channels); + // DBG("pbrec_count = %i rate =%i channels = %i", pbrec_count, rate, channels); + // DBG("totalTime = %i", total); + } + return total; +} + +int AlsaPlayer::currentTime() const +{ + int current = 0; + int rate = hwdata.rate; + int channels = hwdata.channels; + if (rate > 0 && channels > 0) { + current = int((double(fdcount) / rate) / channels); + // DBG("fdcount = %i rate = %i channels = %i", fdcount, rate, channels); + // DBG("currentTime = %i", current); + } + return current; +} + +int AlsaPlayer::position() const +{ + // TODO: Make this more accurate by adding frames that have been so-far + // played within the Alsa ring buffer. + return pbrec_count > 0 ? int(double(fdcount) * 1000 / pbrec_count + .5) : 0; +} + +///////////////////////////////////////////////////////////////////////////////// +// player seek functions +///////////////////////////////////////////////////////////////////////////////// + +void AlsaPlayer::seek(int /*seekTime*/) +{ + // TODO: +} + +void AlsaPlayer::seekPosition(int /*position*/) +{ + // TODO: +} + +/* + * Returns a list of PCM devices. + * This function fills the specified list with ALSA hardware soundcards found on the system. + * It uses plughw:xx instead of hw:xx for specifiers, because hw:xx are not practical to + * use (e.g. they require a resampler/channel mixer in the application). + */ +QStringList AlsaPlayer::getPluginList( const QCString& /*classname*/ ) +{ + int err = 0; + int card = -1, device = -1; + snd_ctl_t *handle; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + QStringList result; + + result.append("default"); + for (;;) { + err = snd_card_next(&card); + if (err < 0 || card < 0) break; + if (card >= 0) { + char name[32]; + sprintf(name, "hw:%i", card); + if ((err = snd_ctl_open(&handle, name, 0)) < 0) continue; + if ((err = snd_ctl_card_info(handle, info)) < 0) { + snd_ctl_close(handle); + continue; + } + for (int devCnt=0;;++devCnt) { + err = snd_ctl_pcm_next_device(handle, &device); + if (err < 0 || device < 0) break; + + snd_pcm_info_set_device(pcminfo, device); + snd_pcm_info_set_subdevice(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); + if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) continue; + QString infoName = " "; + infoName += snd_ctl_card_info_get_name(info); + infoName += " ("; + infoName += snd_pcm_info_get_name(pcminfo); + infoName += ")"; + if (0 == devCnt) { + QString pcmName = QString("default:%1").arg(card); + result.append(pcmName + infoName); + } + QString pcmName = QString("plughw:%1,%2").arg(card).arg(device); + result.append(pcmName + infoName); + } + snd_ctl_close(handle); + } + } + return result; +} + +// QStringList AlsaPlayer::getPluginList( const QCString& /*classname*/ ) +// { +// QStringList assumed("default"); +// snd_config_t *conf; +// int err = snd_config_update(); +// if (err < 0) { +// ERR("snd_config_update: %s", snd_strerror(err)); +// return assumed; +// } +// err = snd_config_search(snd_config, "pcm", &conf); +// if (err < 0) return QStringList(); +// snd_config_iterator_t it = snd_config_iterator_first(conf); +// snd_config_iterator_t itEnd = snd_config_iterator_end(conf); +// const char* id; +// snd_config_t *entry; +// QStringList result; +// snd_ctl_card_info_t *info; +// snd_ctl_card_info_alloca(&info); +// snd_pcm_info_t *pcminfo; +// snd_pcm_info_alloca(&pcminfo); +// while (it != itEnd) { +// entry = snd_config_iterator_entry(it); +// err = snd_config_get_id(entry, &id); +// if (err >= 0) { +// if (QString(id) != "default") +// { +// int card = -1; +// while (snd_card_next(&card) >= 0 && card >= 0) { +// char name[32]; +// sprintf(name, "%s:%d", id, card); +// DBG("Checking %s", name); +// snd_ctl_t *handle; +// if ((err = snd_ctl_open(&handle, name, SND_CTL_NONBLOCK)) >= 0) { +// if ((err = snd_ctl_card_info(handle, info)) >= 0) { +// int dev = -1; +// snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; +// while (snd_ctl_pcm_next_device(handle, &dev) >= 0 && dev >= 0) { +// snd_pcm_info_set_device(pcminfo, dev); +// snd_pcm_info_set_subdevice(pcminfo, 0); +// snd_pcm_info_set_stream(pcminfo, stream); +// if ((err = snd_ctl_pcm_info(handle, pcminfo)) >= 0) { +// QString pluginName = name; +// pluginName += ","; +// pluginName += QString::number(dev); +// pluginName += " "; +// pluginName += snd_ctl_card_info_get_name(info); +// pluginName += ","; +// pluginName += snd_pcm_info_get_name(pcminfo); +// result.append(pluginName); +// // DBG(pluginName); +// } +// } +// } +// snd_ctl_close(handle); +// } +// } +// if (card == -1) result.append(id); +// } else result.append(id); +// } +// it = snd_config_iterator_next(it); +// } +// snd_config_update_free_global(); +// return result; +// } + +void AlsaPlayer::setSinkName(const QString& sinkName) { m_pcmName = sinkName; } + +///////////////////////////////////////////////////////////////////////////////// +// private +///////////////////////////////////////////////////////////////////////////////// + +void AlsaPlayer::init() +{ + pcm_name = 0; + handle = 0; + canPause = false; + timelimit = 0; + file_type = FORMAT_DEFAULT; + sleep_min = 0; + // open_mode = 0; + open_mode = SND_PCM_NONBLOCK; + stream = SND_PCM_STREAM_PLAYBACK; + mmap_flag = 0; + interleaved = 1; + audiobuf = NULL; + chunk_size = 0; + period_time = 0; + buffer_time = 0; + avail_min = -1; + start_delay = 0; + stop_delay = 0; + buffer_pos = 0; + log = 0; + fd = -1; + pbrec_count = LLONG_MAX; + alsa_stop_pipe[0] = 0; + alsa_stop_pipe[1] = 0; + alsa_poll_fds = 0; + m_simulatedPause = false; +} + +void AlsaPlayer::cleanup() +{ + DBG("cleaning up"); + m_mutex.lock(); + if (pcm_name) free(pcm_name); + if (fd >= 0) audiofile.close(); + if (handle) { + snd_pcm_drop(handle); + snd_pcm_close(handle); + } + if (alsa_stop_pipe[0]) close(alsa_stop_pipe[0]); + if (alsa_stop_pipe[1]) close(alsa_stop_pipe[1]); + if (audiobuf) audioBuffer.resize(0); + if (alsa_poll_fds) alsa_poll_fds_barray.resize(0); + if (log) snd_output_close(log); + snd_config_update_free_global(); + init(); + m_mutex.unlock(); +} + +/* + * Safe read (for pipes) + */ + +ssize_t AlsaPlayer::safe_read(int fd, void *buf, size_t count) +{ + ssize_t result = 0; + ssize_t res; + + while (count > 0) { + if ((res = read(fd, buf, count)) == 0) + break; + if (res < 0) + return result > 0 ? result : res; + count -= res; + result += res; + buf = (char *)buf + res; + } + return result; +} + +/* + * Test, if it is a .VOC file and return >=0 if ok (this is the length of rest) + * < 0 if not + */ +int AlsaPlayer::test_vocfile(void *buffer) +{ + VocHeader *vp = (VocHeader*)buffer; + + if (!memcmp(vp->magic, VOC_MAGIC_STRING, 20)) { + vocminor = LE_SHORT(vp->version) & 0xFF; + vocmajor = LE_SHORT(vp->version) / 256; + if (LE_SHORT(vp->version) != (0x1233 - LE_SHORT(vp->coded_ver))) + return -2; /* coded version mismatch */ + return LE_SHORT(vp->headerlen) - sizeof(VocHeader); /* 0 mostly */ + } + return -1; /* magic string fail */ +} + +/* + * helper for test_wavefile + */ + +size_t AlsaPlayer::test_wavefile_read(int fd, char *buffer, size_t *size, size_t reqsize, int line) +{ + if (*size >= reqsize) + return *size; + if ((size_t)safe_read(fd, buffer + *size, reqsize - *size) != reqsize - *size) { + ERR("read error (called from line %i)", line); + stopAndExit(); + } + return *size = reqsize; +} + +#define check_wavefile_space(buffer, len, blimit) \ + if (len > blimit) { \ + blimit = len; \ + if ((buffer = (char*)realloc(buffer, blimit)) == NULL) { \ + ERR("not enough memory"); \ + stopAndExit(); \ + } \ + } + +/* + * test, if it's a .WAV file, > 0 if ok (and set the speed, stereo etc.) + * == 0 if not + * Value returned is bytes to be discarded. + */ +ssize_t AlsaPlayer::test_wavefile(int fd, char *_buffer, size_t size) +{ + WaveHeader *h = (WaveHeader *)_buffer; + char *buffer = NULL; + size_t blimit = 0; + WaveFmtBody *f; + WaveChunkHeader *c; + u_int type; + u_int len; + + if (size < sizeof(WaveHeader)) + return -1; + if (h->magic != WAV_RIFF || h->type != WAV_WAVE) + return -1; + if (size > sizeof(WaveHeader)) { + check_wavefile_space(buffer, size - sizeof(WaveHeader), blimit); + memcpy(buffer, _buffer + sizeof(WaveHeader), size - sizeof(WaveHeader)); + } + size -= sizeof(WaveHeader); + while (1) { + check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit); + test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__); + c = (WaveChunkHeader*)buffer; + type = c->type; + len = LE_INT(c->length); + len += len % 2; + if (size > sizeof(WaveChunkHeader)) + memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader)); + size -= sizeof(WaveChunkHeader); + if (type == WAV_FMT) + break; + check_wavefile_space(buffer, len, blimit); + test_wavefile_read(fd, buffer, &size, len, __LINE__); + if (size > len) + memmove(buffer, buffer + len, size - len); + size -= len; + } + + if (len < sizeof(WaveFmtBody)) { + ERR("unknown length of 'fmt ' chunk (read %u, should be %u at least)", len, (u_int)sizeof(WaveFmtBody)); + stopAndExit(); + } + check_wavefile_space(buffer, len, blimit); + test_wavefile_read(fd, buffer, &size, len, __LINE__); + f = (WaveFmtBody*) buffer; + if (LE_SHORT(f->format) != WAV_PCM_CODE) { + ERR("can't play not PCM-coded WAVE-files"); + stopAndExit(); + } + if (LE_SHORT(f->modus) < 1) { + ERR("can't play WAVE-files with %d tracks", LE_SHORT(f->modus)); + stopAndExit(); + } + hwdata.channels = LE_SHORT(f->modus); + switch (LE_SHORT(f->bit_p_spl)) { + case 8: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_U8) + MSG("Warning: format is changed to U8"); + hwdata.format = SND_PCM_FORMAT_U8; + break; + case 16: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_S16_LE) + MSG("Warning: format is changed to S16_LE"); + hwdata.format = SND_PCM_FORMAT_S16_LE; + break; + case 24: + switch (LE_SHORT(f->byte_p_spl) / hwdata.channels) { + case 3: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_S24_3LE) + MSG("Warning: format is changed to S24_3LE"); + hwdata.format = SND_PCM_FORMAT_S24_3LE; + break; + case 4: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_S24_LE) + MSG("Warning: format is changed to S24_LE"); + hwdata.format = SND_PCM_FORMAT_S24_LE; + break; + default: + ERR("can't play WAVE-files with sample %d bits in %d bytes wide (%d channels)", LE_SHORT(f->bit_p_spl), LE_SHORT(f->byte_p_spl), hwdata.channels); + stopAndExit(); + } + break; + case 32: + hwdata.format = SND_PCM_FORMAT_S32_LE; + break; + default: + ERR("can't play WAVE-files with sample %d bits wide", LE_SHORT(f->bit_p_spl)); + stopAndExit(); + } + hwdata.rate = LE_INT(f->sample_fq); + + if (size > len) + memmove(buffer, buffer + len, size - len); + size -= len; + + while (1) { + u_int type, len; + + check_wavefile_space(buffer, sizeof(WaveChunkHeader), blimit); + test_wavefile_read(fd, buffer, &size, sizeof(WaveChunkHeader), __LINE__); + c = (WaveChunkHeader*)buffer; + type = c->type; + len = LE_INT(c->length); + if (size > sizeof(WaveChunkHeader)) + memmove(buffer, buffer + sizeof(WaveChunkHeader), size - sizeof(WaveChunkHeader)); + size -= sizeof(WaveChunkHeader); + if (type == WAV_DATA) { + if (len < pbrec_count && len < 0x7ffffffe) + pbrec_count = len; + if (size > 0) + memcpy(_buffer, buffer, size); + free(buffer); + return size; + } + len += len % 2; + check_wavefile_space(buffer, len, blimit); + test_wavefile_read(fd, buffer, &size, len, __LINE__); + if (size > len) + memmove(buffer, buffer + len, size - len); + size -= len; + } + + /* shouldn't be reached */ + return -1; +} + +/* + * Test for AU file. + */ + +int AlsaPlayer::test_au(int fd, char *buffer) +{ + AuHeader *ap = (AuHeader*)buffer; + + if (ap->magic != AU_MAGIC) + return -1; + if (BE_INT(ap->hdr_size) > 128 || BE_INT(ap->hdr_size) < 24) + return -1; + pbrec_count = BE_INT(ap->data_size); + switch (BE_INT(ap->encoding)) { + case AU_FMT_ULAW: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_MU_LAW) + MSG("Warning: format is changed to MU_LAW"); + hwdata.format = SND_PCM_FORMAT_MU_LAW; + break; + case AU_FMT_LIN8: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_U8) + MSG("Warning: format is changed to U8"); + hwdata.format = SND_PCM_FORMAT_U8; + break; + case AU_FMT_LIN16: + if (hwdata.format != DEFAULT_FORMAT && + hwdata.format != SND_PCM_FORMAT_S16_BE) + MSG("Warning: format is changed to S16_BE"); + hwdata.format = SND_PCM_FORMAT_S16_BE; + break; + default: + return -1; + } + hwdata.rate = BE_INT(ap->sample_rate); + if (hwdata.rate < 2000 || hwdata.rate > 256000) + return -1; + hwdata.channels = BE_INT(ap->channels); + if (hwdata.channels < 1 || hwdata.channels > 128) + return -1; + if ((size_t)safe_read(fd, buffer + sizeof(AuHeader), BE_INT(ap->hdr_size) - sizeof(AuHeader)) != BE_INT(ap->hdr_size) - sizeof(AuHeader)) { + ERR("read error"); + stopAndExit(); + } + return 0; +} + +void AlsaPlayer::set_params(void) +{ + snd_pcm_hw_params_t *hwparams; + snd_pcm_uframes_t period_size; + int err; + int dir; + unsigned int rate; + unsigned int periods; + + snd_pcm_hw_params_alloca(&hwparams); + err = snd_pcm_hw_params_any(handle, hwparams); + if (err < 0) { + ERR("Broken configuration for this PCM: no configurations available"); + stopAndExit(); + } + + /* Create the pipe for communication about stop requests. */ + if (pipe(alsa_stop_pipe)) { + ERR("Stop pipe creation failed (%s)", strerror(errno)); + stopAndExit(); + } + + /* Find how many descriptors we will get for poll(). */ + alsa_fd_count = snd_pcm_poll_descriptors_count(handle); + if (alsa_fd_count <= 0){ + ERR("Invalid poll descriptors count returned from ALSA."); + stopAndExit(); + } + + /* Create and fill in struct pollfd *alsa_poll_fds with ALSA descriptors. */ + // alsa_poll_fds = (pollfd *)malloc ((alsa_fd_count + 1) * sizeof(struct pollfd)); + alsa_poll_fds_barray.resize((alsa_fd_count + 1) * sizeof(struct pollfd)); + alsa_poll_fds = (pollfd *)alsa_poll_fds_barray.data(); + assert(alsa_poll_fds); + if ((err = snd_pcm_poll_descriptors(handle, alsa_poll_fds, alsa_fd_count)) < 0) { + ERR("Unable to obtain poll descriptors for playback: %s", snd_strerror(err)); + stopAndExit(); + } + + /* Create a new pollfd structure for requests by alsa_stop(). */ + struct pollfd alsa_stop_pipe_pfd; + alsa_stop_pipe_pfd.fd = alsa_stop_pipe[0]; + alsa_stop_pipe_pfd.events = POLLIN; + alsa_stop_pipe_pfd.revents = 0; + + /* Join this our own pollfd to the ALSAs ones. */ + alsa_poll_fds[alsa_fd_count] = alsa_stop_pipe_pfd; + ++alsa_fd_count; + + if (mmap_flag) { + snd_pcm_access_mask_t *mask = (snd_pcm_access_mask_t *)alloca(snd_pcm_access_mask_sizeof()); + snd_pcm_access_mask_none(mask); + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_INTERLEAVED); + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED); + snd_pcm_access_mask_set(mask, SND_PCM_ACCESS_MMAP_COMPLEX); + err = snd_pcm_hw_params_set_access_mask(handle, hwparams, mask); + } else if (interleaved) + err = snd_pcm_hw_params_set_access(handle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + else + err = snd_pcm_hw_params_set_access(handle, hwparams, + SND_PCM_ACCESS_RW_NONINTERLEAVED); + if (err < 0) { + ERR("Error setting access type: %s", snd_strerror(err)); + stopAndExit(); + } + err = snd_pcm_hw_params_set_format(handle, hwparams, hwdata.format); + if (err < 0) { + ERR("Error setting sample format to %i: %s", hwdata.format, snd_strerror(err)); + stopAndExit(); + } + err = snd_pcm_hw_params_set_channels(handle, hwparams, hwdata.channels); + if (err < 0) { + ERR("Error setting channel count to %i: %s", hwdata.channels, snd_strerror(err)); + stopAndExit(); + } + +#if 0 + err = snd_pcm_hw_params_set_periods_min(handle, hwparams, 2); + assert(err >= 0); +#endif + rate = hwdata.rate; +#if SND_LIB_MAJOR >= 1 + err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &hwdata.rate, 0); +#else + err = snd_pcm_hw_params_set_rate_near(handle, hwparams, hwdata.rate, 0); +#endif + assert(err >= 0); + if ((float)rate * 1.05 < hwdata.rate || (float)rate * 0.95 > hwdata.rate) { + MSG("Warning: rate is not accurate (requested = %iHz, got = %iHz)", rate, hwdata.rate); + MSG(" please, try the plug plugin (-Dplug:%s)", snd_pcm_name(handle)); + } + + period_size = m_defPeriodSize; + dir = 1; +#if SND_LIB_MAJOR >= 1 + err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, &dir); +#else + err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, period_size, &dir); +#endif + if (err < 0) { + MSG("Setting period_size to %lu failed, but continuing: %s", period_size, snd_strerror(err)); + } + + periods = m_defPeriods; + dir = 1; +#if SND_LIB_MAJOR >= 1 + err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &periods, &dir); +#else + err = snd_pcm_hw_params_set_periods_near(handle, hwparams, periods, &dir); +#endif + if (err < 0) + MSG("Unable to set number of periods to %i, but continuing: %s", periods, snd_strerror(err)); + + /* Install hw parameters. */ + err = snd_pcm_hw_params(handle, hwparams); + if (err < 0) { + MSG("Unable to install hw params: %s", snd_strerror(err)); + snd_pcm_hw_params_dump(hwparams, log); + stopAndExit(); + } + + /* Determine if device can pause. */ + canPause = (1 == snd_pcm_hw_params_can_pause(hwparams)); + + /* Get final buffer size and calculate the chunk size we will pass to device. */ +#if SND_LIB_MAJOR >= 1 + snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); +#else + buffer_size = snd_pcm_hw_params_get_buffer_size(hwparams); +#endif + chunk_size = periods * period_size; + + if (0 == chunk_size) { + ERR("Invalid periods or period_size. Cannot continue."); + stopAndExit(); + } + + if (chunk_size == buffer_size) + MSG("WARNING: Shouldn't use chunk_size equal to buffer_size (%lu). Continuing anyway.", chunk_size); + + DBG("Final buffer_size = %lu, chunk_size = %lu, periods = %i, period_size = %lu, canPause = %i", + buffer_size, chunk_size, periods, period_size, canPause); + + if (m_debugLevel >= 2) + snd_pcm_dump(handle, log); + + bits_per_sample = snd_pcm_format_physical_width(hwdata.format); + bits_per_frame = bits_per_sample * hwdata.channels; + chunk_bytes = chunk_size * bits_per_frame / 8; + audioBuffer.resize(chunk_bytes); + audiobuf = audioBuffer.data(); + if (audiobuf == NULL) { + ERR("not enough memory"); + stopAndExit(); + } +} + +#ifndef timersub +#define timersub(a, b, result) \ +do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ +} while (0) +#endif + +/* I/O error handler */ +void AlsaPlayer::xrun() +{ + snd_pcm_status_t *status; + int res; + + snd_pcm_status_alloca(&status); + if ((res = snd_pcm_status(handle, status))<0) { + ERR("status error: %s", snd_strerror(res)); + stopAndExit(); + } + if (SND_PCM_STATE_XRUN == snd_pcm_status_get_state(status)) { + struct timeval now, diff, tstamp; + gettimeofday(&now, 0); + snd_pcm_status_get_trigger_tstamp(status, &tstamp); + timersub(&now, &tstamp, &diff); + MSG("%s!!! (at least %.3f ms long)", + stream == SND_PCM_STREAM_PLAYBACK ? "underrun" : "overrun", + diff.tv_sec * 1000 + diff.tv_usec / 1000.0); + if (m_debugLevel >= 2) { + DBG("Status:"); + snd_pcm_status_dump(status, log); + } + if ((res = snd_pcm_prepare(handle))<0) { + ERR("xrun: prepare error: %s", snd_strerror(res)); + stopAndExit(); + } + return; /* ok, data should be accepted again */ + } if (SND_PCM_STATE_DRAINING == snd_pcm_status_get_state(status)) { + if (m_debugLevel >= 2) { + DBG("Status(DRAINING):"); + snd_pcm_status_dump(status, log); + } + if (stream == SND_PCM_STREAM_CAPTURE) { + MSG("capture stream format change? attempting recover..."); + if ((res = snd_pcm_prepare(handle))<0) { + ERR("xrun(DRAINING): prepare error: %s", snd_strerror(res)); + stopAndExit(); + } + return; + } + } + if (m_debugLevel >= 2) { + DBG("Status(R/W):"); + snd_pcm_status_dump(status, log); + } + ERR("read/write error, state = %s", snd_pcm_state_name(snd_pcm_status_get_state(status))); + stopAndExit(); +} + +/* I/O suspend handler */ +void AlsaPlayer::suspend(void) +{ + int res; + + MSG("Suspended. Trying resume. "); + while ((res = snd_pcm_resume(handle)) == -EAGAIN) + sleep(1); /* wait until suspend flag is released */ + if (res < 0) { + MSG("Failed. Restarting stream. "); + if ((res = snd_pcm_prepare(handle)) < 0) { + ERR("suspend: prepare error: %s", snd_strerror(res)); + stopAndExit(); + } + } + MSG("Suspend done."); +} + +/* peak handler */ +void AlsaPlayer::compute_max_peak(char *data, size_t count) +{ + signed int val, max, max_peak = 0, perc; + size_t ocount = count; + + switch (bits_per_sample) { + case 8: { + signed char *valp = (signed char *)data; + signed char mask = snd_pcm_format_silence(hwdata.format); + while (count-- > 0) { + val = *valp++ ^ mask; + val = abs(val); + if (max_peak < val) + max_peak = val; + } + break; + } + case 16: { + signed short *valp = (signed short *)data; + signed short mask = snd_pcm_format_silence_16(hwdata.format); + count /= 2; + while (count-- > 0) { + val = *valp++ ^ mask; + val = abs(val); + if (max_peak < val) + max_peak = val; + } + break; + } + case 32: { + signed int *valp = (signed int *)data; + signed int mask = snd_pcm_format_silence_32(hwdata.format); + count /= 4; + while (count-- > 0) { + val = *valp++ ^ mask; + val = abs(val); + if (max_peak < val) + max_peak = val; + } + break; + } + default: + break; + } + max = 1 << (bits_per_sample-1); + if (max <= 0) + max = 0x7fffffff; + DBG("Max peak (%li samples): %05i (0x%04x) ", (long)ocount, max_peak, max_peak); + if (bits_per_sample > 16) + perc = max_peak / (max / 100); + else + perc = max_peak * 100 / max; + for (val = 0; val < 20; val++) + if (val <= perc / 5) + kdDebug() << '#'; + else + kdDebug() << ' '; + DBG(" %i%%", perc); +} + +/* + * Write to the ALSA pcm. + */ + +ssize_t AlsaPlayer::pcm_write(char *data, size_t count) +{ + ssize_t r; + ssize_t result = 0; + + if (sleep_min == 0 && count < chunk_size) { + DBG("calling snd_pcm_format_set_silence"); + snd_pcm_format_set_silence(hwdata.format, data + count * bits_per_frame / 8, (chunk_size - count) * hwdata.channels); + count = chunk_size; + } + while (count > 0) { + DBG("calling writei_func, count = %i", count); + r = writei_func(handle, data, count); + DBG("writei_func returned %i", r); + if (-EAGAIN == r || (r >= 0 && (size_t)r < count)) { + DBG("r = %i calling snd_pcm_wait", r); + snd_pcm_wait(handle, 100); + } else if (-EPIPE == r) { + xrun(); + } else if (-ESTRPIPE == r) { + suspend(); + } else if (-EBUSY == r){ + MSG("WARNING: sleeping while PCM BUSY"); + usleep(1000); + continue; + } else if (r < 0) { + ERR("write error: %s", snd_strerror(r)); + stopAndExit(); + } + if (r > 0) { + if (m_debugLevel >= 1) + compute_max_peak(data, r * hwdata.channels); + result += r; + count -= r; + data += r * bits_per_frame / 8; + } + /* Report current state */ + DBG("PCM state before polling: %s", + snd_pcm_state_name(snd_pcm_state(handle))); + + int err = wait_for_poll(0); + if (err < 0) { + ERR("Wait for poll() failed"); + return -1; + } + else if (err == 1){ + MSG("Playback stopped"); + /* Drop the playback on the sound device (probably + still in progress up till now) */ + err = snd_pcm_drop(handle); + if (err < 0) { + ERR("snd_pcm_drop() failed: %s", snd_strerror(err)); + return -1; + } + return -1; + } + } + return result; +} + +/* + * ok, let's play a .voc file + */ + +ssize_t AlsaPlayer::voc_pcm_write(u_char *data, size_t count) +{ + ssize_t result = count, r; + size_t size; + + while (count > 0) { + size = count; + if (size > chunk_bytes - buffer_pos) + size = chunk_bytes - buffer_pos; + memcpy(audiobuf + buffer_pos, data, size); + data += size; + count -= size; + buffer_pos += size; + if ((size_t)buffer_pos == chunk_bytes) { + if ((size_t)(r = pcm_write(audiobuf, chunk_size)) != chunk_size) + return r; + buffer_pos = 0; + } + } + return result; +} + +void AlsaPlayer::voc_write_silence(unsigned x) +{ + unsigned l; + char *buf; + + QByteArray buffer(chunk_bytes); + // buf = (char *) malloc(chunk_bytes); + buf = buffer.data(); + if (buf == NULL) { + ERR("can't allocate buffer for silence"); + return; /* not fatal error */ + } + snd_pcm_format_set_silence(hwdata.format, buf, chunk_size * hwdata.channels); + while (x > 0) { + l = x; + if (l > chunk_size) + l = chunk_size; + if (voc_pcm_write((u_char*)buf, l) != (ssize_t)l) { + ERR("write error"); + stopAndExit(); + } + x -= l; + } + // free(buf); +} + +void AlsaPlayer::voc_pcm_flush(void) +{ + if (buffer_pos > 0) { + size_t b; + if (sleep_min == 0) { + if (snd_pcm_format_set_silence(hwdata.format, audiobuf + buffer_pos, chunk_bytes - buffer_pos * 8 / bits_per_sample) < 0) + MSG("voc_pcm_flush - silence error"); + b = chunk_size; + } else { + b = buffer_pos * 8 / bits_per_frame; + } + if (pcm_write(audiobuf, b) != (ssize_t)b) + ERR("voc_pcm_flush error"); + } + snd_pcm_drain(handle); +} + +void AlsaPlayer::voc_play(int fd, int ofs, const char* name) +{ + int l; + VocBlockType *bp; + VocVoiceData *vd; + VocExtBlock *eb; + size_t nextblock, in_buffer; + u_char *data, *buf; + char was_extended = 0, output = 0; + u_short *sp, repeat = 0; + size_t silence; + off64_t filepos = 0; + +#define COUNT(x) nextblock -= x; in_buffer -= x; data += x +#define COUNT1(x) in_buffer -= x; data += x + + QByteArray buffer(64 * 1024); + // data = buf = (u_char *)malloc(64 * 1024); + data = buf = (u_char*)buffer.data(); + buffer_pos = 0; + if (data == NULL) { + ERR("malloc error"); + stopAndExit(); + } + MSG("Playing Creative Labs Channel file '%s'...", name); + /* first we waste the rest of header, ugly but we don't need seek */ + while (ofs > (ssize_t)chunk_bytes) { + if ((size_t)safe_read(fd, buf, chunk_bytes) != chunk_bytes) { + ERR("read error"); + stopAndExit(); + } + ofs -= chunk_bytes; + } + if (ofs) { + if (safe_read(fd, buf, ofs) != ofs) { + ERR("read error"); + stopAndExit(); + } + } + hwdata.format = DEFAULT_FORMAT; + hwdata.channels = 1; + hwdata.rate = DEFAULT_SPEED; + set_params(); + + in_buffer = nextblock = 0; + while (1) { + Fill_the_buffer: /* need this for repeat */ + if (in_buffer < 32) { + /* move the rest of buffer to pos 0 and fill the buf up */ + if (in_buffer) + memcpy(buf, data, in_buffer); + data = buf; + if ((l = safe_read(fd, buf + in_buffer, chunk_bytes - in_buffer)) > 0) + in_buffer += l; + else if (!in_buffer) { + /* the file is truncated, so simulate 'Terminator' + and reduce the datablock for safe landing */ + nextblock = buf[0] = 0; + if (l == -1) { +// perror(name); + stopAndExit(); + } + } + } + while (!nextblock) { /* this is a new block */ + if (in_buffer < sizeof(VocBlockType)) + goto __end; + bp = (VocBlockType *) data; + COUNT1(sizeof(VocBlockType)); + nextblock = VOC_DATALEN(bp); + if (output) + MSG(" "); /* write /n after ASCII-out */ + output = 0; + switch (bp->type) { + case 0: +#if 0 + MSG("Terminator"); +#endif + return; /* VOC-file stop */ + case 1: + vd = (VocVoiceData *) data; + COUNT1(sizeof(VocVoiceData)); + /* we need a SYNC, before we can set new SPEED, STEREO ... */ + + if (!was_extended) { + hwdata.rate = (int) (vd->tc); + hwdata.rate = 1000000 / (256 - hwdata.rate); +#if 0 + MSG("Channel data %d Hz", dsp_speed); +#endif + if (vd->pack) { /* /dev/dsp can't it */ + ERR("can't play packed .voc files"); + return; + } + if (hwdata.channels == 2) /* if we are in Stereo-Mode, switch back */ + hwdata.channels = 1; + } else { /* there was extended block */ + hwdata.channels = 2; + was_extended = 0; + } + set_params(); + break; + case 2: /* nothing to do, pure data */ +#if 0 + MSG("Channel continuation"); +#endif + break; + case 3: /* a silence block, no data, only a count */ + sp = (u_short *) data; + COUNT1(sizeof(u_short)); + hwdata.rate = (int) (*data); + COUNT1(1); + hwdata.rate = 1000000 / (256 - hwdata.rate); + set_params(); + silence = (((size_t) * sp) * 1000) / hwdata.rate; +#if 0 + MSG("Silence for %d ms", (int) silence); +#endif + voc_write_silence(*sp); + break; + case 4: /* a marker for syncronisation, no effect */ + sp = (u_short *) data; + COUNT1(sizeof(u_short)); +#if 0 + MSG("Marker %d", *sp); +#endif + break; + case 5: /* ASCII text, we copy to stderr */ + output = 1; +#if 0 + MSG("ASCII - text :"); +#endif + break; + case 6: /* repeat marker, says repeatcount */ + /* my specs don't say it: maybe this can be recursive, but + I don't think somebody use it */ + repeat = *(u_short *) data; + COUNT1(sizeof(u_short)); +#if 0 + MSG("Repeat loop %d times", repeat); +#endif + if (filepos >= 0) { /* if < 0, one seek fails, why test another */ + if ((filepos = lseek64(fd, 0, 1)) < 0) { + ERR("can't play loops; %s isn't seekable", name); + repeat = 0; + } else { + filepos -= in_buffer; /* set filepos after repeat */ + } + } else { + repeat = 0; + } + break; + case 7: /* ok, lets repeat that be rewinding tape */ + if (repeat) { + if (repeat != 0xFFFF) { +#if 0 + MSG("Repeat loop %d", repeat); +#endif + --repeat; + } +#if 0 + else + MSG("Neverending loop"); +#endif + lseek64(fd, filepos, 0); + in_buffer = 0; /* clear the buffer */ + goto Fill_the_buffer; + } +#if 0 + else + MSG("End repeat loop"); +#endif + break; + case 8: /* the extension to play Stereo, I have SB 1.0 :-( */ + was_extended = 1; + eb = (VocExtBlock *) data; + COUNT1(sizeof(VocExtBlock)); + hwdata.rate = (int) (eb->tc); + hwdata.rate = 256000000L / (65536 - hwdata.rate); + hwdata.channels = eb->mode == VOC_MODE_STEREO ? 2 : 1; + if (hwdata.channels == 2) + hwdata.rate = hwdata.rate >> 1; + if (eb->pack) { /* /dev/dsp can't it */ + ERR("can't play packed .voc files"); + return; + } +#if 0 + MSG("Extended block %s %d Hz", + (eb->mode ? "Stereo" : "Mono"), dsp_speed); +#endif + break; + default: + ERR("unknown blocktype %d. terminate.", bp->type); + return; + } /* switch (bp->type) */ + } /* while (! nextblock) */ + /* put nextblock data bytes to dsp */ + l = in_buffer; + if (nextblock < (size_t)l) + l = nextblock; + if (l) { + if (output) { + if (write(2, data, l) != l) { /* to stderr */ + ERR("write error"); + stopAndExit(); + } + } else { + if (voc_pcm_write(data, l) != l) { + ERR("write error"); + stopAndExit(); + } + } + COUNT(l); + } + } /* while(1) */ + __end: + voc_pcm_flush(); + // free(buf); +} +/* that was a big one, perhaps somebody split it :-) */ + +/* setting the globals for playing raw data */ +void AlsaPlayer::init_raw_data(void) +{ + hwdata = rhwdata; +} + +/* calculate the data count to read from/to dsp */ +off64_t AlsaPlayer::calc_count(void) +{ + off64_t count; + + if (timelimit == 0) { + count = pbrec_count; + } else { + count = snd_pcm_format_size(hwdata.format, hwdata.rate * hwdata.channels); + count *= (off64_t)timelimit; + } + return count < pbrec_count ? count : pbrec_count; +} + +void AlsaPlayer::header(int /*rtype*/, const char* /*name*/) +{ +// fprintf(stderr, "%s %s '%s' : ", +// (stream == SND_PCM_STREAM_PLAYBACK) ? "Playing" : "Recording", +// fmt_rec_table[rtype].what, +// name); + QString channels; + if (hwdata.channels == 1) + channels = "Mono"; + else if (hwdata.channels == 2) + channels = "Stereo"; + else + channels = QString("Channels %1").arg(hwdata.channels); + DBG("Format: %s, Rate %d Hz, %s", + snd_pcm_format_description(hwdata.format), + hwdata.rate, + channels.ascii()); +} + +/* playing raw data */ + +void AlsaPlayer::playback_go(int fd, size_t loaded, off64_t count, int rtype, const char *name) +{ + int l, r; + off64_t written = 0; + off64_t c; + + if (m_debugLevel >= 1) header(rtype, name); + set_params(); + + while (loaded > chunk_bytes && written < count) { + if (pcm_write(audiobuf + written, chunk_size) <= 0) + return; + written += chunk_bytes; + loaded -= chunk_bytes; + } + if (written > 0 && loaded > 0) + memmove(audiobuf, audiobuf + written, loaded); + + l = loaded; + while (written < count) { + do { + c = count - written; + if (c > chunk_bytes) + c = chunk_bytes; + c -= l; + + if (c == 0) + break; + r = safe_read(fd, audiobuf + l, c); + if (r < 0) { +// perror(name); + stopAndExit(); + } + fdcount += r; + if (r == 0) + break; + l += r; + } while (sleep_min == 0 && (size_t)l < chunk_bytes); + l = l * 8 / bits_per_frame; + DBG("calling pcm_write with %i frames.", l); + r = pcm_write(audiobuf, l); + DBG("pcm_write returned r = %i", r); + if (r < 0) return; + if (r != l) + break; + r = r * bits_per_frame / 8; + written += r; + l = 0; + } + + DBG("Draining..."); + + /* We want the next "device ready" notification only when the buffer is completely empty. */ + /* Do this by setting the avail_min to the buffer size. */ + int err; + DBG("Getting swparams"); + snd_pcm_sw_params_t *swparams; + snd_pcm_sw_params_alloca(&swparams); + err = snd_pcm_sw_params_current(handle, swparams); + if (err < 0) { + ERR("Unable to get current swparams: %s", snd_strerror(err)); + return; + } + DBG("Setting avail min to %lu", buffer_size); + err = snd_pcm_sw_params_set_avail_min(handle, swparams, buffer_size); + if (err < 0) { + ERR("Unable to set avail min for playback: %s", snd_strerror(err)); + return; + } + /* write the parameters to the playback device */ + DBG("Writing swparams"); + err = snd_pcm_sw_params(handle, swparams); + if (err < 0) { + ERR("Unable to set sw params for playback: %s", snd_strerror(err)); + return; + } + + DBG("Waiting for poll"); + err = wait_for_poll(1); + if (err < 0) { + ERR("Wait for poll() failed"); + return; + } else if (err == 1){ + MSG("Playback stopped while draining"); + + /* Drop the playback on the sound device (probably + still in progress up till now) */ + err = snd_pcm_drop(handle); + if (err < 0) { + ERR("snd_pcm_drop() failed: %s", snd_strerror(err)); + return; + } + } + DBG("Draining completed"); +} + +/* + * let's play or capture it (capture_type says VOC/WAVE/raw) + */ + +void AlsaPlayer::playback(int fd) +{ + int ofs; + size_t dta; + ssize_t dtawave; + + pbrec_count = LLONG_MAX; + fdcount = 0; + + /* read the file header */ + dta = sizeof(AuHeader); + if ((size_t)safe_read(fd, audiobuf, dta) != dta) { + ERR("read error"); + stopAndExit(); + } + if (test_au(fd, audiobuf) >= 0) { + rhwdata.format = hwdata.format; + pbrec_count = calc_count(); + playback_go(fd, 0, pbrec_count, FORMAT_AU, name.ascii()); + goto __end; + } + dta = sizeof(VocHeader); + if ((size_t)safe_read(fd, audiobuf + sizeof(AuHeader), + dta - sizeof(AuHeader)) != dta - sizeof(AuHeader)) { + ERR("read error"); + stopAndExit(); + } + if ((ofs = test_vocfile(audiobuf)) >= 0) { + pbrec_count = calc_count(); + voc_play(fd, ofs, name.ascii()); + goto __end; + } + /* read bytes for WAVE-header */ + if ((dtawave = test_wavefile(fd, audiobuf, dta)) >= 0) { + pbrec_count = calc_count(); + playback_go(fd, dtawave, pbrec_count, FORMAT_WAVE, name.ascii()); + } else { + /* should be raw data */ + init_raw_data(); + pbrec_count = calc_count(); + playback_go(fd, dta, pbrec_count, FORMAT_RAW, name.ascii()); + } +__end: + return; +} + +/* Wait until ALSA is ready for more samples or stop() was called. + @return 0 if ALSA is ready for more input, +1 if a request to stop + the sound output was received and a negative value on error. */ +int AlsaPlayer::wait_for_poll(int draining) +{ + unsigned short revents; + snd_pcm_state_t state; + int ret; + + DBG("Waiting for poll"); + + /* Wait for certain events */ + while (1) { + /* Simulated pause by not writing to alsa device, which will lead to an XRUN + when resumed. */ + if (m_simulatedPause) + msleep(500); + else { + + ret = poll(alsa_poll_fds, alsa_fd_count, -1); + DBG("activity on %d descriptors", ret); + + /* Check for stop request from alsa_stop on the last file descriptors. */ + if ((revents = alsa_poll_fds[alsa_fd_count-1].revents)) { + if (revents & POLLIN){ + DBG("stop requested"); + return 1; + } + } + + /* Check the first count-1 descriptors for ALSA events */ + snd_pcm_poll_descriptors_revents(handle, alsa_poll_fds, alsa_fd_count-1, &revents); + + /* Ensure we are in the right state */ + state = snd_pcm_state(handle); + DBG("State after poll returned is %s", snd_pcm_state_name(state)); + + if (SND_PCM_STATE_XRUN == state){ + if (!draining){ + MSG("WARNING: Buffer underrun detected!"); + xrun(); + return 0; + }else{ + DBG("Playback terminated"); + return 0; + } + } + + if (SND_PCM_STATE_SUSPENDED == state){ + DBG("WARNING: Suspend detected!"); + suspend(); + return 0; + } + + /* Check for errors */ + if (revents & POLLERR) { + DBG("poll revents says POLLERR"); + return -EIO; + } + + /* Is ALSA ready for more input? */ + if ((revents & POLLOUT)){ + DBG("Ready for more input"); + return 0; + } + } + } +} + +#include "alsaplayer.moc" + +#undef DBG +#undef MSG +#undef ERR + +// vim: sw=4 ts=8 et diff --git a/kttsd/players/alsaplayer/alsaplayer.h b/kttsd/players/alsaplayer/alsaplayer.h new file mode 100644 index 0000000..acd465c --- /dev/null +++ b/kttsd/players/alsaplayer/alsaplayer.h @@ -0,0 +1,187 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + ALSA player. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef ALSAPLAYER_H +#define ALSAPLAYER_H + +// System includes. +#include <alsa/asoundlib.h> +#include <sys/poll.h> + +// Qt includes. +#include <qstring.h> +#include <qobject.h> +#include <qthread.h> +#include <qfile.h> +#include <qmutex.h> + +// KDE includes. +#include <config.h> +#include <kdemacros.h> +#include "kdeexportfix.h" +#include <kurl.h> + +// AlsaPlayer includes. +#include "formats.h" +#include "player.h" + +#ifndef LLONG_MAX +#define LLONG_MAX 9223372036854775807LL +#endif + +#define DEFAULT_FORMAT SND_PCM_FORMAT_U8 +#define DEFAULT_SPEED 8000 + +#define FORMAT_DEFAULT -1 +#define FORMAT_RAW 0 +#define FORMAT_VOC 1 +#define FORMAT_WAVE 2 +#define FORMAT_AU 3 + +static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size); +static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size); +static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); +static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); + +class KDE_EXPORT AlsaPlayer : public Player, QThread +{ + Q_OBJECT + +public: + AlsaPlayer(QObject* parent = 0, const char* name = 0, const QStringList& args=QStringList()); + ~AlsaPlayer(); + + virtual void startPlay(const QString& file); + virtual void pause(); + virtual void stop(); + + virtual void setVolume(float volume = 1.0); + virtual float volume() const; + + virtual bool playing() const; + virtual bool paused() const; + + virtual int totalTime() const; + virtual int currentTime() const; + virtual int position() const; // in this case not really the percent + + virtual void seek(int seekTime); + virtual void seekPosition(int position); + + virtual QStringList getPluginList( const QCString& classname ); + virtual void setSinkName(const QString &sinkName); + + virtual void setDebugLevel(uint level) { m_debugLevel = level; } + virtual void setPeriodSize(uint periodSize) { m_defPeriodSize = periodSize; } + virtual void setPeriods(uint periods) { m_defPeriods = periods; } + +protected: + virtual void run(); + +private slots: + +private: + void init(); + void cleanup(); + void stopAndExit(); + int wait_for_poll(int draining); + + QString timestamp() const; + + ssize_t safe_read(int fd, void *buf, size_t count); + int test_vocfile(void *buffer); + size_t test_wavefile_read(int fd, char *buffer, size_t *size, size_t reqsize, int line); + ssize_t test_wavefile(int fd, char *_buffer, size_t size); + int test_au(int fd, char *buffer); + void set_params(void); + void xrun(); + void suspend(void); + void compute_max_peak(char *data, size_t count); + ssize_t pcm_write(char *data, size_t count); + ssize_t voc_pcm_write(u_char *data, size_t count); + void voc_write_silence(unsigned x); + void voc_pcm_flush(void); + void voc_play(int fd, int ofs, const char *name); + void init_raw_data(void); + off64_t calc_count(void); + void header(int rtype, const char *name); + void playback_go(int fd, size_t loaded, off64_t count, int rtype, const char *name); + void playback(int fd); + + KURL m_currentURL; + float m_currentVolume; + QString m_pcmName; + char* pcm_name; + mutable QMutex m_mutex; + + QFile audiofile; + QString name; + bool canPause; + + snd_pcm_t *handle; + struct { + snd_pcm_format_t format; + unsigned int channels; + unsigned int rate; + } hwdata, rhwdata; + int timelimit; + int file_type; + unsigned int sleep_min; + int open_mode; + snd_pcm_stream_t stream; + int mmap_flag; + int interleaved; + QByteArray audioBuffer; + char *audiobuf; + snd_pcm_uframes_t chunk_size; + snd_pcm_uframes_t period_frames; + unsigned period_time; + unsigned buffer_time; + snd_pcm_uframes_t buffer_size; + int avail_min; + int start_delay; + int stop_delay; + int buffer_pos; + size_t bits_per_sample; + size_t bits_per_frame; + size_t chunk_bytes; + snd_output_t *log; + int fd; + off64_t pbrec_count; + off64_t fdcount; + int vocmajor; + int vocminor; + + int alsa_stop_pipe[2]; /* Pipe for communication about stop requests*/ + int alsa_fd_count; /* Counter of descriptors to poll */ + QByteArray alsa_poll_fds_barray; + struct pollfd *alsa_poll_fds; /* Descriptors to poll */ + unsigned int m_defPeriodSize; + unsigned int m_defPeriods; + unsigned int m_debugLevel; + bool m_simulatedPause; +}; + +#endif // ALSAPLAYER_H + +// vim: sw=4 ts=8 et diff --git a/kttsd/players/alsaplayer/alsaplugin.cpp b/kttsd/players/alsaplayer/alsaplugin.cpp new file mode 100644 index 0000000..4bd700d --- /dev/null +++ b/kttsd/players/alsaplayer/alsaplugin.cpp @@ -0,0 +1,29 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so ALSA can be used as an audio plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "alsaplayer.h" + +K_EXPORT_COMPONENT_FACTORY( libkttsd_alsaplugin, KGenericFactory<AlsaPlayer>("kttsd_alsa") ) + diff --git a/kttsd/players/alsaplayer/formats.h b/kttsd/players/alsaplayer/formats.h new file mode 100644 index 0000000..2c4f724 --- /dev/null +++ b/kttsd/players/alsaplayer/formats.h @@ -0,0 +1,110 @@ +#ifndef FORMATS_H +#define FORMATS_H 1 + +#include <endian.h> +#include <byteswap.h> + +/* Definitions for .VOC files */ + +#define VOC_MAGIC_STRING "Creative Voice File\x1A" +#define VOC_ACTUAL_VERSION 0x010A +#define VOC_SAMPLESIZE 8 + +#define VOC_MODE_MONO 0 +#define VOC_MODE_STEREO 1 + +#define VOC_DATALEN(bp) ((u_long)(bp->datalen) | \ + ((u_long)(bp->datalen_m) << 8) | \ + ((u_long)(bp->datalen_h) << 16) ) + +typedef struct voc_header { + u_char magic[20]; /* must be MAGIC_STRING */ + u_short headerlen; /* Headerlength, should be 0x1A */ + u_short version; /* VOC-file version */ + u_short coded_ver; /* 0x1233-version */ +} VocHeader; + +typedef struct voc_blocktype { + u_char type; + u_char datalen; /* low-byte */ + u_char datalen_m; /* medium-byte */ + u_char datalen_h; /* high-byte */ +} VocBlockType; + +typedef struct voc_voice_data { + u_char tc; + u_char pack; +} VocVoiceData; + +typedef struct voc_ext_block { + u_short tc; + u_char pack; + u_char mode; +} VocExtBlock; + +/* Definitions for Microsoft WAVE format */ + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define COMPOSE_ID(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) +#define LE_SHORT(v) (v) +#define LE_INT(v) (v) +#define BE_SHORT(v) bswap_16(v) +#define BE_INT(v) bswap_32(v) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define COMPOSE_ID(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) +#define LE_SHORT(v) bswap_16(v) +#define LE_INT(v) bswap_32(v) +#define BE_SHORT(v) (v) +#define BE_INT(v) (v) +#else +#error "Wrong endian" +#endif + +#define WAV_RIFF COMPOSE_ID('R','I','F','F') +#define WAV_WAVE COMPOSE_ID('W','A','V','E') +#define WAV_FMT COMPOSE_ID('f','m','t',' ') +#define WAV_DATA COMPOSE_ID('d','a','t','a') +#define WAV_PCM_CODE 1 + +/* it's in chunks like .voc and AMIGA iff, but my source say there + are in only in this combination, so I combined them in one header; + it works on all WAVE-file I have + */ +typedef struct { + u_int magic; /* 'RIFF' */ + u_int length; /* filelen */ + u_int type; /* 'WAVE' */ +} WaveHeader; + +typedef struct { + u_short format; /* should be 1 for PCM-code */ + u_short modus; /* 1 Mono, 2 Stereo */ + u_int sample_fq; /* frequence of sample */ + u_int byte_p_sec; + u_short byte_p_spl; /* samplesize; 1 or 2 bytes */ + u_short bit_p_spl; /* 8, 12 or 16 bit */ +} WaveFmtBody; + +typedef struct { + u_int type; /* 'data' */ + u_int length; /* samplecount */ +} WaveChunkHeader; + +/* Definitions for Sparc .au header */ + +#define AU_MAGIC COMPOSE_ID('.','s','n','d') + +#define AU_FMT_ULAW 1 +#define AU_FMT_LIN8 2 +#define AU_FMT_LIN16 3 + +typedef struct au_header { + u_int magic; /* '.snd' */ + u_int hdr_size; /* size of header (min 24) */ + u_int data_size; /* size of data */ + u_int encoding; /* see to AU_FMT_XXXX */ + u_int sample_rate; /* sample rate */ + u_int channels; /* number of channels (voices) */ +} AuHeader; + +#endif /* FORMATS */ diff --git a/kttsd/players/alsaplayer/kttsd_alsaplugin.desktop b/kttsd/players/alsaplayer/kttsd_alsaplugin.desktop new file mode 100644 index 0000000..36eec0b --- /dev/null +++ b/kttsd/players/alsaplayer/kttsd_alsaplugin.desktop @@ -0,0 +1,89 @@ +[Desktop Entry] +Name=KTTSD ALSA Plugin +Name[br]=Lugent ALSA evit KTTSD +Name[bs]=KTTSD ALSA dodatak +Name[ca]=Connector ALSA pel KTTSD +Name[cs]=KTTSD ALSA modul +Name[da]=KTTSD ALSA plugin +Name[de]=KTTSD ALSA-Modul +Name[el]=KTTSD πρόσθετο ALSA +Name[es]=Complemento ALSA de KTTSD +Name[et]=KTTSD ALSA plugin +Name[eu]=KTTSD-ren ALSA plugina +Name[fa]=وصلۀ KTTSD ALSA +Name[fi]=KTTSD ALSA -liitännäinen +Name[fr]=Module ALSA pour KTTSD +Name[ga]=Breiseán KTTSD ALSA +Name[gl]=Plugin ALSA de KTTSD +Name[hu]=KTTSD ALSA hangmodul +Name[is]=KTTSD ALSA íforrit +Name[it]=Plugin per ALSA di KTTSD +Name[ja]=KTTSD ALSA プラグイン +Name[ka]=KTTSD ALSA მოდული +Name[km]= កម្មវិធីជំនួយ ALSA សម្រាប់ KTTSD +Name[mk]=ALSA-приклучок за KTTSD +Name[nb]=Alsas programtillegg for KTTSD +Name[nds]=ALSA-Moduul för KTTSD +Name[ne]=KTTSD ALSA प्लगइन +Name[nl]=KTTSD ALSA-plugin +Name[pa]=KTTSD ALSA ਪਲੱਗਿੰਨ +Name[pl]=Wtyczka ALSA dla KTTSD +Name[pt]='Plugin' ALSA do KTTSD +Name[pt_BR]=Plug-in ALSA para o KTTSD +Name[ru]=Модуль alsa для KTTSD +Name[sk]=Modul KTTSD ALSA +Name[sl]=Vstavek KTTSD za ALSA +Name[sr]=ALSA прикључак за KTTSD +Name[sr@Latn]=ALSA priključak za KTTSD +Name[sv]=KTTSD-insticksprogram för ALSA +Name[tg]=Модули KTTSD ALSA +Name[tr]=KTTSD ALSA Eklentisi +Name[uk]=Втулок ALSA для KTTSD +Name[vi]=Trình bổ sung KTTSD ALSA +Name[zh_TW]=KTTSd ALSA 外掛程式 +Comment=KTTSD ALSA audio plugin +Comment[bg]=Аудио приставка на KTTSD за ALSA +Comment[br]=Lugent klevet ALSA evit KTTSD +Comment[bs]=KTTSD ALSA audio dodatak +Comment[ca]=Connector d'àudio ALSA pel KTTSD +Comment[cs]=KTTSD ALSA zvukový modul +Comment[da]=KTTSD ALSA lyd-plugin +Comment[de]=KTTSD ALSA-Audio-Modul +Comment[el]=KTTSD πρόσθετο ήχου ALSA +Comment[es]=Complemento de audio ALSA de KTTSD +Comment[et]=KTTSD ALSA audioplugin +Comment[eu]=KTTSD-ren ALSA audio-plugina +Comment[fa]=وصلۀ صوتی KTTSD ALSA +Comment[fi]=KTTSD ALSA ääniliitännäinen +Comment[fr]=Module audio ALSA pour KTTSD +Comment[ga]=Breiseán fuaime KTTSD ALSA +Comment[gl]=Plugin de áudio ALSA para KTTSD +Comment[hu]=KTTSD ALSA hangmodul +Comment[is]=KTTSD ALSA hljóð íforrit +Comment[it]=Plugin audio per ALSA di KTTSD +Comment[ja]=KTTSD ALSA オーディオプラグイン +Comment[ka]=KTTSD ALSA აუდიო მოდული +Comment[km]= កម្មវិធីជំនួយអូឌីយ៉ូ ALSA សម្រាប់ KTTSD +Comment[mk]=ALSA-аудиоприклучок за KTTSD +Comment[nb]=Alsas lyd-programtillegg for KTTSD +Comment[nds]=ALSA-Audiomoduul för KTTSD +Comment[ne]=KTTSD ALSA अडियो प्लगइन +Comment[nl]=KTTSD ALSA geluidsplugin +Comment[pa]=KTTSD ALSA ਆਡੀਓ ਪਲੱਗਿੰਨ +Comment[pl]=Wtyczka dźwięku ALSA dla KTTSD +Comment[pt]='Plugin' de áudio ALSA do KTTSD +Comment[pt_BR]=Plug-in de áudio ALSA para o KTTSD +Comment[ru]=Модуль вывода звука KTTSD через alsa +Comment[sk]=Modul KTTSD ALSA audio +Comment[sl]=Vstavek KTTSD za zvok v ALSA +Comment[sr]=ALSA аудио прикључак за KTTSD +Comment[sr@Latn]=ALSA audio priključak za KTTSD +Comment[sv]=KTTSD-ljudinsticksprogram för ALSA +Comment[tg]=Модули KTTSD ALSA барои аудио +Comment[tr]=KTTSD ALSA ses eklentisi +Comment[uk]=Втулок аудіо ALSA для KTTSD +Comment[vi]=Trình bổ sung âm thanh KTTSD ALSA +Comment[zh_TW]=KTTSd ALSA 語音外掛程式 +Type=Service +ServiceTypes=KTTSD/AudioPlugin +X-KDE-Library=libkttsd_alsaplugin diff --git a/kttsd/players/artsplayer/Makefile.am b/kttsd/players/artsplayer/Makefile.am new file mode 100644 index 0000000..d5c6012 --- /dev/null +++ b/kttsd/players/artsplayer/Makefile.am @@ -0,0 +1,30 @@ +# Include paths. INCLUDES is maintained by KDevelop, AM_CPPFLAGS is the preferred variable, +# so keep them synchronized. +INCLUDES = \ + -I$(kde_includes)/arts \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# LIBRARY SECTION +######################################################################### +# This is the library that gets installed. It's name is used for all +# of the other Makefile.am variables. +kde_module_LTLIBRARIES = libkttsd_artsplugin.la + +libkttsd_artsplugin_la_SOURCES = \ + artsplugin.cpp \ + artsplayer.cpp +libkttsd_artsplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) -no-undefined +libkttsd_artsplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la -lartskde + +# Header files that should not be installed. +noinst_HEADERS = \ + artsplayer.h + +# This library is installed as a plugin. +services_DATA = kttsd_artsplugin.desktop +servicesdir = $(kde_servicesdir) diff --git a/kttsd/players/artsplayer/artsplayer.cpp b/kttsd/players/artsplayer/artsplayer.cpp new file mode 100644 index 0000000..512765a --- /dev/null +++ b/kttsd/players/artsplayer/artsplayer.cpp @@ -0,0 +1,296 @@ +/*************************************************************************** + begin : Sun Feb 17 2002 + copyright : (C) 2002 - 2004 by Scott Wheeler + email : wheeler@kde.org + + copyright : (C) 2003 by Matthias Kretz + email : kretz@kde.org +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "artsplayer.h" + +#include <kdebug.h> +#include <kconfig.h> +#include <kstandarddirs.h> + +#include <qdir.h> +#include <qapplication.h> + +#include <kartsserver.h> +#include <kartsdispatcher.h> +#include <kplayobject.h> +#include <kplayobjectfactory.h> + +#include <sys/wait.h> + +#include <kmessagebox.h> +#include <kaudiomanagerplay.h> +#include <klocale.h> + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +ArtsPlayer::ArtsPlayer(QObject* parent, const char* name, const QStringList& args) : + Player(parent, name, args), + m_dispatcher(0), + m_server(0), + m_factory(0), + m_playobject(0), + m_amanPlay(0), + m_volumeControl(Arts::StereoVolumeControl::null()), + m_currentVolume(1.0) +{ +} + +ArtsPlayer::~ArtsPlayer() +{ + delete m_playobject; + delete m_factory; + delete m_amanPlay; + delete m_server; + delete m_dispatcher; +} + +//void ArtsPlayer::play(const FileHandle &file) +void ArtsPlayer::startPlay(const QString &file) +{ + if (!m_dispatcher) + setupPlayer(); + + // kdDebug(65432) << k_funcinfo << endl; + // Make sure that the server still exists, if it doesn't a new one should + // be started automatically and the factory and amanPlay are created again. + + if(!file.isNull()) +// m_currentURL.setPath(file.absFilePath()); + m_currentURL.setPath(file); + + if(m_server->server().isNull()) { + KMessageBox::error(0, i18n("Cannot find the aRts soundserver.")); + return; + } + + if(!m_playobject || !file.isNull()) { + stop(); + + delete m_playobject; + m_playobject = m_factory->createPlayObject(m_currentURL, false); + + if(m_playobject->object().isNull()) + connect(m_playobject, SIGNAL(playObjectCreated()), SLOT(playObjectCreated())); + else + playObjectCreated(); + } + + m_playobject->play(); +} + +void ArtsPlayer::pause() +{ + // kdDebug(65432) << k_funcinfo << endl; + if(m_playobject) + m_playobject->pause(); +} + +void ArtsPlayer::stop() +{ + // kdDebug(65432) << k_funcinfo << endl; + if(m_playobject) { + m_playobject->halt(); + delete m_playobject; + m_playobject = 0; + } + if(!m_volumeControl.isNull()) { + m_volumeControl.stop(); + m_volumeControl = Arts::StereoVolumeControl::null(); + } +} + +void ArtsPlayer::setVolume(float volume) +{ + // kdDebug( 65432 ) << k_funcinfo << endl; + + m_currentVolume = volume; + + if(serverRunning() && m_playobject && !m_playobject->isNull()) { + if(m_volumeControl.isNull()) + setupVolumeControl(); + if(!m_volumeControl.isNull()) { + m_volumeControl.scaleFactor(volume); + // kdDebug( 65432 ) << "set volume to " << volume << endl; + } + } +} + +float ArtsPlayer::volume() const +{ + return m_currentVolume; +} + +///////////////////////////////////////////////////////////////////////////////// +// player status functions +///////////////////////////////////////////////////////////////////////////////// + +bool ArtsPlayer::playing() const +{ + if(serverRunning() && m_playobject && m_playobject->state() == Arts::posPlaying) + return true; + else + return false; +} + +bool ArtsPlayer::paused() const +{ + if(serverRunning() && m_playobject && m_playobject->state() == Arts::posPaused) + return true; + else + return false; +} + +int ArtsPlayer::totalTime() const +{ + if(serverRunning() && m_playobject) + return m_playobject->overallTime().seconds; + else + return -1; +} + +int ArtsPlayer::currentTime() const +{ + if(serverRunning() && m_playobject && + (m_playobject->state() == Arts::posPlaying || + m_playobject->state() == Arts::posPaused)) + { + return m_playobject->currentTime().seconds; + } + else + return -1; +} + +int ArtsPlayer::position() const +{ + if(serverRunning() && m_playobject && m_playobject->state() == Arts::posPlaying) { + long total = m_playobject->overallTime().seconds * 1000 + m_playobject->overallTime().ms; + long current = m_playobject->currentTime().seconds * 1000 + m_playobject->currentTime().ms; + + // add .5 to make rounding happen properly + + return int(double(current) * 1000 / total + .5); + } + else + return -1; +} + +///////////////////////////////////////////////////////////////////////////////// +// player seek functions +///////////////////////////////////////////////////////////////////////////////// + +void ArtsPlayer::seek(int seekTime) +{ + if(serverRunning() && m_playobject) { + Arts::poTime poSeekTime; + poSeekTime.custom = 0; + poSeekTime.ms = 0; + poSeekTime.seconds = seekTime; + m_playobject->object().seek(poSeekTime); + } +} + +void ArtsPlayer::seekPosition(int position) +{ + if(serverRunning() && m_playobject) { + Arts::poTime poSeekTime; + long total = m_playobject->overallTime().seconds; + poSeekTime.custom = 0; + poSeekTime.ms = 0; + poSeekTime.seconds = long(double(total) * position / 1000 + .5); + m_playobject->object().seek(poSeekTime); + } +} + +///////////////////////////////////////////////////////////////////////////////// +// private +///////////////////////////////////////////////////////////////////////////////// + +void ArtsPlayer::setupArtsObjects() +{ + // kdDebug( 65432 ) << k_funcinfo << endl; + delete m_factory; + delete m_amanPlay; + m_volumeControl = Arts::StereoVolumeControl::null(); + m_factory = new KDE::PlayObjectFactory(m_server); + m_amanPlay = new KAudioManagerPlay(m_server); + + if(m_amanPlay->isNull() || !m_factory) { + KMessageBox::error(0, i18n("Connecting/starting aRts soundserver failed. " + "Make sure that artsd is configured properly.")); + qApp->exit(1); + } + + m_amanPlay->setTitle(i18n("kttsd")); + m_amanPlay->setAutoRestoreID("kttsdAmanPlay"); + + m_factory->setAudioManagerPlay(m_amanPlay); +} + +void ArtsPlayer::playObjectCreated() +{ + // kdDebug(65432) << k_funcinfo << endl; + setVolume(m_currentVolume); +} + +void ArtsPlayer::setupPlayer() +{ + m_dispatcher = new KArtsDispatcher; + m_server = new KArtsServer; + setupArtsObjects(); + connect(m_server, SIGNAL(restartedServer()), SLOT(setupArtsObjects())); +} + +void ArtsPlayer::setupVolumeControl() +{ + // kdDebug( 65432 ) << k_funcinfo << endl; + m_volumeControl = Arts::DynamicCast(m_server->server().createObject("Arts::StereoVolumeControl")); + if(!m_volumeControl.isNull() && !m_playobject->isNull() && !m_playobject->object().isNull()) { + Arts::Synth_AMAN_PLAY ap = m_amanPlay->amanPlay(); + Arts::PlayObject po = m_playobject->object(); + ap.stop(); + Arts::disconnect(po, "left" , ap, "left" ); + Arts::disconnect(po, "right", ap, "right"); + + m_volumeControl.start(); + ap.start(); + + Arts::connect(po, "left" , m_volumeControl, "inleft" ); + Arts::connect(po, "right", m_volumeControl, "inright"); + Arts::connect(m_volumeControl, "outleft" , ap, "left" ); + Arts::connect(m_volumeControl, "outright", ap, "right"); + // kdDebug( 65432 ) << "connected volume control" << endl; + } + else { + m_volumeControl = Arts::StereoVolumeControl::null(); + kdDebug(65432) << "Could not initialize volume control!" << endl; + } +} + +bool ArtsPlayer::serverRunning() const +{ + if(m_server) + return !(m_server->server().isNull()); + else + return false; +} + +#include "artsplayer.moc" + +// vim: sw=4 ts=8 et diff --git a/kttsd/players/artsplayer/artsplayer.h b/kttsd/players/artsplayer/artsplayer.h new file mode 100644 index 0000000..96d62f6 --- /dev/null +++ b/kttsd/players/artsplayer/artsplayer.h @@ -0,0 +1,95 @@ +/*************************************************************************** + begin : Sun Feb 17 2002 + copyright : (C) 2002 - 2004 by Scott Wheeler + email : wheeler@kde.org + + copyright : (C) 2003 by Matthias Kretz + email : kretz@kde.org +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef ARTSPLAYER_H +#define ARTSPLAYER_H + +#include <config.h> +#include <kdemacros.h> +#include "kdeexportfix.h" + +#include "player.h" + +#include <kurl.h> + +#include <qstring.h> +#include <qobject.h> +#include <artsflow.h> + +class KArtsDispatcher; +class KArtsServer; +class KAudioManagerPlay; + +namespace KDE { + class PlayObjectFactory; + class PlayObject; +} + +class KDE_EXPORT ArtsPlayer : public Player +{ + Q_OBJECT + +public: + ArtsPlayer(QObject* parent = 0, const char* name = 0, const QStringList& args=QStringList()); + ~ArtsPlayer(); + +// virtual void play(const FileHandle &file = FileHandle::null()); + virtual void startPlay(const QString& file); + virtual void pause(); + virtual void stop(); + + virtual void setVolume(float volume = 1.0); + virtual float volume() const; + + virtual bool playing() const; + virtual bool paused() const; + + virtual int totalTime() const; + virtual int currentTime() const; + virtual int position() const; // in this case not really the percent + + virtual void seek(int seekTime); + virtual void seekPosition(int position); + +private slots: + void setupArtsObjects(); + void playObjectCreated(); + +private: + void setupPlayer(); + void setupVolumeControl(); + bool serverRunning() const; + + KArtsDispatcher *m_dispatcher; + KArtsServer *m_server; + KDE::PlayObjectFactory *m_factory; + KDE::PlayObject *m_playobject; + KAudioManagerPlay *m_amanPlay; + + // This is a pretty heavy module for the needs that JuK has, it would probably + // be good to use two Synth_MUL instead or the one from Noatun. + + Arts::StereoVolumeControl m_volumeControl; + + KURL m_currentURL; + float m_currentVolume; +}; + +#endif + +// vim: sw=4 ts=8 et diff --git a/kttsd/players/artsplayer/artsplugin.cpp b/kttsd/players/artsplayer/artsplugin.cpp new file mode 100644 index 0000000..36f4517 --- /dev/null +++ b/kttsd/players/artsplayer/artsplugin.cpp @@ -0,0 +1,29 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so aRts can be used as an audio plug in. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "artsplayer.h" + +K_EXPORT_COMPONENT_FACTORY( libkttsd_artsplugin, KGenericFactory<ArtsPlayer>("kttsd_arts") ) + diff --git a/kttsd/players/artsplayer/kttsd_artsplugin.desktop b/kttsd/players/artsplayer/kttsd_artsplugin.desktop new file mode 100644 index 0000000..9eef151 --- /dev/null +++ b/kttsd/players/artsplayer/kttsd_artsplugin.desktop @@ -0,0 +1,91 @@ +[Desktop Entry] +Name=KTTSD Arts Plugin +Name[br]=Lugent Arts evit KTTSD +Name[bs]=KTTSD Arts dodatak +Name[ca]=Connector Arts pel KTTSD +Name[cs]=KTTSD Arts modul +Name[da]=KTTSD aRts-plugin +Name[de]=KTTSD Arts-Modul +Name[el]=KTTSD πρόσθετο Arts +Name[es]=Complemento KTTSD Arts +Name[et]=KTTSD Artsi plugin +Name[eu]=KTTSD-ren Arts-plugina +Name[fa]=وصلۀ KTTSD Arts +Name[fi]=KTTSD Arts -liitännäinen +Name[fr]=Module Arts pour KTTSD +Name[ga]=Breiseán KTTSD Arts +Name[gl]=Plugin Arts de KTTSD +Name[hu]=KTTSD aRts hangmodul +Name[is]=KTTSD Arts íforrit +Name[it]=Plugin per Arts di KTTSD +Name[ja]=KTTSD aRts プラグイン +Name[ka]=KTTSD Arts მოდული +Name[km]=កម្មវិធីជំនួយ Arts សម្រាប់ KTTSD +Name[mk]=aRts-приклучок за KTTSD +Name[nb]=Arts programtillegg for KTTSD +Name[nds]=aRts-Moduul för KTTSD +Name[ne]=KTTSD कला प्लगइन +Name[nl]=KTTSD Arts-plugin +Name[pa]=KTTSD Arts ਪਲੱਗਿੰਨ +Name[pl]=Wtyczka aRts KTTSD +Name[pt]='Plugin' Arts do KTTSD +Name[pt_BR]=Plug-in do Arts para o KTTSD +Name[ru]=Модуль Arts для KTTSD +Name[sk]=Modul KTTSD Arts +Name[sl]=Vstavek KTTSD za aRts +Name[sr]=Arts као прикључак за KTTSD +Name[sr@Latn]=Arts kao priključak za KTTSD +Name[sv]=KTTSD-insticksprogram för aRts +Name[ta]=KTTSD ஆர்ட்ஸ் சொருகுப்பொருள் +Name[tg]=Модули Arts барои KTTSD +Name[tr]=KTTSD Arts Eklentisi +Name[uk]=Втулок Arts для KTTSD +Name[vi]=Trình bổ sung KTTSD Arts +Name[zh_TW]=KTTSd Arts 外掛程式 +Comment=KTTSD aRts audio plugin +Comment[bg]=Аудио приставка на KTTSD за aRts +Comment[br]=Lugent klevet Arts evit KTTSD +Comment[ca]=Connector d'àudio aRts pel KTTSD +Comment[cs]=KTTSD aRts zvukový modul +Comment[da]=KTTSD aRts lyd-plugin +Comment[de]=KTTSD aRts Audio-Modul +Comment[el]=KTTSD πρόσθετο ήχου aRts +Comment[es]=Complemento de audio KTTSD aRts +Comment[et]=KTTSD aRts'i audioplugin +Comment[eu]=KTTSD-ren aRts audio-plugina +Comment[fa]=وصلۀ صوتی KTTSD aRts +Comment[fi]=KTTSD aRts ääniliitännäinen +Comment[fr]=Module audio Arts pour KTTSD +Comment[ga]=Breiseán fuaime KTTSD aRts +Comment[gl]=Plugin de áudio aRts para KTTSD +Comment[hu]=KTTSD hangmodul az aRts hangrendszerhez +Comment[is]=KTTSD aRts hljóð íforrit +Comment[it]=Plugin audio per aRts di KTTSD +Comment[ja]=KTTSD aRts オーディオプラグイン +Comment[ka]=KTTSD aRts აუდიო მოდული +Comment[km]=កម្មវិធីជំនួយអូឌីយ៉ូ aRts សម្រាប់ KTTSD +Comment[mk]=aRts-аудиоприклучок за KTTSD +Comment[ms]=Plugin audio aRts KTTSD +Comment[nb]=aRts lyd-programtillegg for KTTSD +Comment[nds]=aRts-Audiomoduul för KTTSD +Comment[ne]=KTTSD कला अडियो प्लगइन +Comment[nl]=KTTSD aRts-audioplugin +Comment[pa]=KTTSD aRts ਆਡੀਓ ਪਲੱਗਿੰਨ +Comment[pl]=Wtyczka dźwięku aRts dla KTTSD +Comment[pt]='Plugin' áudio aRts do KTTSD +Comment[pt_BR]=Plug-in de áudio do Arts para o KTTSD +Comment[ru]=Модуль вывода звука KTTSD через aRts +Comment[sk]=Modul KTTSD aRts audio +Comment[sl]=Vstavek KTTSD za zvok v aRts +Comment[sr]=Arts као аудио прикључак за KTTSD +Comment[sr@Latn]=Arts kao audio priključak za KTTSD +Comment[sv]=KTTSD-ljudinsticksprogram för aRts +Comment[ta]=KTTSD aRts கேட்பொலி சொருகுப்பொருள் +Comment[tg]=Модули барориши овози KTTSD ба воситаи aRts +Comment[tr]=KTTSD aRts ses eklentisi +Comment[uk]=Звуковий втулок aRts для KTTSD +Comment[vi]=Trình bổ sung âm thanh KTTSD aRts +Comment[zh_TW]=KTTSd aRts 語音外掛程式 +Type=Service +ServiceTypes=KTTSD/AudioPlugin +X-KDE-Library=libkttsd_artsplugin diff --git a/kttsd/players/gstplayer/Makefile.am b/kttsd/players/gstplayer/Makefile.am new file mode 100644 index 0000000..df00c55 --- /dev/null +++ b/kttsd/players/gstplayer/Makefile.am @@ -0,0 +1,30 @@ +# Include paths. INCLUDES is maintained by KDevelop, AM_CPPFLAGS is the preferred variable, +# so keep them synchronized. +INCLUDES = \ + $(GST_CFLAGS) \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +# Let automoc handle all of the metsource files (moc). +METASOURCES = AUTO + +######################################################################### +# LIBRARY SECTION +######################################################################### +# This is the library that gets installed. It's name is used for all +# of the other Makefile.am variables. +kde_module_LTLIBRARIES = libkttsd_gstplugin.la + +libkttsd_gstplugin_la_SOURCES = \ + gstplugin.cpp \ + gstreamerplayer.cpp +libkttsd_gstplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) $(LDFLAGS_GST) -no-undefined +libkttsd_gstplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la $(LDADD_GST) + +# Header files that should not be installed. +noinst_HEADERS = \ + gstreamerplayer.h + +# This library is installed as a plugin. +services_DATA = kttsd_gstplugin.desktop +servicesdir = $(kde_servicesdir) diff --git a/kttsd/players/gstplayer/gstplugin.cpp b/kttsd/players/gstplayer/gstplugin.cpp new file mode 100644 index 0000000..633bff9 --- /dev/null +++ b/kttsd/players/gstplayer/gstplugin.cpp @@ -0,0 +1,29 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so that GStreamer can be used as and audio plug in. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "gstreamerplayer.h" + +K_EXPORT_COMPONENT_FACTORY( libkttsd_gstplugin, KGenericFactory<GStreamerPlayer>("kttsd_gst") ); + diff --git a/kttsd/players/gstplayer/gstreamerplayer.cpp b/kttsd/players/gstplayer/gstreamerplayer.cpp new file mode 100644 index 0000000..64aa1c4 --- /dev/null +++ b/kttsd/players/gstplayer/gstreamerplayer.cpp @@ -0,0 +1,258 @@ +/*************************************************************************** + copyright : (C) 2004 Scott Wheeler + email : wheeler@kde.org +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qfile.h> + +// KDE includes. +#include <kapplication.h> +#include <kconfig.h> +#include <kglobal.h> +#include <kdebug.h> + +// GStreamerPlayer includes. +#include "gstreamerplayer.h" +#include "gstreamerplayer.moc" + +//////////////////////////////////////////////////////////////////////////////// +// public methods +//////////////////////////////////////////////////////////////////////////////// + +GStreamerPlayer::GStreamerPlayer(QObject* parent, const char* name, const QStringList& args) : + Player(parent, name, args), + m_initialized(false), + m_pipeline(0), + m_source(0), + m_decoder(0), + m_volume(0), + m_sink(0) +{ + // readConfig(); + setupPipeline(); +} + +GStreamerPlayer::~GStreamerPlayer() +{ + stop(); + gst_object_unref(GST_OBJECT(m_pipeline)); +} + +//void GStreamerPlayer::play(const FileHandle &file) +void GStreamerPlayer::startPlay(const QString &file) +{ + if(!file.isNull()) { + stop(); + // g_object_set(G_OBJECT(m_source), "location", file.absFilePath().local8Bit().data(), 0); + g_object_set(G_OBJECT(m_source),"location",file.local8Bit().data(),NULL); + } + + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); +} + +void GStreamerPlayer::pause() +{ + gst_element_set_state(m_pipeline, GST_STATE_PAUSED); +} + +void GStreamerPlayer::stop() +{ + gst_element_set_state(m_pipeline, GST_STATE_NULL); +} + +void GStreamerPlayer::setVolume(float volume) +{ + g_object_set(G_OBJECT(m_volume), "volume", volume, NULL); +} + +float GStreamerPlayer::volume() const +{ + gfloat value; + g_object_get(G_OBJECT(m_volume), "volume", &value, NULL); + return value; +} + +bool GStreamerPlayer::playing() const +{ + return gst_element_get_state(m_pipeline) == GST_STATE_PLAYING; +} + +bool GStreamerPlayer::paused() const +{ + return gst_element_get_state(m_pipeline) == GST_STATE_PAUSED; +} + +int GStreamerPlayer::totalTime() const +{ + return time(GST_QUERY_TOTAL) / GST_SECOND; +} + +int GStreamerPlayer::currentTime() const +{ + return time(GST_QUERY_POSITION) / GST_SECOND; +} + +int GStreamerPlayer::position() const +{ + long long total = time(GST_QUERY_TOTAL); + long long current = time(GST_QUERY_POSITION); + return total > 0 ? int((double(current) / double(total)) * double(1000) + 0.5) : 0; +} + +void GStreamerPlayer::seek(int seekTime) +{ + int type = (GST_FORMAT_TIME | GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH); + gst_element_seek(m_sink, GstSeekType(type), seekTime * GST_SECOND); +} + +void GStreamerPlayer::seekPosition(int position) +{ + long long total = time(GST_QUERY_TOTAL); + if(total > 0) + seek(int(double(position) / double(1000) * double(totalTime()) + 0.5)); +} + +/** + * Returns a list of GStreamer plugins of the specified class. + * @param classname Desired class. Use "Sink/Audio" for sinks. + * @return List of plugin names. + */ +QStringList GStreamerPlayer::getPluginList( const QCString& classname ) +{ + GList * pool_registries = NULL; + GList* registries = NULL; + GList* plugins = NULL; + GList* features = NULL; + QString name; + QStringList results; + + if(!m_initialized) { + int argc = kapp->argc(); + char **argv = kapp->argv(); + gst_init(&argc, &argv); + m_initialized = true; + } + + pool_registries = gst_registry_pool_list (); + registries = pool_registries; + + while ( registries ) { + GstRegistry * registry = GST_REGISTRY ( registries->data ); + plugins = registry->plugins; + + while ( plugins ) { + GstPlugin * plugin = GST_PLUGIN ( plugins->data ); + features = gst_plugin_get_feature_list ( plugin ); + + while ( features ) { + GstPluginFeature * feature = GST_PLUGIN_FEATURE ( features->data ); + + if ( GST_IS_ELEMENT_FACTORY ( feature ) ) { + GstElementFactory * factory = GST_ELEMENT_FACTORY ( feature ); + + if ( g_strrstr ( factory->details.klass, classname ) ) { + name = g_strdup ( GST_OBJECT_NAME ( factory ) ); + if ( name != "artsdsink" ) results << name; + } + } + features = g_list_next ( features ); + } + plugins = g_list_next ( plugins ); + } + registries = g_list_next ( registries ); + } + g_list_free ( pool_registries ); + pool_registries = NULL; + + return results; +} + +bool GStreamerPlayer::requireVersion(uint major, uint minor, uint micro) +{ + guint gmajor, gminor, gmicro; + + if(!m_initialized) { + int argc = kapp->argc(); + char **argv = kapp->argv(); + gst_init(&argc, &argv); + m_initialized = true; + } + + gst_version(&gmajor, &gminor, &gmicro); + // kdDebug() << QString("GStreamerPlayer::requireVersion: You have gstreamer %1.%2.%3 installed.").arg(gmajor).arg(gminor).arg(gmicro) << endl; + if (gmajor > major) return true; + if (gminor > minor) return true; + if (gmicro >= micro) return true; + kdDebug() << QString("GStreamerPlayer::requireVersion: You have gstreamer %1.%2.%3 installed.").arg(gmajor).arg(gminor).arg(gmicro) << endl; + kdDebug() << QString("GStreamerPlayer::requireVersion: This application requires %1.%2.%3 or greater.").arg(major).arg(minor).arg(micro) << endl; + return false; +} + +void GStreamerPlayer::setSinkName(const QString &sinkName) { m_sinkName = sinkName; } + +//////////////////////////////////////////////////////////////////////////////// +// private methods +//////////////////////////////////////////////////////////////////////////////// + +void GStreamerPlayer::readConfig() +{ + KConfigGroup config(KGlobal::config(), "GStreamerPlayer"); + m_sinkName = config.readEntry("SinkName", QString::null); +} + +void GStreamerPlayer::setupPipeline() +{ + if(!m_initialized) { + int argc = kapp->argc(); + char **argv = kapp->argv(); + gst_init(&argc, &argv); + m_initialized = true; + } + + m_pipeline = gst_thread_new("pipeline"); + m_source = gst_element_factory_make("filesrc", "source"); + m_decoder = gst_element_factory_make("spider", "decoder"); + m_volume = gst_element_factory_make("volume", "volume"); + + if(!m_sinkName.isNull()) + m_sink = gst_element_factory_make(m_sinkName.utf8().data(), "sink"); + if (!m_sink) + { + // m_sink = gst_element_factory_make("alsasink", "sink"); + // if(!m_sink) + // m_sink = gst_element_factory_make("osssink", "sink"); + + // Reversing order. OSS seems to work. Alsa sink produces ugly echo of last + // couple of words in each wav file. argh! + // kdDebug() << "GStreamerPlayer::setupPipeline: trying oss sink." << endl; + m_sink = gst_element_factory_make("osssink", "sink"); + if(!m_sink) + { + // kdDebug() << "GStreamerPlayer::setupPipeline: reverting to alsa sink." << endl; + m_sink = gst_element_factory_make("alsasink", "sink"); + } + } + + gst_bin_add_many(GST_BIN(m_pipeline), m_source, m_decoder, m_volume, m_sink, 0); + gst_element_link_many(m_source, m_decoder, m_volume, m_sink, 0); +} + +long long GStreamerPlayer::time(GstQueryType type) const +{ + gint64 ns = 0; + GstFormat format = GST_FORMAT_TIME; + gst_element_query(m_sink, type, &format, &ns); + return ns; +} + +// vim: set et sw=4: diff --git a/kttsd/players/gstplayer/gstreamerplayer.h b/kttsd/players/gstplayer/gstreamerplayer.h new file mode 100644 index 0000000..6a7f1d6 --- /dev/null +++ b/kttsd/players/gstplayer/gstreamerplayer.h @@ -0,0 +1,76 @@ +/*************************************************************************** + copyright : (C) 2004 Scott Wheeler + email : wheeler@kde.org +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef GSTREAMERPLAYER_H +#define GSTREAMERPLAYER_H + +#include "config.h" + +#include <glib.h> +extern "C" { +#include <gst/gstversion.h> +} +#include <gst/gst.h> +#include <qstring.h> +#include "player.h" + +class GStreamerPlayer : public Player +{ + Q_OBJECT + +public: + GStreamerPlayer(QObject* parent = 0, const char* name = 0, const QStringList& args=QStringList()); + virtual ~GStreamerPlayer(); + + // virtual void play(const FileHandle &file = FileHandle::null()); + virtual void startPlay(const QString& file); + + virtual void setVolume(float volume = 1.0); + virtual float volume() const; + + virtual bool playing() const; + virtual bool paused() const; + + virtual int totalTime() const; + virtual int currentTime() const; + virtual int position() const; // in this case not really the percent + + virtual void seek(int seekTime); + virtual void seekPosition(int position); + + virtual QStringList getPluginList( const QCString& classname ); + virtual void setSinkName(const QString &sinkName); + + virtual bool requireVersion(uint major, uint minor, uint micro); + + void pause(); + void stop(); + +private: + void readConfig(); + void setupPipeline(); + long long time(GstQueryType type) const; + + QString m_sinkName; + // True once gst_init() has been called. + bool m_initialized; + + GstElement *m_pipeline; + GstElement *m_source; + GstElement *m_decoder; + GstElement *m_volume; + GstElement *m_sink; +}; + +#endif diff --git a/kttsd/players/gstplayer/kttsd_gstplugin.desktop b/kttsd/players/gstplayer/kttsd_gstplugin.desktop new file mode 100644 index 0000000..7646cee --- /dev/null +++ b/kttsd/players/gstplayer/kttsd_gstplugin.desktop @@ -0,0 +1,92 @@ +[Desktop Entry] +Name=KTTSD GStreamer Plugin +Name[br]=Lugent KTTSD GStreamer +Name[bs]=KTTSD GStreamer dodatak +Name[ca]=Connector GStreamer pel KTTSD +Name[cs]=KTTSD GStreamer modul +Name[da]=KTTSD GStreamer-plugin +Name[de]=KTTSD GStreamer-Modul +Name[el]=KTTSD πρόσθετο GStreamer +Name[es]=Complemento KTTSD GStreamer +Name[et]=KTTSD GStreameri plugin +Name[eu]=KTTSD-ren GStreamer plugina +Name[fa]=وصلۀ KTTSD GStreamer +Name[fi]=KTTSD GStreamer-liitännäinen +Name[fr]=Module GStreamer pour KTTSD +Name[ga]=Breiseán KTTSD GStreamer +Name[gl]=Plugin GStreamer de KTTSD +Name[hu]=KTTSD GStreamer-modul +Name[is]=KTTSD GStreamer íforrit +Name[it]=Plugin per GStreamer di KTTSD +Name[ja]=KTTSD GStreamer プラグイン +Name[ka]=KTTSD GStreamer მოდული +Name[km]= កម្មវិធីជំនួយ GStreamer សម្រាប់ KTTSD +Name[mk]=GStreamer-приклучок за KTTSD +Name[ms]=Plugin KTTSD GStreamer +Name[nb]=GStreamer programtillegg for KTTSD +Name[nds]=GStreamer-Moduul för KTTSD +Name[ne]=KTTSD जि स्ट्रिमर प्लगइन +Name[nl]=KTTSD GStreamer-plugin +Name[pa]=KTTSD ਜੀਸਟਰੀਮਰ ਪਲੱਗਿੰਨ +Name[pl]=Wtyczka GStreamer KTTSD +Name[pt]='Plugin' GStreamer do KTTSD +Name[pt_BR]=Plug-in do GStreamer para o KTTSD +Name[ru]=Модуль GStreamer для KTTSD +Name[sk]=Modul KTTSD GStreamer +Name[sl]=Vstavek KTTSD za GStreamer +Name[sr]=GStreamer као прикључак за KTTSD +Name[sr@Latn]=GStreamer kao priključak za KTTSD +Name[sv]=KTTSD-insticksprogram för Gstreamer +Name[ta]=KTTSD GStreamer சொருகுப்பொருள் +Name[tg]=Модули GStreamer барои KTTSD +Name[tr]=KTTSD GStreamer Eklentisi +Name[uk]=Втулок GStreamer для KTTSD +Name[vi]=Trình bổ sung KTTSD GStreamer +Name[zh_TW]=KTTSd GStreamer 外掛程式 +Comment=KTTSD GStreamer audio plugin +Comment[bg]=Аудио приставка на KTTSD за GStreamer +Comment[br]=Lugent klevet GStreamer evit KTTSD +Comment[ca]=Connector d'àudio GStreamer pel KTTSD +Comment[cs]=KTTSD GStreamer zvukový modul +Comment[da]=KTTSD GStreamer lyd-plugin +Comment[de]=KTTSD GStreamer Audio-Modul +Comment[el]=KTTSD πρόσθετο ήχου GStreamer +Comment[es]=Complemento de audio KTTSD GStreamer +Comment[et]=KTTSD GStreameri audioplugin +Comment[eu]=KTTSD-ren GStreamer audio-plugina +Comment[fa]=وصلۀ صوتی KTTSD GStreamer +Comment[fi]=KTTSD GStreamer ääniliitännäinen +Comment[fr]=Module audio GStreamer pour KTTSD +Comment[ga]=Breiseán fuaime GStreamer KTTSD +Comment[gl]=Plugin de áudio GStreamer para KTTSD +Comment[hu]=KTTSD hangmodul a GStreamerhez +Comment[is]=KTTSD GStreamer hljóð íforrit +Comment[it]=Plugin audio per GStreamer di KTTSD +Comment[ja]=KTTSD GStreamer オーディオプラグイン +Comment[ka]=KTTSD GStreamer აუდიო მოდული +Comment[km]=កម្មវិធីជំនួយអូឌីយ៉ូ GStreamer សម្រាប់ KTTSD +Comment[mk]=GStreamer-аудиоприклучок за KTTSD +Comment[ms]=Plugin audio KTTSD GStreamer +Comment[nb]=GStreamer lyd-programtillegg for KTTSD +Comment[nds]=GStreamer-Audiomoduul för KTTSD +Comment[ne]=KTTSD जि स्ट्रिमर अडियो प्लगइन +Comment[nl]=KTTSD GStreamer-audioplugin +Comment[pa]=KTTSD ਜੀਸਟਰੀਮਰ ਆਡੀਓ ਪਲੱਗਿੰਨ +Comment[pl]=Wtyczka dźwięku GStreamer dla KTTSD +Comment[pt]='Plugin' áudio GStreamer do KTTSD +Comment[pt_BR]=Plug-in de áudio do GStreamer para o KTTSD +Comment[ru]=Модуль вывода звука KTTSD через GStreamer +Comment[sk]=Modul KTTSD GStreamer audio +Comment[sl]=Vstavek KTTSD za zvok v GStreamer +Comment[sr]=GStreamer као аудио прикључак за KTTSD +Comment[sr@Latn]=GStreamer kao audio priključak za KTTSD +Comment[sv]=KTTSD-ljudinsticksprogram för Gstreamer +Comment[ta]=KTTSD GStreamer கேட்பொலி சொருகுப்பொருள் +Comment[tg]=Модули барориши овози KTTSD ба воситаи GStreamer +Comment[tr]= KTTSD GStreamer ses eklentisi +Comment[uk]=Звуковий втулок GStreamer для KTTSD +Comment[vi]=Trình bổ sung âm thanh KTTSD GStreamer +Comment[zh_TW]=KTTSd GStreamer 語音外掛程式 +Type=Service +ServiceTypes=KTTSD/AudioPlugin +X-KDE-Library=libkttsd_gstplugin diff --git a/kttsd/players/kttsd_audioplugin.desktop b/kttsd/players/kttsd_audioplugin.desktop new file mode 100644 index 0000000..ae39e7c --- /dev/null +++ b/kttsd/players/kttsd_audioplugin.desktop @@ -0,0 +1,47 @@ +[Desktop Entry] +Type=ServiceType +X-KDE-ServiceType=KTTSD/AudioPlugin +Comment=Audio Plugin for KTTSD +Comment[bg]=Аудио приставка на KTTSD +Comment[br]=Lugent klevet evit KTTSD +Comment[ca]=Connector d'àudio pel KTTSD +Comment[cs]=Zvukový modul KTTSD +Comment[da]=Lyd-plugin for KTTSD +Comment[de]=Audio-Modul für KTTSD +Comment[el]=Πρόσθετο ήχου για το KTTSD +Comment[es]=Complemento de audio para KTTSD +Comment[et]=KTTSD audioplugin +Comment[eu]=KTTSD-ren audio plugnka +Comment[fa]=وصلۀ صوتی برای KTTSD +Comment[fi]=Ääniliitännäinen KTTSD-ohjelmalle +Comment[fr]=Module audio pour KTTSD +Comment[ga]=Breiseán fuaime KTTSD +Comment[gl]=Plugin de Áudio para KTTSD +Comment[hu]=Hangmodul a KTTSD szolgáltatáshoz +Comment[is]=Hljóð íforrit fyrir KTTSD +Comment[it]=Plugin audio per KTTSD +Comment[ja]=KTTSD 用オーディオプラグイン +Comment[ka]=აუდიო მოდული KTTSD-თვის +Comment[km]=កម្មវិធីជំនួយអូឌីយ៉ូសម្រាប់ KTTSD +Comment[mk]=Аудиоприклучок за KTTSD +Comment[ms]=Plugin Audio bagi KTTSD +Comment[nb]=Lyd-programtillegg for KTTSD +Comment[nds]=Audiomoduul för KTTSD +Comment[ne]=KTTSD का लागि अडियो प्लगइन +Comment[nl]=Audio-plugin voor KTTSD +Comment[pa]=KTTSD ਲਈ ਆਡੀਓ ਪਲੱਗਿੰਨ +Comment[pl]=Wtyczka dźwięku dla KTTSD +Comment[pt]='Plugin' Áudio para o KTTSD +Comment[pt_BR]=Plug-in de áudio para o KTTSD +Comment[ru]=Модуль вывода звука KTTSD +Comment[sk]=Audio modul pre KTTSD +Comment[sl]=Vstavek KTTSD za zvok +Comment[sr]=Аудио прикључак за KTTSD +Comment[sr@Latn]=Audio priključak za KTTSD +Comment[sv]=Ljudinsticksprogram för KTTSD +Comment[ta]= KTTSDக்கான கேட்பொலி சொருகுப்பொருள் +Comment[tg]=Модули барориши овози KTTSD +Comment[tr]=KTTS için Ses Eklentisi +Comment[uk]=Звуковий втулок для KTTSD +Comment[vi]=Trình bổ sung Âm thanh cho KTTSD +Comment[zh_TW]=KTTSd 語音外掛程式 diff --git a/kttsd/plugins/Makefile.am b/kttsd/plugins/Makefile.am new file mode 100644 index 0000000..7bf657e --- /dev/null +++ b/kttsd/plugins/Makefile.am @@ -0,0 +1,37 @@ +#kdevelop: festivalint_DIR = festivalint +if include_kttsd_festivalint + festivalint_DIR = festivalint +endif + +#kdevelop: freetts_DIR = freetts +if include_kttsd_freetts + freetts_DIR = freetts +endif + +#kdevelop: command_DIR = command +if include_kttsd_command + command_DIR = command +endif + +#kdevelop: hadifix_DIR = hadifix +if include_kttsd_hadifix + hadifix_DIR = hadifix +endif + +#kdevelop: flite_DIR = flite +if include_kttsd_flite + flite_DIR = flite +endif + +#kdevelop: epos_DIR = epos +if include_kttsd_epos + epos_DIR = epos +endif + +SUBDIRS = \ + $(festivalint_DIR) \ + $(freetts_DIR) \ + $(command_DIR) \ + $(hadifix_DIR) \ + $(flite_DIR) \ + $(epos_DIR) 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 diff --git a/kttsd/plugins/epos/Makefile.am b/kttsd/plugins/epos/Makefile.am new file mode 100644 index 0000000..8f40ca7 --- /dev/null +++ b/kttsd/plugins/epos/Makefile.am @@ -0,0 +1,20 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_eposplugin.la + +libkttsd_eposplugin_la_SOURCES = \ + eposconfwidget.ui \ + eposconf.cpp \ + eposproc.cpp \ + eposplugin.cpp +libkttsd_eposplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_eposplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_eposplugin.desktop +servicesdir = $(kde_servicesdir) + +noinst_HEADERS = eposproc.h eposconf.h eposconfwidget.h diff --git a/kttsd/plugins/epos/README b/kttsd/plugins/epos/README new file mode 100644 index 0000000..2f61a9c --- /dev/null +++ b/kttsd/plugins/epos/README @@ -0,0 +1,3 @@ +This is the directory containing the Epos plug in. +This plugin is developed and maintained by Gary Cramblitt. +<garycramblitt@comcast.net> diff --git a/kttsd/plugins/epos/configure.in.bot b/kttsd/plugins/epos/configure.in.bot new file mode 100644 index 0000000..59f4fdc --- /dev/null +++ b/kttsd/plugins/epos/configure.in.bot @@ -0,0 +1,15 @@ +if test "x$epos_bindir" = "xno"; then + if test "$compile_epos_plugin" = "yes"; then + echo "" + echo "=======================================================" + echo "The Epos program does not appear to be" + echo "installed on this system. The epos plugin will" + echo "be built, but you need to install epos before you" + echo "can use it. You can get it at" + echo " http://epos.ure.cas.cz/" + echo "Debian users: apt-get install epos" + echo "======================================================" + all_tests=bad + fi +fi + diff --git a/kttsd/plugins/epos/configure.in.in b/kttsd/plugins/epos/configure.in.in new file mode 100644 index 0000000..5a0f655 --- /dev/null +++ b/kttsd/plugins/epos/configure.in.in @@ -0,0 +1,22 @@ +dnl ========================================= +dnl checks for Festival Lite (epos) Plug In +dnl ========================================= + +AC_ARG_ENABLE(kttsd-epos, + AC_HELP_STRING([--enable-kttsd-epos], + [build KTTSD Epos plugin [default=yes]]), + epos_plugin=$enableval, + epos_plugin=yes) + +compile_epos_plugin="yes" + +if test "x$epos_plugin" = "xno"; then + compile_epos_plugin="no" +fi + +dnl Check for epos executable. +dnl Note that epos plugin is always built, unless +dnl user overrides on configure command line. +AC_PATH_PROG(epos_bindir, "epos", "no") + +AM_CONDITIONAL(include_kttsd_epos, test "x$compile_epos_plugin" != "xno") diff --git a/kttsd/plugins/epos/eposconf.cpp b/kttsd/plugins/epos/eposconf.cpp new file mode 100644 index 0000000..063c9f9 --- /dev/null +++ b/kttsd/plugins/epos/eposconf.cpp @@ -0,0 +1,319 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration widget and functions for Epos plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// C++ includes. +#include <math.h> + +// Qt includes. +#include <qfile.h> +#include <qapplication.h> +#include <qtextcodec.h> +#include <qlayout.h> +#include <qslider.h> + +// KDE includes. +#include <kdialog.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kcombobox.h> +#include <klocale.h> +#include <knuminput.h> + +// KTTS includes. +#include <testplayer.h> + +// Epos Plugin includes. +#include "eposproc.h" +#include "eposconf.h" +#include "eposconf.moc" + +/** Constructor */ +EposConf::EposConf( QWidget* parent, const char* name, const QStringList& /*args*/) : + PlugInConf(parent, name) +{ + // kdDebug() << "EposConf::EposConf: Running" << endl; + m_eposProc = 0; + m_progressDlg = 0; + + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "EposConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new EposConfWidget(this, "EposConfigWidget"); + 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->eposServerPath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->eposClientPath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->timeBox, SIGNAL(valueChanged(int)), + this, SLOT(timeBox_valueChanged(int))); + connect(m_widget->frequencyBox, SIGNAL(valueChanged(int)), + this, SLOT(frequencyBox_valueChanged(int))); + connect(m_widget->timeSlider, SIGNAL(valueChanged(int)), + this, SLOT(timeSlider_valueChanged(int))); + connect(m_widget->frequencySlider, SIGNAL(valueChanged(int)), + this, SLOT(frequencySlider_valueChanged(int))); + connect(m_widget->timeBox, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->timeSlider, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->frequencyBox, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->frequencySlider, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->characterCodingBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->eposServerOptions, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->eposClientOptions, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->eposTest, SIGNAL(clicked()), + this, SLOT(slotEposTest_clicked())); +} + +/** Destructor */ +EposConf::~EposConf(){ + // kdDebug() << "Running: EposConf::~EposConf()" << endl; + if (!m_waveFile.isNull()) QFile::remove(m_waveFile); + delete m_eposProc; + delete m_progressDlg; +} + +void EposConf::load(KConfig *config, const QString &configGroup){ + // kdDebug() << "EposConf::load: Running " << endl; + + config->setGroup(configGroup); + m_widget->eposServerPath->setURL(config->readEntry("EposServerExePath", "eposd")); + m_widget->eposClientPath->setURL(config->readEntry("EposClientExePath", "say-epos")); + m_widget->eposServerOptions->setText(config->readEntry("EposServerOptions", "")); + m_widget->eposClientOptions->setText(config->readEntry("EposClientOptions", "")); + QString codecString = config->readEntry("Codec", "ISO 8859-2"); + int codec = PlugInProc::codecNameToListIndex(codecString, m_codecList); + m_widget->timeBox->setValue(config->readNumEntry("time", 100)); + m_widget->frequencyBox->setValue(config->readNumEntry("pitch", 100)); + m_widget->characterCodingBox->setCurrentItem(codec); +} + +/** +* Converts a language code into the language setting passed to Epos synth. +*/ +QString EposConf::languageCodeToEposLanguage(const QString &languageCode) +{ + QString eposLanguage; + if (languageCode.left(2) == "cs") eposLanguage = "czech"; + if (languageCode.left(2) == "sk") eposLanguage = "slovak"; + return eposLanguage; +} + +void EposConf::save(KConfig *config, const QString &configGroup){ + // kdDebug() << "EposConf::save: Running" << endl; + + config->setGroup("Epos"); + config->writeEntry("EposServerExePath", + realFilePath(m_widget->eposServerPath->url())); + config->writeEntry("EposClientExePath", + realFilePath(m_widget->eposClientPath->url())); + config->writeEntry("Language", languageCodeToEposLanguage(m_languageCode)); + config->setGroup(configGroup); + config->writeEntry("EposServerExePath", + realFilePath(m_widget->eposServerPath->url())); + config->writeEntry("EposClientExePath", + realFilePath(m_widget->eposClientPath->url())); + config->writeEntry("EposServerOptions", m_widget->eposServerOptions->text()); + config->writeEntry("EposClientOptions", m_widget->eposClientOptions->text()); + config->writeEntry("time", m_widget->timeBox->value()); + config->writeEntry("pitch", m_widget->frequencyBox->value()); + int codec = m_widget->characterCodingBox->currentItem(); + config->writeEntry("Codec", PlugInProc::codecIndexToCodecName(codec, m_codecList)); +} + +void EposConf::defaults(){ + // kdDebug() << "EposConf::defaults: Running" << endl; + // Epos server command changed from epos to eposd. Epos client command changed from + // say to say-epos. These changes appeared around Epos v2.5.35. Try for these automatically. + QString exeName = "eposd"; + if (realFilePath(exeName).isEmpty()) + if (!realFilePath("epos").isEmpty()) + exeName = "epos"; + m_widget->eposServerPath->setURL(exeName); + exeName = "say-epos"; + if (realFilePath(exeName).isEmpty()) + if (!realFilePath("say").isEmpty()) + exeName = "say"; + m_widget->eposClientPath->setURL(exeName); + m_widget->eposServerOptions->setText(""); + m_widget->eposClientOptions->setText(""); + m_widget->timeBox->setValue(100); + timeBox_valueChanged(100); + m_widget->frequencyBox->setValue(100); + frequencyBox_valueChanged(100); + int codec = PlugInProc::codecNameToListIndex("ISO 8859-2", m_codecList); + m_widget->characterCodingBox->setCurrentItem(codec); +} + +void EposConf::setDesiredLanguage(const QString &lang) +{ + m_languageCode = lang; +} + +QString EposConf::getTalkerCode() +{ + QString eposServerExe = realFilePath(m_widget->eposServerPath->url()); + QString eposClientExe = realFilePath(m_widget->eposClientPath->url()); + if (!eposServerExe.isEmpty() && !eposClientExe.isEmpty()) + { + if (!getLocation(eposServerExe).isEmpty() && !getLocation(eposClientExe).isEmpty()) + { + QString rate = "medium"; + if (m_widget->timeBox->value() < 75) rate = "slow"; + if (m_widget->timeBox->value() > 125) rate = "fast"; + 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(rate) + .arg("Epos TTS Synthesis System"); + } + } + return QString::null; +} + +void EposConf::slotEposTest_clicked() +{ + // kdDebug() << "EposConf::slotEposTest_clicked(): Running" << endl; + // If currently synthesizing, stop it. + if (m_eposProc) + m_eposProc->stopText(); + else + { + m_eposProc = new EposProc(); + connect (m_eposProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + // Create a temp file name for the wave file. + KTempFile tempFile (locateLocal("tmp", "eposplugin-"), ".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_epos_testdlg", + i18n("Testing"), + i18n("Testing."), + true); + m_progressDlg->progressBar()->hide(); + m_progressDlg->setAllowCancel(true); + + // TODO: Whenever server options change, the server must be restarted. + // TODO: Do codec names contain non-ASCII characters? + connect (m_eposProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + m_eposProc->synth( + testMsg, + tmpWaveFile, + realFilePath(m_widget->eposServerPath->url()), + realFilePath(m_widget->eposClientPath->url()), + m_widget->eposServerOptions->text(), + m_widget->eposClientOptions->text(), + PlugInProc::codecIndexToCodec(m_widget->characterCodingBox->currentItem(), m_codecList), + languageCodeToEposLanguage(m_languageCode), + m_widget->timeBox->value(), + m_widget->frequencyBox->value() + ); + + // Display progress dialog modally. Processing continues when plugin signals synthFinished, + // or if user clicks Cancel button. + m_progressDlg->exec(); + disconnect (m_eposProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + if (m_progressDlg->wasCancelled()) m_eposProc->stopText(); + delete m_progressDlg; + m_progressDlg = 0; +} + +void EposConf::slotSynthFinished() +{ + // If user canceled, progress dialog is gone, so exit. + if (!m_progressDlg) + { + m_eposProc->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_eposProc->getFilename(); + // Tell synth we're done. + m_eposProc->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 EposConf::slotSynthStopped() +{ + // Clean up after canceling test. + QString filename = m_eposProc->getFilename(); + if (!filename.isNull()) QFile::remove(filename); +} + +// 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 EposConf::percentToSlider(int percentValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor (0.5 + alpha * (log(percentValue)-log(50))); +} + +int EposConf::sliderToPercent(int sliderValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor(0.5 + exp (sliderValue/alpha + log(50))); +} + +void EposConf::timeBox_valueChanged(int percentValue) { + m_widget->timeSlider->setValue (percentToSlider (percentValue)); +} + +void EposConf::frequencyBox_valueChanged(int percentValue) { + m_widget->frequencySlider->setValue(percentToSlider(percentValue)); +} + +void EposConf::timeSlider_valueChanged(int sliderValue) { + m_widget->timeBox->setValue (sliderToPercent (sliderValue)); +} + +void EposConf::frequencySlider_valueChanged(int sliderValue) { + m_widget->frequencyBox->setValue(sliderToPercent(sliderValue)); +} diff --git a/kttsd/plugins/epos/eposconf.h b/kttsd/plugins/epos/eposconf.h new file mode 100644 index 0000000..5c3fbe3 --- /dev/null +++ b/kttsd/plugins/epos/eposconf.h @@ -0,0 +1,141 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration widget and functions for Epos plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _EPOSCONF_H_ +#define _EPOSCONF_H_ + +// Qt includes. +#include <qstring.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> +#include <kprogress.h> + +// KTTS includes. +#include <pluginconf.h> + +// Epos plugin includes. +#include "eposconfwidget.h" + +class EposProc; +class KProgressDialog; + +class EposConf : public PlugInConf { + Q_OBJECT + + public: + /** Constructor */ + EposConf( QWidget* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~EposConf(); + + /** 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() << "EposConf::configChanged: Running" << endl; + emit changed(true); + }; + void slotEposTest_clicked(); + void slotSynthFinished(); + void slotSynthStopped(); + void timeBox_valueChanged(int percentValue); + void frequencyBox_valueChanged(int percentValue); + void timeSlider_valueChanged(int sliderValue); + void frequencySlider_valueChanged(int sliderValue); + + private: + /** + * Converts a language code into the language setting passed to Epos synth. + */ + QString languageCodeToEposLanguage(const QString &languageCode); + + int percentToSlider(int percentValue); + int sliderToPercent(int sliderValue); + + // Language code. + QString m_languageCode; + + // Configuration widget. + EposConfWidget* m_widget; + + // Epos synthesizer. + EposProc* m_eposProc; + // Synthesized wave file name. + QString m_waveFile; + // Progress dialog. + KProgressDialog* m_progressDlg; + // List of displayed codec names. + QStringList m_codecList; +}; +#endif // _EPOSCONF_H_ diff --git a/kttsd/plugins/epos/eposconfwidget.ui b/kttsd/plugins/epos/eposconfwidget.ui new file mode 100644 index 0000000..23e47da --- /dev/null +++ b/kttsd/plugins/epos/eposconfwidget.ui @@ -0,0 +1,610 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>EposConfWidget</class> +<author>Gary Cramblitt</author> +<widget class="QWidget"> + <property name="name"> + <cstring>EposConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>535</width> + <height>381</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Epos Config UI</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the configuration dialog for the Epos Czech and Slovak speech synthesizer.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>eposConfigurationBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>GroupBoxPanel</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="title"> + <string>E&pos Configuration</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the configuration dialog for the Epos Czech and Slovak 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="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout13</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>Specifies which character encoding is used for passing the text.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout17</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast".</string> + </property> + </widget> + <widget class="QLabel"> + <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>Sets the tone (frequency) of speech. Slide the slider to the left to lower the voice tone; to the right to increase tone. Anything less than 75 percent is considered "low", and anything greater than 125 percent is considered "high".</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout15</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KIntSpinBox"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast".</string> + </property> + </widget> + <widget class="KIntSpinBox"> + <property name="name"> + <cstring>frequencyBox</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>Sets the tone (frequency) of speech. Slide the slider to the left to lower the voice tone; to the right to increase tone. Anything less than 75 percent is considered "low", and anything greater than 125 percent is considered "high".</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout16</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QSlider"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast".</string> + </property> + </widget> + <widget class="QSlider"> + <property name="name"> + <cstring>frequencySlider</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>Sets the tone (frequency) of speech. Slide the slider to the left to lower the voice tone; to the right to increase tone. Anything less than 75 percent is considered "low", and anything greater than 125 percent is considered "high".</string> + </property> + </widget> + </vbox> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>eposServerPathLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Epos server executable path:</string> + </property> + <property name="alignment"> + <set>AlignVCenter</set> + </property> + <property name="buddy" stdset="0"> + <cstring>festivalVoicesPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>If the Epos server program will be found due to your PATH environment variable, simply enter "epos", otherwise enter the full path to the Epos server executable program.</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>eposClientPathLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Epos client executable path:</string> + </property> + <property name="alignment"> + <set>AlignVCenter</set> + </property> + <property name="buddy" stdset="0"> + <cstring>festivalVoicesPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>If the Epos client program will be found due to the PATH environment variable, simply enter "say" here. Otherwise, specify the full path to the Epos client program.</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KURLRequester"> + <property name="name"> + <cstring>eposServerPath</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="url" stdset="0"> + <string>epos</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If the Epos server program will be found due to your PATH environment variable, simply enter "epos", otherwise enter the full path to the Epos server executable program.</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>eposClientPath</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="url" stdset="0"> + <string>say</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If the Epos client program will be found due to the PATH environment variable, simply enter "say" here. Otherwise, specify the full path to the Epos client program.</string> + </property> + </widget> + </vbox> + </widget> + </hbox> + </widget> + <widget class="QGroupBox" row="3" column="0"> + <property name="name"> + <cstring>advancedGroupBox</cstring> + </property> + <property name="title"> + <string>Additional Options (advanced)</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="1"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLineEdit"> + <property name="name"> + <cstring>eposServerOptions</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Optional. Enter any server command line options here. To see available options, enter "epos -h" in a terminal. Do not use "-o".</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>eposClientOptions</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Specify options to be passed to Epos client. To see available options, enter "say -h" in a terminal. Do not use "-o".</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout12</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>eposServerOptionsLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Epos server:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignLeft</set> + </property> + <property name="buddy" stdset="0"> + <cstring>festivalVoicesPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Optional. Enter any server command line options here. To see available options, enter "epos -h" in a terminal. Do not use "-o".</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>eposClientOptionsLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Epos client:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignLeft</set> + </property> + <property name="buddy" stdset="0"> + <cstring>festivalVoicesPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Specify options to be passed to Epos client. Do not use -o. To see available options, enter "say -h" in a terminal. Do not use "-o".</string> + </property> + </widget> + </vbox> + </widget> + </grid> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="4" column="0"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>410</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>eposTest</cstring> + </property> + <property name="text"> + <string>&Test</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to test the configuration. If correct, you will hear a sentence spoken.</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>eposServerPath</tabstop> + <tabstop>eposClientPath</tabstop> + <tabstop>characterCodingBox</tabstop> + <tabstop>eposTest</tabstop> +</tabstops> +<includes> + <include location="global" impldecl="in declaration">kurlrequester.h</include> + <include location="global" impldecl="in implementation">kurlrequester.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kcombobox.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> +</includehints> +</UI> diff --git a/kttsd/plugins/epos/eposplugin.cpp b/kttsd/plugins/epos/eposplugin.cpp new file mode 100644 index 0000000..9bc0bee --- /dev/null +++ b/kttsd/plugins/epos/eposplugin.cpp @@ -0,0 +1,31 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so Epos can be used as plug in. + ------------------- + Copyright: + (C) 2005 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "eposconf.h" +#include "eposproc.h" + +typedef K_TYPELIST_2( EposProc, EposConf ) Epos; +K_EXPORT_COMPONENT_FACTORY( libkttsd_eposplugin, KGenericFactory<Epos>("kttsd_epos") ) + diff --git a/kttsd/plugins/epos/eposproc.cpp b/kttsd/plugins/epos/eposproc.cpp new file mode 100644 index 0000000..35bc1d3 --- /dev/null +++ b/kttsd/plugins/epos/eposproc.cpp @@ -0,0 +1,389 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + eposproc.cpp + Main speaking functions for the Epos Plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// C++ includes. +#include <math.h> + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> +#include <qtextcodec.h> +#include <qfile.h> + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kprocess.h> + +// Epos Plugin includes. +#include "eposproc.h" +#include "eposproc.moc" + +/** Constructor */ +EposProc::EposProc( QObject* parent, const char* name, const QStringList& ) : + PlugInProc( parent, name ){ + kdDebug() << "EposProc::EposProc: Running" << endl; + m_state = psIdle; + m_waitingStop = false; + m_eposServerProc = 0; + m_eposProc = 0; +} + +/** Destructor */ +EposProc::~EposProc(){ + kdDebug() << "EposProc::~EposProc:: Running" << endl; + if (m_eposProc) + { + stopText(); + delete m_eposProc; + } + delete m_eposServerProc; +} + +/** Initialize the speech */ +bool EposProc::init(KConfig* config, const QString& configGroup) +{ + // kdDebug() << "EposProc::init: Running" << endl; + // kdDebug() << "Initializing plug in: Epos" << endl; + // Retrieve path to epos executable. + config->setGroup(configGroup); + m_eposServerExePath = config->readEntry("EposServerExePath", "epos"); + m_eposClientExePath = config->readEntry("EposClientExePath", "say"); + m_eposLanguage = config->readEntry("Language", QString::null); + m_time = config->readNumEntry("time", 100); + m_pitch = config->readNumEntry("pitch", 100); + m_eposServerOptions = config->readEntry("EposServerOptions", QString::null); + m_eposClientOptions = config->readEntry("EposClientOptions", QString::null); + kdDebug() << "EposProc::init: path to epos server: " << m_eposServerExePath << endl; + kdDebug() << "EposProc::init: path to epos client: " << m_eposClientExePath << endl; + + QString codecString = config->readEntry("Codec", "Local"); + m_codec = codecNameToCodec(codecString); + // Start the Epos server if not already started. + if (!m_eposServerProc) + { + m_eposServerProc = new KProcess; + *m_eposServerProc << m_eposServerExePath; + if (!m_eposServerOptions.isEmpty()) + *m_eposServerProc << m_eposServerOptions; + connect(m_eposServerProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_eposServerProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + m_eposServerProc->start(KProcess::DontCare, KProcess::AllOutput); + } + + kdDebug() << "EposProc::init: Initialized with 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 EposProc::sayText(const QString &text) +{ + synth(text, QString::null, m_eposServerExePath, m_eposClientExePath, + m_eposServerOptions, m_eposClientOptions, + m_codec, m_eposLanguage, m_time, m_pitch); +} + +/** +* 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 EposProc::synthText(const QString& text, const QString& suggestedFilename) +{ + synth(text, suggestedFilename, m_eposServerExePath, m_eposClientExePath, + m_eposServerOptions, m_eposClientOptions, + m_codec, m_eposLanguage, m_time, m_pitch); +} + +/** +* Say or Synthesize text. +* @param text The text to be synthesized. +* @param suggestedFilename If not Null, synthesize only to this filename, otherwise +* synthesize and audibilize the text. +* @param eposServerExePath Path to the Epos server executable. +* @param eposClientExePath Path to the Epos client executable. +* @param eposServerOptions Options passed to Epos server executable. +* @param eposClientOptions Options passed to Epos client executable (don't include -o). +* @param codec Codec for encoding of text. +* @param eposLanguage Epos language setting. "czech", "slovak", +* or null (default language). +* @param time Speed percentage. 50 to 200. 200% = 2x normal. +* @param pitch Pitch persentage. 50 to 200. +*/ +void EposProc::synth( + const QString &text, + const QString &suggestedFilename, + const QString& eposServerExePath, + const QString& eposClientExePath, + const QString& eposServerOptions, + const QString& eposClientOptions, + QTextCodec *codec, + const QString& eposLanguage, + int time, + int pitch) +{ + // kdDebug() << "Running: EposProc::synth(const QString &text)" << endl; + + if (m_eposProc) + { + if (m_eposProc->isRunning()) m_eposProc->kill(); + delete m_eposProc; + m_eposProc = 0; + } + // Start the Epos server if not already started. + if (!m_eposServerProc) + { + m_eposServerProc = new KProcess; + *m_eposServerProc << eposServerExePath; + if (!eposServerOptions.isEmpty()) + *m_eposServerProc << eposServerOptions; + connect(m_eposServerProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_eposServerProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + m_eposServerProc->start(KProcess::DontCare, KProcess::AllOutput); + kdDebug() << "EposProc:: Epos server process started" << endl; + } + +// // Encode the text. +// // 1.a) encode the text +// m_encText = QCString(); +// QTextStream ts (m_encText, IO_WriteOnly); +// ts.setCodec(codec); +// ts << text; +// ts << endl; // Some synths need this, eg. flite. + + if (codec) + m_encText = codec->fromUnicode(text); + else + m_encText = text.latin1(); // Should not happen, but just in case. + + // kdDebug()<< "EposProc::synth: Creating Epos object" << endl; + m_eposProc = new KProcess; + m_eposProc->setUseShell(true); + QString languageCode; + if (eposLanguage == "czech") + languageCode == "cz"; + else if (eposLanguage == "slovak") + languageCode == "sk"; + if (!languageCode.isEmpty()) + { + m_eposProc->setEnvironment("LANG", languageCode + "." + codec->mimeName()); + m_eposProc->setEnvironment("LC_CTYPE", languageCode + "." + codec->mimeName()); + } + *m_eposProc << eposClientExePath; + // Language. + if (!eposLanguage.isEmpty()) + *m_eposProc << QString("--language=%1").arg(eposLanguage); + // Rate (speed). + // Map 50% to 200% onto 0 to 1000. + // slider = alpha * (log(percent)-log(50)) + // with alpha = 1000/(log(200)-log(50)) + double alpha = 1000 / (log(200) - log(50)); + int slider = (int)floor (0.5 + alpha * (log(time)-log(50))); + // Center at 0. + slider = slider - 500; + // Map -500 to 500 onto 45 to -45 then shift to 130 to 40 (85 midpoint). + float stretchValue = (-float(slider) * 45.0 / 500.0) + 85.0; + QString timeMsg = QString("--init_t=%1").arg(stretchValue, 0, 'f', 3); + *m_eposProc << timeMsg; + // Pitch. Map 50% to 200% onto 50 to 200. easy. + QString pitchMsg = QString("--init_f=%1").arg(pitch); + *m_eposProc << pitchMsg; + // Output file. + if (!suggestedFilename.isEmpty()) + *m_eposProc << "-o"; + if (!eposClientOptions.isEmpty()) + *m_eposProc << eposClientOptions; + *m_eposProc << "-"; // Read from StdIn. + if (!suggestedFilename.isEmpty()) + *m_eposProc << " >" + suggestedFilename; + connect(m_eposProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + connect(m_eposProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_eposProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + connect(m_eposProc, SIGNAL(wroteStdin(KProcess*)), + this, SLOT(slotWroteStdin(KProcess* ))); + if (suggestedFilename.isEmpty()) + m_state = psSaying; + else + m_state = psSynthing; + + // Ok, let's rock. + m_synthFilename = suggestedFilename; + // kdDebug() << "EposProc::synth: Synthing text: '" << text << "' using Epos plug in" << endl; + if (!m_eposProc->start(KProcess::NotifyOnExit, KProcess::All)) + { + kdDebug() << "EposProc::synth: Error starting Epos process. Is epos in the PATH?" << endl; + m_state = psIdle; + return; + } + // kdDebug() << "EposProc:synth: Epos initialized" << endl; + // kdDebug() << "EposProc::synth: m_encText.length() = " << m_encText.length() << " text.length() = " + // << text.length() << endl; + if (!m_eposProc->writeStdin(m_encText, m_encText.length())) + kdDebug() << "EposProc::synth: Error writing to Epos client StdIn." << endl; +} + +/** +* 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 EposProc::getFilename() +{ + kdDebug() << "EposProc::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 EposProc::stopText(){ + kdDebug() << "EposProc::stopText:: Running" << endl; + if (m_eposProc) + { + if (m_eposProc->isRunning()) + { + kdDebug() << "EposProc::stopText: killing Epos." << endl; + m_waitingStop = true; + m_eposProc->kill(); + } else m_state = psIdle; + } else m_state = psIdle; + kdDebug() << "EposProc::stopText: Epos stopped." << endl; +} + +void EposProc::slotProcessExited(KProcess*) +{ + // kdDebug() << "EposProc:slotProcessExited: Epos 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 EposProc::slotReceivedStdout(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "EposProc::slotReceivedStdout: Received output from Epos: " << buf << endl; +} + +void EposProc::slotReceivedStderr(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "EposProc::slotReceivedStderr: Received error from Epos: " << buf << endl; +} + +void EposProc::slotWroteStdin(KProcess*) +{ + // kdDebug() << "EposProc::slotWroteStdin: closing Stdin" << endl; + m_eposProc->closeStdin(); + m_encText = QCString(); +} + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @see pluginState +*/ +pluginState EposProc::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 EposProc::ackFinished() +{ + if (m_state == psFinished) + { + m_state = psIdle; + m_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 EposProc::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 EposProc::supportsSynth() { return true; } diff --git a/kttsd/plugins/epos/eposproc.h b/kttsd/plugins/epos/eposproc.h new file mode 100644 index 0000000..b2d1d90 --- /dev/null +++ b/kttsd/plugins/epos/eposproc.h @@ -0,0 +1,241 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + eposproc.h + Main speaking functions for the Epos Plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _EPOSPROC_H_ +#define _EPOSPROC_H_ + +// Qt includes. +#include <qstringlist.h> +#include <qmutex.h> + +// KTTS includes. +#include <pluginproc.h> + +class KProcess; +class QTextCodec; + +class EposProc : public PlugInProc{ + Q_OBJECT + + public: + /** + * Constructor + */ + EposProc( QObject* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** + * Destructor + */ + virtual ~EposProc(); + + /** + * Initializate the speech engine. + * @param config Settings object. + * @param configGroup Settings group. + */ + virtual 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 text The text to be synthesized. + * @param suggestedFilename If not Null, synthesize only to this filename, otherwise + * synthesize and audibilize the text. + * @param eposServerExePath Path to the Epos server executable. + * @param eposClientExePath Path to the Epos client executable. + * @param eposServerOptions Options passed to Epos server executable. + * @param eposClientOptions Options passed to Epos client executable (don't include -o). + * @param codec Codec for encoding of text. + * @param eposLanguage Epos language setting. "czech", "slovak", + * or null (default language). + * @param time Speed percentage. 50 to 200. 200% = 2x normal. + * @param pitch Pitch persentage. 50 to 200. + */ + void synth( + const QString &text, + const QString &suggestedFilename, + const QString& eposServerExePath, + const QString& eposClientExePath, + const QString& eposServerOptions, + const QString& eposClientOptions, + QTextCodec *codec, + const QString& eposLanguage, + int time, + int pitch); + + 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: + + /** + * Path to epos executables (from config). + */ + QString m_eposServerExePath; + QString m_eposClientExePath; + + /** + * User options passed to executables (from config). + */ + QString m_eposServerOptions; + QString m_eposClientOptions; + + /** + * Epos Server process. + */ + KProcess* m_eposServerProc; + + /** + * Epos Client process + */ + KProcess* m_eposProc; + + /** + * Epos language setting. "czech", "slovak", or Null (use default language). + */ + QString m_eposLanguage; + + /** + * Rate (speed) from config file. + */ + int m_time; + + /** + * Pitch from the config file. + */ + int m_pitch; + + /** + * Codec. + */ + QTextCodec* m_codec; + + /** + * Encoded buffer to be sent to Epos client. + */ + QCString m_encText; + + /** + * Synthesis filename. + */ + QString m_synthFilename; + + /** + * Plugin state. + */ + pluginState m_state; + + /** + * True when stopText has been called. Used to force transition to psIdle when + * Epos exits. + */ + bool m_waitingStop; + +}; + +#endif // _EPOSPROC_H_ diff --git a/kttsd/plugins/epos/kttsd_eposplugin.desktop b/kttsd/plugins/epos/kttsd_eposplugin.desktop new file mode 100644 index 0000000..7673929 --- /dev/null +++ b/kttsd/plugins/epos/kttsd_eposplugin.desktop @@ -0,0 +1,87 @@ +[Desktop Entry] +Name=Epos TTS Synthesis System +Name[ca]=Sistema de síntesi Epos TTS +Name[cs]=Epos TTS +Name[da]=Epos TTS Synthesis-system +Name[de]=Epos TTS-Synthese-System +Name[el]=Σύστημα σύνθεσης Epos TTS +Name[es]=Sistema de síntesis Epos TTS +Name[et]=Kõnesünteesisüsteem Epos TTS +Name[eu]=Epos TTS sintesi-sistema +Name[fa]=سیستم ترکیبدهی Epos TTS +Name[fi]=Epos TTS -syntetisoijasysteemi +Name[fr]=Système de synthèse Epos TTS +Name[ga]=Córas Sintéise TTS Epos +Name[gl]=Sistema de Síntese de TTS Epos +Name[hu]=Epos szövegfelolvasó motor +Name[it]=Sistema di sintesi TTS Epos +Name[ja]=Epos TTS シンセサイズシステム +Name[ka]=Epos TTS სინთეზის სისტემა +Name[km]=ប្រព័ន្ធសង្គ្រោះ Epos TTS +Name[mk]=Epos TTS систем за синтеза +Name[ms]=Sistem Sintesis Epos TTS +Name[nb]=Epos TTT syntesesystem +Name[nds]=Epos Blicksnuut +Name[ne]=Epos TTS सिन्थेसिस प्रणाली +Name[nl]=Epos TTS Synthesis-systeem +Name[pa]=Epos TTS ਸੰਸਲੇਸ਼ਣ ਸਿਸਟਮ +Name[pl]=System syntezy mowy Epos +Name[pt]=Sistema de Síntese Epos TTS +Name[pt_BR]=Sistema de Sintetizador de Fala Epos +Name[sk]=Systém Epos TTS Synthesis +Name[sl]=Sistem sinteze besedila v govor Epos +Name[sr]=Систем за синтезу Epos TTS +Name[sr@Latn]=Sistem za sintezu Epos TTS +Name[sv]=Epos TTS syntessystem +Name[ta]=Epos TTS கூட்டிணைப்பு அமைப்பு +Name[tg]=Системаи Синтезиси Epos TTS +Name[tr]=Epos TTS Sentezleme Sistemi +Name[vi]=Hệ thống Tổng hợp Văn bản sang Tiếng nói Epos +Name[zh_TW]=Epos TTS 合成系統 +Comment=Epos TTS speech synthesizer +Comment[bg]=Синтезатор на глас Epos TTS +Comment[ca]=Sintetitzador de veu Epos TTS +Comment[cs]=Hlasový syntetizér Epos TTS +Comment[da]=Epos TTS tale-synthesizer +Comment[de]=Epos TTS-Sprachsynthesizer +Comment[el]=Συνθέτης ομιλίας Epos TTS +Comment[es]=Sintetizador de texto a voz Epos TTS +Comment[et]=Kõnesüntesaator Epos TTS +Comment[eu]=Epos TTS hizketa-sintetizadorea +Comment[fa]=ترکیبدهندۀ گفتار Epos TTS +Comment[fi]=Epos TTS -puhesyntetisaattori +Comment[fr]=Synthèse vocale Epos TTS +Comment[ga]=Sintéiseoir cainte TTS Epos +Comment[gl]=Sintetizador de fala TTS Epos +Comment[hu]=Epos TTS beszédszintetizátor +Comment[is]=Epos TTS talgerfill +Comment[it]=Sintetizzatore vocale TTS Epos +Comment[ja]=Epos TTS スピーチシンセサイザ +Comment[ka]=Epos TTS სიტყვის სინთეზატორი +Comment[km]=កម្មវិធីសង្គ្រោះការនិយាយ Epos TTS +Comment[mk]=Epos TTS синтетизатор на говор +Comment[ms]=Pensintesis tutur Epos TTS +Comment[nb]=Epos TTT talesyntetisering +Comment[nds]=Blicksnuut vun Epos +Comment[ne]=Epos TTS संवाद सिन्थेसाइजर +Comment[nl]=Epos TTS spraaksynthesizer +Comment[pa]=Epos TTS ਬੋਲੀ ਸੰਸਲੇਸ਼ਣ +Comment[pl]=Syntezator mowy Epos +Comment[pt]=O sintetizador de fala Epos TTS +Comment[pt_BR]=Sistema de Sintetizador de Fala Epos +Comment[ru]=Синтезатор речи Epos TTS +Comment[sk]=Syntetizátor reči Epos TTS +Comment[sl]=Sintetizator besedila v govor Epos +Comment[sr]=Синтетизатор говора Epos TTS +Comment[sr@Latn]=Sintetizator govora Epos TTS +Comment[sv]=Epos TTS talsyntes +Comment[ta]=Epos TTS பேச்சு கூட்டிணைப்பான் +Comment[tg]=Таҳлилгари овози Epos TTS +Comment[tr]=Epos TTS konuşma sentezleyicisi +Comment[uk]=Синтезатор мовлення Epos TTS +Comment[vi]=Trình tổng hợp Văn bản sang Tiếng nói Epos +Comment[zh_TW]=Epos TTS 語音合成器 +Type=Service +ServiceTypes=KTTSD/SynthPlugin +X-KDE-Library=libkttsd_eposplugin +X-KDE-Languages=cs,sk diff --git a/kttsd/plugins/festivalint/Makefile.am b/kttsd/plugins/festivalint/Makefile.am new file mode 100644 index 0000000..7c6ca0e --- /dev/null +++ b/kttsd/plugins/festivalint/Makefile.am @@ -0,0 +1,27 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_festivalintplugin.la + +libkttsd_festivalintplugin_la_SOURCES = \ + festivalintconfwidget.ui \ + festivalintconf.cpp \ + festivalintproc.cpp \ + festivalintplugin.cpp +libkttsd_festivalintplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_festivalintplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_festivalintplugin.desktop +servicesdir = $(kde_servicesdir) + +# Install data files. +festivalintdatadir = $(kde_datadir)/kttsd/festivalint/ +festivalintdata_DATA = voices sabletowave.scm + +festivalintxsltdatadir = $(kde_datadir)/kttsd/festivalint/xslt/ +festivalintxsltdata_DATA = SSMLtoSable.xsl + +noinst_HEADERS = festivalintconfwidget.h diff --git a/kttsd/plugins/festivalint/README b/kttsd/plugins/festivalint/README new file mode 100644 index 0000000..3903dd0 --- /dev/null +++ b/kttsd/plugins/festivalint/README @@ -0,0 +1,9 @@ +This is the directory containing the Festival (Interactive) plug in. +This plugin is developed and maintained by Gary Cramblitt. +<garycramblitt@comcast.net> + +Make sure that Festival has write access to the audio device + + chmod a+rw /dev/dsp* + + diff --git a/kttsd/plugins/festivalint/SSMLtoSable.xsl b/kttsd/plugins/festivalint/SSMLtoSable.xsl new file mode 100644 index 0000000..48c2fd0 --- /dev/null +++ b/kttsd/plugins/festivalint/SSMLtoSable.xsl @@ -0,0 +1,272 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- *********************************************************************** + SSMLtoSable.xsl + Stylesheet for transforming SSML into SABLE markup. + ============ + Copyright : (C) 2004 by Paul Giannaros + ============ + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + *************************************************************************** + + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + *************************************************************************** --> +<!-- @todo create a doc detailing which parts of SSML this sheet can handle --> + +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="html" indent="no"/> + +<!-- speak: Indicates SSML markup. --> +<xsl:template match="/speak"> + <SABLE><xsl:apply-templates/></SABLE> +</xsl:template> + +<!-- sub: The word that the text sounds like as abbreviations + can be pronounced differently. For example, + <sub alias="doctor">Dr.</sub> smith lives at 32 johnson <sub alias="drive">dr.</sub> --> +<xsl:template match="//sub"> + <xsl:choose> + <xsl:when test="@alias"> + <xsl:value-of select="@alias"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="."/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- p: Indicate a paragraph of text --> +<xsl:template match="//p"> + <DIV TYPE="paragraph"><xsl:apply-templates/></DIV> +</xsl:template> +<!-- s: Forceefully indicate a sentence (Does not need to be used + if full stops are present) --> +<xsl:template match="//s"> + <DIV TYPE="sentence"><xsl:apply-templates/></DIV> +</xsl:template> + +<!-- emphasis: Emphasize a word or group of words. --> +<xsl:template match="//emphasis"> + <!-- SSML and SABLE both take the same values for their attributes - + strong, moderate, none, reduced --> + <EM TYPE="{@level}"><xsl:apply-templates/></EM> +</xsl:template> + +<xsl:template match="//voice"> + <!-- This is turned off because if Festival lacks an installed voice to match a gender + or voice name, it bombs out. argh! + <xsl:call-template name="voice"><xsl:with-param name="a" select="@*" /></xsl:call-template> --> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template name="voice"> + <xsl:param name="a" /> + <!-- Get the name of the tag we're creating and convert to a SABLE tag. --> + <xsl:variable name="tag"> + <xsl:choose> + <xsl:when test="name($a[1])='gender'">SPEAKER</xsl:when> + <xsl:when test="name($a[1])='age'">SPEAKER</xsl:when> + <xsl:when test="name($a[1])='name'">SPEAKER</xsl:when> + <xsl:otherwise><xsl:value-of select="$a[1]"/></xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:element name="{$tag}"> + <!-- Create the right attribute to go with element $tag. --> + + <xsl:choose> + <!-- gender: + The gender of the voice. + Values such as male, female, and neutral are supported. --> + <xsl:when test="name($a[1])='gender' and $a[1]='male'"> + <xsl:attribute name="GENDER">male1</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='gender' and $a[1]='female'"> + <xsl:attribute name="GENDER">female1</xsl:attribute> + </xsl:when> + <!-- If none of the above match, take the users selected value. --> + <xsl:when test="name($a[1])='gender'"> + <xsl:attribute name="GENDER"><xsl:value-of select=" $a[1]"/></xsl:attribute> + </xsl:when> + + <!-- age: + The age of the voice. + Positive integer values are supported. --> + <xsl:when test="name($a[1])='age' and number($a[1]) < 10"> + <xsl:attribute name="AGE">child</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='age' and number($a[1]) < 20"> + <xsl:attribute name="AGE">teen</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='age' and number($a[1]) < 30"> + <xsl:attribute name="AGE">younger</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='age' and number($a[1]) < 50"> + <xsl:attribute name="AGE">middle</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='age'"> + <xsl:attribute name="AGE">older</xsl:attribute> + </xsl:when> + + <!-- name: + Voice name. Synth dependent. --> + <xsl:when test="name($a[1])='name'"> + <xsl:attribute name="NAME"><xsl:value-of select=" $a[1]"/></xsl:attribute> + </xsl:when> + </xsl:choose> + + <!-- Recursively call ourself. --> + <xsl:choose> + <xsl:when test="$a[2]"> + <xsl:call-template name="voice"><xsl:with-param name="a" select="$a[position()>1]" /></xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + + </xsl:element> +</xsl:template> + +<xsl:template match="prosody"> + <!-- contour and duration not supported. --> + <xsl:call-template name="prosody"><xsl:with-param name="a" + select="@pitch|@rate|@volume|@range" /> + </xsl:call-template> +</xsl:template> + +<xsl:template name="prosody"> + <xsl:param name="a" /> + <!-- Get the name of the tag we're creating and convert to a SABLE tag. --> + <xsl:variable name="tag"> + <xsl:choose> + <xsl:when test="name($a[1])='pitch'">PITCH</xsl:when> + <xsl:when test="name($a[1])='rate'">RATE</xsl:when> + <xsl:when test="name($a[1])='volume'">VOLUME</xsl:when> + <xsl:when test="name($a[1])='range'">PITCH</xsl:when> + <xsl:otherwise><xsl:value-of select="$a[1]"/></xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:element name="{$tag}"> + <!-- Create the right attribute to go with element $tag. --> + + <xsl:choose> + <!-- pitch: + The pitch with which the text is spoken. + Values such as x-high, high, low, etc. and percentages (+ or -) + are supported. --> + <xsl:when test="name($a[1])='pitch' and $a[1]='x-high'"> + <xsl:attribute name="BASE">70%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='pitch' and $a[1]='high'"> + <xsl:attribute name="BASE">40%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='pitch' and $a[1]='medium'"> + <xsl:attribute name="BASE">0%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='pitch' and $a[1]='low'"> + <xsl:attribute name="BASE">-40%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='pitch' and $a[1]='x-low'"> + <xsl:attribute name="BASE">-70%</xsl:attribute> + </xsl:when> + <!-- If none of the above match, take the users selected value. --> + <xsl:when test="name($a[1])='pitch'"> + <xsl:attribute name="BASE"><xsl:value-of select=" $a[1]"/></xsl:attribute> + </xsl:when> + + <!-- rate: + The speed at which the text is spoken. + Values such as x-fast, fast, slow, etc. and percentages (+ or -) + are supported. --> + <xsl:when test="name($a[1])='rate' and $a[1]='x-fast'"> + <xsl:attribute name="SPEED">70%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='rate' and $a[1]='fast'"> + <xsl:attribute name="SPEED">40%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='rate' and $a[1]='medium'"> + <xsl:attribute name="SPEED">0%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='rate' and $a[1]='slow'"> + <xsl:attribute name="SPEED">-40%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='rate' and $a[1]='x-slow'"> + <xsl:attribute name="SPEED">-70%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='rate'"> + <xsl:attribute name="SPEED"><xsl:value-of select=" $a[1]"/></xsl:attribute> + </xsl:when> + + <!-- volume: + The volume at which the text is spoken. + Values such as x-loud, loud, quiet, etc. and percentages (+ or -) + are supported. --> + <xsl:when test="name($a[1])='volume' and $a[1]='x-loud'"> + <xsl:attribute name="LEVEL">70%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='volume' and $a[1]='loud'"> + <xsl:attribute name="LEVEL">50%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='volume' and $a[1]='medium'"> + <xsl:attribute name="LEVEL">0%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='volume' and $a[1]='soft'"> + <xsl:attribute name="LEVEL">-50%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='volume' and $a[1]='x-soft'"> + <xsl:attribute name="LEVEL">-70%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='volume' and $a[1]='silent'"> + <xsl:attribute name="LEVEL">-100%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='volume'"> + <xsl:attribute name="LEVEL"><xsl:value-of select=" $a[1]"/></xsl:attribute> + </xsl:when> + + <!-- range: + The volume at which the text is spoken. + Values such as x-high, high, medium, low, x-low, etc. and percentages (+ or -) + are supported. --> + <xsl:when test="name($a[1])='range' and $a[1]='x-high'"> + <xsl:attribute name="RANGE">70%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='range' and $a[1]='high'"> + <xsl:attribute name="RANGE">40%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='range' and $a[1]='medium'"> + <xsl:attribute name="RANGE">0%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='range' and $a[1]='low'"> + <xsl:attribute name="RANGE">-40%</xsl:attribute> + </xsl:when> + <xsl:when test="name($a[1])='range' and $a[1]='x-low'"> + <xsl:attribute name="RANGE">-70%</xsl:attribute> + </xsl:when> + <!-- If none of the above match, take the users selected value. --> + <xsl:when test="name($a[1])='range'"> + <xsl:attribute name="RANGE"><xsl:value-of select=" $a[1]"/></xsl:attribute> + </xsl:when> + + </xsl:choose> + + <!-- Recursively call ourself. --> + <xsl:choose> + <xsl:when test="$a[2]"> + <xsl:call-template name="prosody"><xsl:with-param name="a" select="$a[position()>1]" /></xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:element> +</xsl:template> + +</xsl:stylesheet> + diff --git a/kttsd/plugins/festivalint/configure.in.bot b/kttsd/plugins/festivalint/configure.in.bot new file mode 100644 index 0000000..fee52df --- /dev/null +++ b/kttsd/plugins/festivalint/configure.in.bot @@ -0,0 +1,16 @@ +if test "x$festival_bindir" = "xno"; then + if test "$compile_festivalint_plugin" = "yes"; then + echo "" + echo "======================================================" + echo "The festival program does not appear to be installed" + echo "on this system. The Festival Interactive plugin will" + echo "be built, but you need to install festival before you" + echo "can use it. Festival comes on most Linux distribution" + echo "CDs, or you can get it at" + echo " http://www.cstr.ed.ac.uk/projects/festival/" + echo "Debian users: apt-get install festival" + echo "=====================================================" + all_tests=bad + fi +fi + diff --git a/kttsd/plugins/festivalint/configure.in.in b/kttsd/plugins/festivalint/configure.in.in new file mode 100644 index 0000000..c22f422 --- /dev/null +++ b/kttsd/plugins/festivalint/configure.in.in @@ -0,0 +1,22 @@ +dnl ============================== +dnl checks for FestivalInt Plug In +dnl ============================== + +AC_ARG_ENABLE(kttsd-festivalint, + AC_HELP_STRING([--enable-kttsd-festivalint], + [build KTTSD Festival Interactive plugin [default=yes]]), + festivalint_plugin=$enableval, + festivalint_plugin=yes) + +compile_festivalint_plugin="yes" + +if test "x$festivalint_plugin" = "xno"; then + compile_festivalint_plugin="no" +fi + +dnl Check for festival executable. +dnl Note that Festival Interactive plugin is always built +dnl whether binary is found or not, unless user overrides with -disable-festivalint. +AC_PATH_PROG(festival_bindir, "festival", "no") + +AM_CONDITIONAL(include_kttsd_festivalint, test "x$compile_festivalint_plugin" = "xyes") diff --git a/kttsd/plugins/festivalint/festivalintconf.cpp b/kttsd/plugins/festivalint/festivalintconf.cpp new file mode 100644 index 0000000..91cd24f --- /dev/null +++ b/kttsd/plugins/festivalint/festivalintconf.cpp @@ -0,0 +1,730 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration widget and functions for Festival (Interactive) plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// C++ includes. +#include <math.h> + +// Qt includes. +#include <qlayout.h> +#include <qlabel.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qcheckbox.h> +#include <qdir.h> +#include <qslider.h> +#include <qdom.h> +#include <qtextcodec.h> + +// KDE includes. +#include <kdialog.h> +#include <kdebug.h> +#include <klocale.h> +#include <kcombobox.h> +#include <kglobal.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <knuminput.h> +#include <kprocio.h> +#include <kprogress.h> +#include <kiconloader.h> + +// KTTS includes. +#include "testplayer.h" + +// FestivalInt includes. +#include "festivalintproc.h" +#include "festivalintconf.h" +#include "festivalintconf.moc" + +/** Constructor */ +FestivalIntConf::FestivalIntConf( QWidget* parent, const char* name, const QStringList& /*args*/) : + PlugInConf(parent, name) +{ + // kdDebug() << "FestivalIntConf::FestivalIntConf: Running" << endl; + m_festProc = 0; + m_progressDlg = 0; + m_supportsSSML = FestivalIntProc::ssUnknown; + + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "FestivalIntConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new FestivalIntConfWidget(this, "FestivalIntConfigWidget"); + layout->addWidget(m_widget); + + m_widget->festivalPath->setMode(KFile::File | KFile::ExistingOnly); + m_widget->festivalPath->setFilter("*"); + + // Build codec list and fill combobox. + m_codecList = PlugInProc::buildCodecList(); + m_widget->characterCodingBox->clear(); + m_widget->characterCodingBox->insertStringList(m_codecList); + + // defaults(); + + connect(m_widget->festivalPath, SIGNAL(textChanged(const QString&)), + this, SLOT(slotFestivalPath_textChanged())); + connect(m_widget->selectVoiceCombo, SIGNAL(activated(const QString&)), + this, SLOT(slotSelectVoiceCombo_activated())); + connect(m_widget->selectVoiceCombo, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->testButton, SIGNAL(clicked()), this, SLOT(slotTest_clicked())); + connect(m_widget->rescan, SIGNAL(clicked()), this, SLOT(scanVoices())); + connect(m_widget->volumeBox, SIGNAL(valueChanged(int)), + this, SLOT(volumeBox_valueChanged(int))); + connect(m_widget->timeBox, SIGNAL(valueChanged(int)), + this, SLOT(timeBox_valueChanged(int))); + connect(m_widget->frequencyBox, SIGNAL(valueChanged(int)), + this, SLOT(frequencyBox_valueChanged(int))); + connect(m_widget->volumeSlider, SIGNAL(valueChanged(int)), + this, SLOT(volumeSlider_valueChanged(int))); + connect(m_widget->timeSlider, SIGNAL(valueChanged(int)), + this, SLOT(timeSlider_valueChanged(int))); + connect(m_widget->frequencySlider, SIGNAL(valueChanged(int)), + this, SLOT(frequencySlider_valueChanged(int))); + connect(m_widget->volumeBox, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->volumeSlider, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->timeBox, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->timeSlider, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->frequencyBox, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->frequencySlider, SIGNAL(valueChanged(int)), this, SLOT(configChanged())); + connect(m_widget->preloadCheckBox, SIGNAL(clicked()), this, SLOT(configChanged())); + connect(m_widget->characterCodingBox, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->characterCodingBox, SIGNAL(activated(const QString&)), + this, SLOT(configChanged())); +} + +/** Destructor */ +FestivalIntConf::~FestivalIntConf(){ + // kdDebug() << "FestivalIntConf::~FestivalIntConf: Running" << endl; + if (!m_waveFile.isNull()) QFile::remove(m_waveFile); + delete m_festProc; + delete m_progressDlg; +} + +/** +* Given a voice code, returns index into m_voiceList array (and voiceCombo box). +* -1 if not found. +*/ +int FestivalIntConf::voiceCodeToListIndex(const QString& voiceCode) const +{ + const int voiceListCount = m_voiceList.count(); + for(int index = 0; index < voiceListCount; ++index){ + // kdDebug() << "Testing: " << voiceCode << " == " << m_voiceList[index].code << endl; + if(voiceCode == m_voiceList[index].code) + return index; + } + return -1; +} + +void FestivalIntConf::load(KConfig *config, const QString &configGroup){ + //kdDebug() << "FestivalIntConf::load: Running" << endl; + config->setGroup("FestivalInt"); + QString exePath = config->readEntry("FestivalExecutablePath", "festival"); + QString exeLocation = getLocation(exePath); + if (!exeLocation.isEmpty()) exePath = exeLocation; + exePath = realFilePath(exePath); + config->setGroup(configGroup); + m_widget->festivalPath->setURL(config->readEntry("FestivalExecutablePath", exePath)); + m_widget->preloadCheckBox->setChecked(false); + scanVoices(); + QString voiceSelected(config->readEntry("Voice")); + int index = voiceCodeToListIndex(voiceSelected); + if (index >= 0) + { + m_widget->selectVoiceCombo->setCurrentItem(index); + m_widget->preloadCheckBox->setChecked(m_voiceList[index].preload); + } + m_widget->volumeBox->setValue(config->readNumEntry("volume", 100)); + m_widget->timeBox->setValue(config->readNumEntry("time", 100)); + m_widget->frequencyBox->setValue(config->readNumEntry("pitch", 100)); + m_widget->preloadCheckBox->setChecked(config->readBoolEntry( + "Preload", m_widget->preloadCheckBox->isChecked())); + m_languageCode = config->readEntry("LanguageCode", m_languageCode); + m_supportsSSML = static_cast<FestivalIntProc::SupportsSSML>( + config->readNumEntry("SupportsSSML", FestivalIntProc::ssUnknown)); + QString codecName = PlugInProc::codecIndexToCodecName( + m_widget->characterCodingBox->currentItem(), m_codecList); + codecName = config->readEntry("Codec", codecName); + int codecNdx = PlugInProc::codecNameToListIndex(codecName, m_codecList); + m_widget->characterCodingBox->setCurrentItem(codecNdx); +} + +void FestivalIntConf::save(KConfig *config, const QString &configGroup){ + // kdDebug() << "FestivalIntConf::save: Running" << endl; + config->setGroup("FestivalInt"); + config->writeEntry("FestivalExecutablePath", realFilePath(m_widget->festivalPath->url())); + config->setGroup(configGroup); + config->writeEntry("FestivalExecutablePath", realFilePath(m_widget->festivalPath->url())); + config->writeEntry("Voice", m_voiceList[m_widget->selectVoiceCombo->currentItem()].code); + config->writeEntry("volume", m_widget->volumeBox->value()); + config->writeEntry("time", m_widget->timeBox->value()); + config->writeEntry("pitch", m_widget->frequencyBox->value()); + config->writeEntry("Preload", m_widget->preloadCheckBox->isChecked()); + config->writeEntry("LanguageCode", m_voiceList[m_widget->selectVoiceCombo->currentItem()].languageCode); + config->writeEntry("SupportsSSML", m_supportsSSML); + int codec = m_widget->characterCodingBox->currentItem(); + config->writeEntry("Codec", PlugInProc::codecIndexToCodecName(codec, m_codecList)); +} + +void FestivalIntConf::defaults(){ + // kdDebug() << "FestivalIntConf::defaults: Running" << endl; + m_widget->festivalPath->setURL("festival"); + m_widget->timeBox->setValue(100); + timeBox_valueChanged(100); + m_widget->volumeBox->setValue(100); + volumeBox_valueChanged(100); + m_widget->frequencyBox->setValue(100); + frequencyBox_valueChanged(100); + m_widget->preloadCheckBox->setChecked(false); + m_widget->characterCodingBox->setCurrentItem( + PlugInProc::codecNameToListIndex("ISO 8859-1", m_codecList)); + scanVoices(); +} + +void FestivalIntConf::setDesiredLanguage(const QString &lang) +{ + // kdDebug() << "FestivalIntConf::setDesiredLanguage: Running" << endl; + m_languageCode = splitLanguageCode(lang, m_countryCode); +} + +QString FestivalIntConf::getTalkerCode() +{ + if (!m_widget->selectVoiceCombo->isEnabled()) return QString::null; + QString exePath = realFilePath(m_widget->festivalPath->url()); + if (exePath.isEmpty()) return QString::null; + if (getLocation(exePath).isEmpty()) return QString::null; + if (m_voiceList.count() == 0) return QString::null; + QString normalTalkerCode; + voiceStruct voiceTemp = m_voiceList[m_widget->selectVoiceCombo->currentItem()]; + // Determine volume attribute. soft < 75% <= medium <= 125% < loud. + QString volume = "medium"; + if (m_widget->volumeBox->value() < 75) volume = "soft"; + if (m_widget->volumeBox->value() > 125) volume = "loud"; + // Determine rate attribute. slow < 75% <= medium <= 125% < fast. + QString rate = "medium"; + if (m_widget->timeBox->value() < 75) rate = "slow"; + if (m_widget->timeBox->value() > 125) rate = "fast"; + normalTalkerCode = QString( + "<voice lang=\"%1\" name=\"%2\" gender=\"%3\" />" + "<prosody volume=\"%4\" rate=\"%5\" />" + "<kttsd synthesizer=\"%6\" />") + .arg(voiceTemp.languageCode) + .arg(voiceTemp.code) + .arg(voiceTemp.gender) + .arg(volume) + .arg(rate) + .arg("Festival Interactive"); + return normalTalkerCode; +} + +/** + * Chooses a default voice given scanned list of voices in m_voiceList and current + * language and country code, and updates controls. + * @param currentVoiceIndex This voice is preferred if it matches. + */ +void FestivalIntConf::setDefaultVoice(int currentVoiceIndex) +{ + // kdDebug() << "FestivalIntCont::setDefaultVoice: Running" << endl; + // If language code is known, auto pick first voice that matches the language code. + if (!m_languageCode.isEmpty()) + { + bool found = false; + // First search for a match on both language code and country code. + QString languageCode = m_languageCode; + if (!m_countryCode.isNull()) languageCode += "_" + m_countryCode; + // kdDebug() << "FestivalIntConf::setDefaultVoice:: looking for default voice to match language code " << languageCode << endl; + uint index = 0; + // Prefer existing voice if it matches. + if (currentVoiceIndex >= 0) + { + QString vlCode = m_voiceList[currentVoiceIndex].languageCode.left(languageCode.length()); + if (languageCode == vlCode) + { + found = true; + index = currentVoiceIndex; + } + } + if (!found) + { + for(index = 0 ; index < m_voiceList.count(); ++index) + { + QString vlCode = m_voiceList[index].languageCode.left(languageCode.length()); + // kdDebug() << "FestivalIntConf::setDefaultVoice: testing " << vlCode << endl; + if(languageCode == vlCode) + { + found = true; + break; + } + } + } + // If not found, search for a match on just the language code. + if (!found) + { + languageCode = m_languageCode; + // Prefer existing voice if it matches. + if (currentVoiceIndex >= 0) + { + QString vlCode = m_voiceList[currentVoiceIndex].languageCode.left(languageCode.length()); + if (languageCode == vlCode) + { + found = true; + index = currentVoiceIndex; + } + } + if (!found) + { + for(index = 0 ; index < m_voiceList.count(); ++index) + { + QString vlCode = m_voiceList[index].languageCode.left(languageCode.length()); + // kdDebug() << "FestivalIntConf::setDefaultVoice: testing " << vlCode << endl; + if(languageCode == vlCode) + { + found = true; + break; + } + } + } + } + // If not found, pick first voice that is not "Unknown". + if (!found) + { + for(index = 0 ; index < m_voiceList.count(); ++index) + { + if (m_voiceList[index].name != i18n("Unknown")) + { + found = true; + break; + } + } + } + if (found) + { + // kdDebug() << "FestivalIntConf::setDefaultVoice: auto picking voice code " << m_voiceList[index].code << endl; + m_widget->selectVoiceCombo->setCurrentItem(index); + m_widget->preloadCheckBox->setChecked(m_voiceList[index].preload); + QString codecName = m_voiceList[index].codecName; + int codecNdx = PlugInProc::codecNameToListIndex(codecName, m_codecList); + m_widget->characterCodingBox->setCurrentItem(codecNdx); + if (m_voiceList[index].volumeAdjustable) + { + m_widget->volumeBox->setEnabled(true); + m_widget->volumeSlider->setEnabled(true); + } + else + { + m_widget->volumeBox->setValue(100); + volumeBox_valueChanged(100); + m_widget->volumeBox->setEnabled(false); + m_widget->volumeSlider->setEnabled(false); + } + if (m_voiceList[index].rateAdjustable) + { + m_widget->timeBox->setEnabled(true); + m_widget->timeSlider->setEnabled(true); + } + else + { + m_widget->timeBox->setValue(100); + timeBox_valueChanged(100); + m_widget->timeBox->setEnabled(false); + m_widget->timeSlider->setEnabled(false); + } + if (m_voiceList[index].pitchAdjustable) + { + m_widget->frequencyBox->setEnabled(true); + m_widget->frequencySlider->setEnabled(true); + } + else + { + m_widget->frequencyBox->setValue(100); + frequencyBox_valueChanged(100); + m_widget->frequencyBox->setEnabled(false); + m_widget->frequencySlider->setEnabled(false); + } + if ((int)index != currentVoiceIndex) configChanged(); + } + } +} + +/** + * Given an XML node and child element name, returns the string value from the child element. + * If no such child element, returns def. + */ +QString FestivalIntConf::readXmlString(QDomNode &node, const QString &elementName, const QString &def) +{ + QDomNode childNode = node.namedItem(elementName); + if (!childNode.isNull()) + return childNode.toElement().text(); + else + return def; +} + +/** + * Given an XML node and child element name, returns the boolean value from the child element. + * If no such child element, returns def. + */ +bool FestivalIntConf::readXmlBool(QDomNode &node, const QString &elementName, bool def) +{ + QDomNode childNode = node.namedItem(elementName); + if (!childNode.isNull()) + return (childNode.toElement().text() == "true"); + else + return def; +} + +void FestivalIntConf::scanVoices() +{ + // kdDebug() << "FestivalIntConf::scanVoices: Running" << endl; + // Get existing voice code (if any). + QString currentVoiceCode; + int index = m_widget->selectVoiceCombo->currentItem(); + if (index < (int)m_voiceList.count()) currentVoiceCode = m_voiceList[index].code; + + m_voiceList.clear(); + m_widget->selectVoiceCombo->clear(); + m_widget->selectVoiceCombo->insertItem(i18n("Scanning... Please wait.")); + + // Save current state of selectVoiceCombo box and disable. + bool selectVoiceComboEnabled = m_widget->selectVoiceCombo->isEnabled(); + m_widget->selectVoiceCombo->setEnabled(false); + + // Clear existing list of supported voice codes. + // m_supportedVoiceCodes.clear(); + m_widget->selectVoiceCombo->clear(); + + QString exePath = realFilePath(m_widget->festivalPath->url()); + if (!getLocation(exePath).isEmpty()) + { + // Set up a progress dialog. + m_progressDlg = new KProgressDialog(m_widget, "kttsmgr_queryvoices", + i18n("Query Voices"), + i18n("Querying Festival for available voices. This could take up to 15 seconds."), + true); + m_progressDlg->progressBar()->hide(); + m_progressDlg->setAllowCancel(true); + + // Create Festival process and request a list of voice codes. + if (m_festProc) + m_festProc->stopText(); + else + { + m_festProc = new FestivalIntProc(); + connect (m_festProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + connect (m_festProc, SIGNAL(queryVoicesFinished(const QStringList&)), + this, SLOT(slotQueryVoicesFinished(const QStringList&))); + m_festProc->queryVoices(exePath); + + // Display progress dialog modally. + m_progressDlg->exec(); + // kdDebug() << "FestivalIntConf::scanVoices: back from progressDlg->exec()" << endl; + + // Processing continues until either user clicks Cancel button, or until + // Festival responds with the list. When Festival responds with list, + // the progress dialog is closed. + + disconnect (m_festProc, SIGNAL(queryVoicesFinished(const QStringList&)), + this, SLOT(slotQueryVoicesFinished(const QStringList&))); + if (!m_progressDlg->wasCancelled()) m_festProc->stopText(); + delete m_progressDlg; + m_progressDlg = 0; + m_supportsSSML = m_festProc->supportsSSML(); + } + + if (!m_supportedVoiceCodes.isEmpty()) + { + // User's desktop language setting. + QString desktopLanguageCode = KGlobal::locale()->language(); + QString twoAlpha; + QString countryCode; + QString charSet; + KGlobal::locale()->splitLocale(desktopLanguageCode, twoAlpha, countryCode, charSet); + desktopLanguageCode = twoAlpha.lower(); + + // Festival known voices list. + QString voicesFilename = KGlobal::dirs()->resourceDirs("data").last() + "/kttsd/festivalint/voices"; + QDomDocument voicesDoc("Festival Voices"); + QFile voicesFile(voicesFilename); + if (voicesFile.open(IO_ReadOnly)) voicesDoc.setContent(&voicesFile); + voicesFile.close(); + QDomNodeList voices = voicesDoc.elementsByTagName("voice"); + uint voicesCount = voices.count(); + if (voicesCount == 0) + kdDebug() << "FestivalIntConf::scanVoices: Unable to open " << voicesFilename << ". Is KDEDIR defined?" << endl; + + // Iterate thru list of voice codes returned by Festival, + // find matching entry in voices.xml file, and add to list of supported voices. + QPixmap maleIcon = KGlobal::iconLoader()->loadIcon("male", KIcon::Small); + QPixmap femaleIcon = KGlobal::iconLoader()->loadIcon("female", KIcon::Small); + QStringList::ConstIterator itEnd = m_supportedVoiceCodes.constEnd(); + for(QStringList::ConstIterator it = m_supportedVoiceCodes.begin(); it != itEnd; ++it ) + { + QString code = *it; + bool found = false; + for (uint index=0; index < voicesCount; ++index) + { + QDomNode voiceNode = voices.item(index); + QString voiceCode = readXmlString(voiceNode, "code", QString::null); + // kdDebug() << "FestivalIntConf::scanVoices: Comparing code " << code << " to " << voiceCode << endl; + if (voiceCode == code) + { + found = true; + voiceStruct voiceTemp; + voiceTemp.code = code; + voiceTemp.name = i18n("FestivalVoiceName", + readXmlString(voiceNode, "name", "Unknown").utf8()); + voiceTemp.languageCode = readXmlString(voiceNode, "language", m_languageCode); + voiceTemp.codecName = readXmlString(voiceNode, "codec", "ISO 8859-1"); + voiceTemp.gender = readXmlString(voiceNode, "gender", "neutral"); + voiceTemp.preload = readXmlBool(voiceNode, "preload", false); + voiceTemp.volumeAdjustable = readXmlBool(voiceNode, "volume-adjustable", true); + voiceTemp.rateAdjustable = readXmlBool(voiceNode, "rate-adjustable", true); + voiceTemp.pitchAdjustable = readXmlBool(voiceNode, "pitch-adjustable", true); + m_voiceList.append(voiceTemp); + QString voiceDisplayName = voiceTemp.name + " (" + voiceTemp.code + ")"; + if (voiceTemp.gender == "male") + m_widget->selectVoiceCombo->insertItem(maleIcon, voiceDisplayName); + else if (voiceTemp.gender == "female") + m_widget->selectVoiceCombo->insertItem(femaleIcon, voiceDisplayName); + else + m_widget->selectVoiceCombo->insertItem(voiceDisplayName); + break; + } + } + if (!found) + { + voiceStruct voiceTemp; + voiceTemp.code = code; + voiceTemp.name = i18n("Unknown"); + voiceTemp.languageCode = m_languageCode; + voiceTemp.codecName = "ISO 8858-1"; + voiceTemp.gender = "neutral"; + voiceTemp.preload = false; + voiceTemp.volumeAdjustable = true; + voiceTemp.rateAdjustable = true; + voiceTemp.pitchAdjustable = true; + m_voiceList.append(voiceTemp); + m_widget->selectVoiceCombo->insertItem(voiceTemp.name + " (" + voiceTemp.code + ")"); + } + } + m_widget->selectVoiceCombo->setEnabled(true); + } else kdDebug() << "FestivalIntConf::scanVoices: No voices found" << endl; + setDefaultVoice(voiceCodeToListIndex(currentVoiceCode)); + // Emit configChanged if the enabled state of the selectVoiceCombo has changed. + // This occurs when user changes Festival EXE path, then clicks Rescan. + if (selectVoiceComboEnabled != m_widget->selectVoiceCombo->isEnabled()) configChanged(); +} + +void FestivalIntConf::slotQueryVoicesFinished(const QStringList &voiceCodes) +{ + // kdDebug() << "FestivalIntConf::slotQueryVoicesFinished: voiceCodes.count() = " << voiceCodes.count() << endl; + m_supportedVoiceCodes = voiceCodes; + if (m_progressDlg) m_progressDlg->close(); +} + +void FestivalIntConf::slotTest_clicked() +{ + // kdDebug() << "FestivalIntConf::slotTest_clicked: Running " << endl; + // If currently synthesizing, stop it. + if (m_festProc) + m_festProc->stopText(); + else + { + m_festProc = new FestivalIntProc(); + connect (m_festProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + // Create a temp file name for the wave file. + KTempFile tempFile (locateLocal("tmp", "festivalintplugin-"), ".wav"); + QString tmpWaveFile = tempFile.file()->name(); + tempFile.close(); + + // Get the code for the selected voice. + QString voiceCode = m_voiceList[m_widget->selectVoiceCombo->currentItem()].code; + + // Get language code for the selected voice. + QString languageCode = m_voiceList[m_widget->selectVoiceCombo->currentItem()].languageCode; + + // Get test message in the language of the voice. + QString testMsg = testMessage(languageCode); + + // Get codec. + QTextCodec* codec = PlugInProc::codecIndexToCodec( + m_widget->characterCodingBox->currentItem(), m_codecList); + + // Tell user to wait. + m_progressDlg = new KProgressDialog(m_widget, "ktts_festivalint_testdlg", + i18n("Testing"), + i18n("Testing. MultiSyn voices require several seconds to load. Please be patient."), + true); + m_progressDlg->progressBar()->hide(); + m_progressDlg->setAllowCancel(true); + + // kdDebug() << "FestivalIntConf::slotTest_clicked: calling synth with voiceCode: " << voiceCode << " time percent: " << m_widget->timeBox->value() << endl; + connect (m_festProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + m_festProc->synth( + realFilePath(m_widget->festivalPath->url()), + testMsg, + tmpWaveFile, + voiceCode, + m_widget->timeBox->value(), + m_widget->frequencyBox->value(), + m_widget->volumeBox->value(), + languageCode, + codec); + + // Display progress dialog modally. Processing continues when plugin signals synthFinished, + // or if user clicks Cancel button. + m_progressDlg->exec(); + disconnect (m_festProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + if (m_progressDlg->wasCancelled()) m_festProc->stopText(); + delete m_progressDlg; + m_progressDlg = 0; +} + +void FestivalIntConf::slotSynthFinished() +{ + // kdDebug() << "FestivalIntConf::slotSynthFinished: Running" << endl; + // If user canceled, progress dialog is gone, so exit. + if (!m_progressDlg) + { + m_festProc->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_festProc->getFilename(); + // Tell synth we're done. + m_festProc->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 FestivalIntConf::slotSynthStopped() +{ + // Clean up after canceling test. + QString filename = m_festProc->getFilename(); + // kdDebug() << "FestivalIntConf::slotSynthStopped: filename = " << filename << endl; + if (!filename.isNull()) QFile::remove(filename); +} + +void FestivalIntConf::slotFestivalPath_textChanged() +{ + QString exePath = realFilePath(m_widget->festivalPath->url()); + m_widget->selectVoiceCombo->setEnabled(false); + if (!exePath.isEmpty() && !getLocation(exePath).isEmpty()) + { + m_widget->rescan->setEnabled(true); + } else m_widget->rescan->setEnabled(false); +} + +void FestivalIntConf::slotSelectVoiceCombo_activated() +{ + int index = m_widget->selectVoiceCombo->currentItem(); + QString codecName = m_voiceList[index].codecName; + int codecNdx = PlugInProc::codecNameToListIndex(codecName, m_codecList); + m_widget->characterCodingBox->setCurrentItem(codecNdx); + m_widget->preloadCheckBox->setChecked( + m_voiceList[index].preload); + if (m_voiceList[index].volumeAdjustable) + { + m_widget->volumeBox->setEnabled(true); + m_widget->volumeSlider->setEnabled(true); + } + else + { + m_widget->volumeBox->setValue(100); + volumeBox_valueChanged(100); + m_widget->volumeBox->setEnabled(false); + m_widget->volumeSlider->setEnabled(false); + } + if (m_voiceList[index].rateAdjustable) + { + m_widget->timeBox->setEnabled(true); + m_widget->timeSlider->setEnabled(true); + } + else + { + m_widget->timeBox->setValue(100); + timeBox_valueChanged(100); + m_widget->timeBox->setEnabled(false); + m_widget->timeSlider->setEnabled(false); + } + if (m_voiceList[index].pitchAdjustable) + { + m_widget->frequencyBox->setEnabled(true); + m_widget->frequencySlider->setEnabled(true); + } + else + { + m_widget->frequencyBox->setValue(100); + frequencyBox_valueChanged(100); + m_widget->frequencyBox->setEnabled(false); + m_widget->frequencySlider->setEnabled(false); + } +} + +// 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 FestivalIntConf::percentToSlider(int percentValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor (0.5 + alpha * (log(percentValue)-log(50))); +} + +int FestivalIntConf::sliderToPercent(int sliderValue) { + double alpha = 1000 / (log(200) - log(50)); + return (int)floor(0.5 + exp (sliderValue/alpha + log(50))); +} + +void FestivalIntConf::volumeBox_valueChanged(int percentValue) { + m_widget->volumeSlider->setValue(percentToSlider(percentValue)); +} + +void FestivalIntConf::timeBox_valueChanged(int percentValue) { + m_widget->timeSlider->setValue (percentToSlider (percentValue)); +} + +void FestivalIntConf::frequencyBox_valueChanged(int percentValue) { + m_widget->frequencySlider->setValue(percentToSlider(percentValue)); +} + +void FestivalIntConf::volumeSlider_valueChanged(int sliderValue) { + m_widget->volumeBox->setValue(sliderToPercent(sliderValue)); +} + +void FestivalIntConf::timeSlider_valueChanged(int sliderValue) { + m_widget->timeBox->setValue (sliderToPercent (sliderValue)); +} + +void FestivalIntConf::frequencySlider_valueChanged(int sliderValue) { + m_widget->frequencyBox->setValue(sliderToPercent(sliderValue)); +} diff --git a/kttsd/plugins/festivalint/festivalintconf.h b/kttsd/plugins/festivalint/festivalintconf.h new file mode 100644 index 0000000..a367682 --- /dev/null +++ b/kttsd/plugins/festivalint/festivalintconf.h @@ -0,0 +1,187 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration widget and functions for Festival (Interactive) plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FESTIVALINTCONF_H_ +#define _FESTIVALINTCONF_H_ + +// Qt includes. +#include <qstringlist.h> +#include <qvaluelist.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include "pluginconf.h" + +// FestivalInt includes. +#include "festivalintconfwidget.h" +#include "festivalintproc.h" + +class KProgressDialog; +class QDomNode; + +typedef struct voiceStruct{ + QString code; // Code as sent to Festival + QString name; // Name as displayed and returned in Talker Code. + QString languageCode; // Language code (en, es, etc) + QString codecName; // Character encoding codec name (eg. ISO 8859-1) + QString gender; // male, female, or neutral + bool preload; // Start Festival and load this language when KTTSD is started. + bool volumeAdjustable; // True if the voice supports volume adjustments. + bool rateAdjustable; // True if the voice supports rate adjustments. + bool pitchAdjustable; // True if the voice supports pitch adjustments. +} voice; + +class FestivalIntConf : public PlugInConf { + Q_OBJECT + + public: + /** Constructor */ + FestivalIntConf( QWidget* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~FestivalIntConf(); + + /** 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: + /** Scan for the different voices in festivalPath/lib */ + void scanVoices(); + void configChanged(){ + // kdDebug() << "FestivalIntConf::configChanged: Running" << endl; + emit changed(true); + }; + void slotTest_clicked(); + void slotSynthFinished(); + void slotSynthStopped(); + void volumeBox_valueChanged(int percentValue); + void timeBox_valueChanged(int percentValue); + void frequencyBox_valueChanged(int percentValue); + void volumeSlider_valueChanged(int sliderValue); + void timeSlider_valueChanged(int sliderValue); + void frequencySlider_valueChanged(int sliderValue); + void slotFestivalPath_textChanged(); + void slotSelectVoiceCombo_activated(); + void slotQueryVoicesFinished(const QStringList &voiceCodes); + + private: + int percentToSlider(int percentValue); + int sliderToPercent(int sliderValue); + + /** + * Given an XML node and child element name, returns the string value from the child element. + * If no such child element, returns def. + */ + QString readXmlString(QDomNode &node, const QString &elementName, const QString &def); + + /** + * Given an XML node and child element name, returns the boolean value from the child element. + * If no such child element, returns def. + */ + bool readXmlBool(QDomNode &node, const QString &elementName, bool def); + + /** + * Given a voice code, returns index into m_voiceList array (and voiceCombo box). + * -1 if not found. + */ + int voiceCodeToListIndex(const QString& voiceCode) const; + + /** + * Chooses a default voice given scanned list of voices in m_voiceList and current + * language and country code, and updates controls. + * @param currentVoiceIndex This voice is preferred if it matches. + */ + void setDefaultVoice(int currentVoiceIndex); + + // Configuration Widget. + FestivalIntConfWidget* m_widget; + + // Language code. + QString m_languageCode; + // Language country code (if any). + QString m_countryCode; + // List of voices */ + QValueList<voice> m_voiceList; + // Festival synthesizer. + FestivalIntProc* m_festProc; + // Synthesized wave file name. + QString m_waveFile; + // Progress dialog. + KProgressDialog* m_progressDlg; + // List of voice codes supported by Festival. + QStringList m_supportedVoiceCodes; + // List of displayed codec names. + QStringList m_codecList; + // Whether Festival supports SSML or not. + FestivalIntProc::SupportsSSML m_supportsSSML; +}; +#endif // _FESTIVALINTCONF_H_ diff --git a/kttsd/plugins/festivalint/festivalintconfwidget.ui b/kttsd/plugins/festivalint/festivalintconfwidget.ui new file mode 100644 index 0000000..f8b33fb --- /dev/null +++ b/kttsd/plugins/festivalint/festivalintconfwidget.ui @@ -0,0 +1,598 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>FestivalIntConfWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>FestivalIntConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>559</width> + <height>318</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Festival Config UI</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the dialog for configuring the Festival speech synthesizer in interactive mode.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>festivalConfigurationBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>GroupBoxPanel</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="title"> + <string>Festival &Interactive Configuration</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the dialog for configuring the Festival speech synthesizer in interactive mode.</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="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>voicesPathBox</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>festivalPathLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Festival executable:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>festivalPath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>If Festival is in your PATH environment variable, just enter "festival", otherwise specify the full path to the Festival executable program.</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>festivalPath</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="whatsThis" stdset="0"> + <string>If Festival is in your PATH environment variable, just enter "festival", otherwise specify the full path to the Festival executable program.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>selectVoiceBox</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>selectVoiceLabel</cstring> + </property> + <property name="text"> + <string>&Select voice:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>selectVoiceCombo</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Select a voice to speak text with. MultiSyn voices are high quality but are slow to load. If no voices are shown, check the Festival executable path. You must install at least one Festival voice. If you have installed a voice and still none are shown, check your Festival configuration. (See the README that comes with Festival.)</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>selectVoiceCombo</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>Select a voice to speak text with. MultiSyn voices are high quality but are slow to load. If no voices are shown, check the Festival executable path. You must install at least one Festival voice. If you have installed a voice and still none are shown, check your Festival configuration. (See the README that comes with Festival.)</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>rescan</cstring> + </property> + <property name="text"> + <string>&Rescan</string> + <comment>Rescan for voices</comment> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="2" column="0"> + <property name="name"> + <cstring>layout11</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <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:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>volumeBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Sets the volume (loudness) of speech. Slide the slider to the left to lower the volume; to the right to increase volume. Anything less than 75 percent is considered "soft", and anything greater than 125 percent is considered "loud".</string> + </property> + </widget> + <widget class="QLabel"> + <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>Sp&eed:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>timeBox</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast". You cannot change the speed of MultiSyn voices.</string> + </property> + </widget> + <widget class="QLabel"> + <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>Sets the tone (frequency) of speech. Slide the slider to the left to lower the voice tone; to the right to increase tone. Anything less than 75 percent is considered "low", and anything greater than 125 percent is considered "high". You cannot change the pitch of MultiSyn voices.</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KIntSpinBox"> + <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>Sets the volume (loudness) of speech. Slide the slider to the left to lower the volume; to the right to increase volume. Anything less than 75 percent is considered "soft", and anything greater than 125 percent is considered "loud".</string> + </property> + </widget> + <widget class="KIntSpinBox"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast". You cannot change the speed of MultiSyn voices.</string> + </property> + </widget> + <widget class="KIntSpinBox"> + <property name="name"> + <cstring>frequencyBox</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>Sets the tone (frequency) of speech. Slide the slider to the left to lower the voice tone; to the right to increase tone. Anything less than 75 percent is considered "low", and anything greater than 125 percent is considered "high". You cannot change the pitch of MultiSyn voices.</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QSlider"> + <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="whatsThis" stdset="0"> + <string>Sets the volume (loudness) of speech. Slide the slider to the left to lower the volume; to the right to increase volume. Anything less than 75 percent is considered "soft", and anything greater than 125 percent is considered "loud".</string> + </property> + </widget> + <widget class="QSlider"> + <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>Sets the speed of speech. Slide the slider to the left to slow speech down; to the right to increase talking speed. Anything less than 75 percent is considered "slow", and anything greater than 125 percent is considered "fast". You cannot change the speed of MultiSyn voices.</string> + </property> + </widget> + <widget class="QSlider"> + <property name="name"> + <cstring>frequencySlider</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>Sets the tone (frequency) of speech. Slide the slider to the left to lower the voice tone; to the right to increase tone. Anything less than 75 percent is considered "low", and anything greater than 125 percent is considered "high". You cannot change the pitch of MultiSyn voices.</string> + </property> + </widget> + </vbox> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="4" column="0"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>preloadCheckBox</cstring> + </property> + <property name="text"> + <string>&Load this voice when starting KTTSD</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If checked, Festival will be started and this voice will be loaded when the Text-to-Speech Deamon (KTTSD) is started. Check when a voice requires a long time to load in Festival (for example, multisyn voices), otherwise, leave unchecked.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer3</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>101</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>testButton</cstring> + </property> + <property name="text"> + <string>&Test</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Click to test the configuration. Festival will be started and a test sentence will be spoken.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="3" 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 e&ncoding:</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> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>festivalPath</tabstop> + <tabstop>selectVoiceCombo</tabstop> + <tabstop>rescan</tabstop> + <tabstop>volumeBox</tabstop> + <tabstop>timeBox</tabstop> + <tabstop>frequencyBox</tabstop> + <tabstop>preloadCheckBox</tabstop> + <tabstop>testButton</tabstop> +</tabstops> +<includes> + <include location="global" impldecl="in declaration">kurlrequester.h</include> + <include location="global" impldecl="in implementation">kurlrequester.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>kcombobox.h</includehint> +</includehints> +</UI> diff --git a/kttsd/plugins/festivalint/festivalintplugin.cpp b/kttsd/plugins/festivalint/festivalintplugin.cpp new file mode 100644 index 0000000..d793b40 --- /dev/null +++ b/kttsd/plugins/festivalint/festivalintplugin.cpp @@ -0,0 +1,31 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so festival (interactive) can be used as plug in. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "festivalintconf.h" +#include "festivalintproc.h" + +typedef K_TYPELIST_2( FestivalIntProc, FestivalIntConf ) Festival; +K_EXPORT_COMPONENT_FACTORY( libkttsd_festivalintplugin, KGenericFactory<Festival>("kttsd_festivalint") ) + diff --git a/kttsd/plugins/festivalint/festivalintproc.cpp b/kttsd/plugins/festivalint/festivalintproc.cpp new file mode 100644 index 0000000..77822ea --- /dev/null +++ b/kttsd/plugins/festivalint/festivalintproc.cpp @@ -0,0 +1,662 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Main speaking functions for the Festival (Interactive) Plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// C++ includes. +#include <math.h> + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> +#include <qthread.h> +#include <qtextcodec.h> + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> +#include <kstandarddirs.h> + +// KTTS includes. +#include "utils.h" + +// FestivalInt includes. +#include "festivalintproc.h" +#include "festivalintproc.moc" + +/** Constructor */ +FestivalIntProc::FestivalIntProc( QObject* parent, const char* name, const QStringList& ) : + PlugInProc( parent, name ){ + // kdDebug() << "FestivalIntProc::FestivalIntProc: Running" << endl; + m_ready = true; + m_writingStdin = false; + m_waitingQueryVoices = false; + m_waitingStop = false; + m_festProc = 0; + m_state = psIdle; + m_supportsSSML = ssUnknown; + m_languageCode = "en"; + m_codec = QTextCodec::codecForName("ISO8859-1"); +} + +/** Destructor */ +FestivalIntProc::~FestivalIntProc(){ + // kdDebug() << "FestivalIntProc::~FestivalIntProc: Running" << endl; + if (m_festProc) + { + if (m_festProc->isRunning()) + { + if (m_ready) + { + m_state = psIdle; + // kdDebug() << "FestivalIntProc::~FestivalIntProc: telling Festival to quit." << endl; + m_ready = false; + m_waitingStop = true; + m_festProc->writeStdin("(quit)", true); + } + else + { + // kdDebug() << "FestivalIntProc::~FestivalIntProc: killing Festival." << endl; + m_waitingStop = true; + m_festProc->kill(); + } + } + delete m_festProc; + } +} + +/** Initialize the speech */ +bool FestivalIntProc::init(KConfig *config, const QString &configGroup) +{ + // kdDebug() << "FestivalIntProc::init: Initializing plug in: Festival" << endl; + + config->setGroup(configGroup); + m_voiceCode = config->readEntry("Voice"); + m_festivalExePath = config->readEntry("FestivalExecutablePath", "festival"); + // kdDebug() << "---- The code for the selected voice " << config->readEntry("Voice") << " is " << voiceCode << endl; + m_time = config->readNumEntry("time", 100); + m_pitch = config->readNumEntry("pitch", 100); + m_volume = config->readNumEntry("volume", 100); + // If voice should be pre-loaded, start Festival and load the voice. + m_preload = config->readBoolEntry("Preload", false); + m_languageCode = config->readEntry("LanguageCode", "en"); + m_supportsSSML = static_cast<SupportsSSML>(config->readNumEntry("SupportsSSML", ssUnknown)); + QString codecName = config->readEntry("Codec", "Latin1"); + m_codec = codecNameToCodec(codecName); + if (m_preload) startEngine(m_festivalExePath, m_voiceCode, m_languageCode, m_codec); + 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 FestivalIntProc::sayText(const QString &text) +{ + synth(m_festivalExePath, text, QString::null, m_voiceCode, m_time, m_pitch, m_volume, + m_languageCode, m_codec); +} + +/** +* 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 FestivalIntProc::synthText(const QString& text, const QString& suggestedFilename) +{ + synth(m_festivalExePath, text, suggestedFilename, m_voiceCode, m_time, m_pitch, m_volume, + m_languageCode, m_codec); +} + +/** +* Sends command to Festival to query for a list of supported voice codes. +* Fires queryVoicesFinished when completed. +* @return False if busy doing something else and therefore cannot +* do the query. +*/ +bool FestivalIntProc::queryVoices(const QString &festivalExePath) +{ + // kdDebug() << "FestivalIntProc::queryVoices: Running" << endl; + if (m_state != psIdle && m_waitingQueryVoices && m_waitingStop) return false; + // Start Festival if not already running. + startEngine(festivalExePath, QString::null, m_languageCode, m_codec); + // Set state, waiting for voice codes list from Festival. + m_waitingQueryVoices = true; + // Voice rab_diphone is needed in order to support SSML. + m_supportsSSML = ssUnknown; + // Send command to query the voice codes. + sendToFestival("(print (mapcar (lambda (pair) (car pair)) voice-locations))"); + return true; +} + +/** +* Start Festival engine. +* @param festivalExePath Path to the Festival executable, or just "festival". +* @param voiceCode Voice code in which to speak text. +* @param languageCode Language code, for example, "en". +*/ +void FestivalIntProc::startEngine(const QString &festivalExePath, const QString &voiceCode, + const QString &languageCode, QTextCodec* codec) +{ + // Initialize Festival only if it's not initialized. + if (m_festProc) + { + // Stop Festival if a different EXE is requested or different language code. + // If festProc exists but is not running, it is because it was stopped. + if ((festivalExePath != m_festivalExePath) || !m_festProc->isRunning() || + (m_languageCode != languageCode) || (codec->name() != m_codec->name())) + { + delete m_festProc; + m_festProc = 0; + } + } + if(!m_festProc) + { + // kdDebug()<< "FestivalIntProc::startEngine: Creating Festival object" << endl; + m_festProc = new KProcess; + *m_festProc << festivalExePath; + *m_festProc << "--interactive"; + m_festProc->setEnvironment("LANG", languageCode + "." + codec->mimeName()); + m_festProc->setEnvironment("LC_CTYPE", languageCode + "." + codec->mimeName()); + // kdDebug() << "FestivalIntProc::startEngine: setting LANG = LC_CTYPE = " << languageCode << "." << codec->mimeName() << endl; + connect(m_festProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + connect(m_festProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_festProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + connect(m_festProc, SIGNAL(wroteStdin(KProcess*)), + this, SLOT(slotWroteStdin(KProcess*))); + } + if (!m_festProc->isRunning()) + { + // kdDebug() << "FestivalIntProc::startEngine: Starting Festival process" << endl; + m_runningVoiceCode = QString::null; + m_runningTime = 100; + m_runningPitch = 100; + m_ready = false; + m_outputQueue.clear(); + if (m_festProc->start(KProcess::NotifyOnExit, KProcess::All)) + { + // kdDebug()<< "FestivalIntProc:startEngine: Festival initialized" << endl; + m_festivalExePath = festivalExePath; + m_languageCode = languageCode; + m_codec = codec; + // Load the SABLE to Wave module. + sendToFestival("(load \"" + + KGlobal::dirs()->resourceDirs("data").last() + "kttsd/festivalint/sabletowave.scm\")"); + } + else + { + kdDebug() << "FestivalIntProc::startEngine: Error starting Festival process. Is festival in the PATH?" << endl; + m_ready = true; + m_state = psIdle; + return; + } + } + // If we just started Festival, or voiceCode has changed, send code to Festival. + if (m_runningVoiceCode != voiceCode && !voiceCode.isEmpty()) { + sendToFestival("(voice_" + voiceCode + ")"); + m_runningVoiceCode = voiceCode; + } +} + +/** +* Say or Synthesize text. +* @param festivalExePath Path to the Festival executable, or just "festival". +* @param text The text to be synthesized. +* @param suggestedFilename If not Null, synthesize only to this filename, otherwise +* synthesize and audibilize the text. +* @param voiceCode Voice code in which to speak text. +* @param time Speed percentage. 50 to 200. 200% = 2x normal. +* @param pitch Pitch persentage. 50 to 200. +* @param volume Volume percentage. 50 to 200. +* @param languageCode Language code, for example, "en". +*/ +void FestivalIntProc::synth( + const QString &festivalExePath, + const QString &text, + const QString &synthFilename, + const QString &voiceCode, + int time, + int pitch, + int volume, + const QString &languageCode, + QTextCodec* codec) +{ + // kdDebug() << "FestivalIntProc::synth: festivalExePath = " << festivalExePath + // << " voiceCode = " << voiceCode << endl; + + // Initialize Festival only if it's not initialized + startEngine(festivalExePath, voiceCode, languageCode, codec); + // If we just started Festival, or rate changed, tell festival. + if (m_runningTime != time) { + QString timeMsg; + if (voiceCode.contains("_hts") > 0) + { + // Map 50% to 200% onto 0 to 1000. + // slider = alpha * (log(percent)-log(50)) + // with alpha = 1000/(log(200)-log(50)) + double alpha = 1000 / (log(200) - log(50)); + int slider = (int)floor (0.5 + alpha * (log(time)-log(50))); + // Center at 0. + slider = slider - 500; + // Map -500 to 500 onto 0.15 to -0.15. + float stretchValue = -float(slider) * 0.15 / 500.0; + timeMsg = QString("(set! hts_duration_stretch %1)").arg( + stretchValue, 0, 'f', 3); + } + else + timeMsg = QString("(Parameter.set 'Duration_Stretch %1)").arg( + 1.0/(float(time)/100.0), 0, 'f', 2); + sendToFestival(timeMsg); + m_runningTime = time; + } + // If we just started Festival, or pitch changed, tell festival. + if (m_runningPitch != pitch) { + // Pitch values range from 50 to 200 %, with 100% as the midpoint, + // while frequency values range from 41 to 500 with 105 as the "midpoint". + int pitchValue; + if (pitch <= 100) + { + pitchValue = (((pitch - 50) * 64) / 50) + 41; + } + else + { + pitchValue = (((pitch - 100) * 395) / 100) + 105; + } + QString pitchMsg = QString( + "(set! int_lr_params '((target_f0_mean %1) (target_f0_std 14)" + "(model_f0_mean 170) (model_f0_std 34)))").arg(pitchValue, 0, 10); + sendToFestival(pitchMsg); + m_runningPitch = pitch; + } + + QString saidText = text; + + // Split really long sentences into shorter sentences, by looking for commas and converting + // to periods. + int len = saidText.length(); + while (len > c_tooLong) + { + len = saidText.findRev(", ", len - (c_tooLong * 2 / 3), true); + if (len != -1) + { + QString c = saidText.mid(len+2, 1); + if (c != c.upper()) + { + saidText.replace(len, 2, ". "); + saidText.replace(len+2, 1, c.upper()); + kdDebug() << "FestivalIntProc::synth: Splitting long sentence at " << len << endl; + // kdDebug() << saidText << endl; + } + } + } + + // Encode quotation characters. + saidText.replace("\\\"", "#!#!"); + saidText.replace("\"", "\\\""); + saidText.replace("#!#!", "\\\""); + // Remove certain comment characters. + saidText.replace("--", ""); + + // Ok, let's rock. + if (synthFilename.isNull()) + { + m_state = psSaying; + m_synthFilename = QString::null; + // kdDebug() << "FestivalIntProc::synth: Saying text: '" << saidText << "' using Festival plug in with voice " + // << voiceCode << endl; + saidText = "(SayText \"" + saidText + "\")"; + sendToFestival(saidText); + } else { + m_state = psSynthing; + m_synthFilename = synthFilename; + // Volume must be given for each utterance. + // Volume values range from 50 to 200%, with 100% = normal. + // Map onto rescale range of .5 to 2. + float volumeValue = float(volume) / 100; + // Expand to range .25 to 4. + // float volumeValue = exp(log(volumeValue) * 2); + // kdDebug() << "FestivalIntProc::synth: Synthing text: '" << saidText << "' using Festival plug in with voice " + // << voiceCode << endl; + if (isSable(saidText)) + { + // Synth the text and adjust volume. + saidText = + "(ktts_sabletowave \"" + saidText + "\" \"" + + synthFilename + "\" " + + QString::number(volumeValue) + ")"; + } + else + { + saidText = + // Suppress pause at the beginning of each utterance. + "(define (insert_initial_pause utt) " + "(item.set_feat (utt.relation.first utt 'Segment) 'end 0.0))" + // Synth the text and adjust volume. + "(set! utt1 (Utterance Text \"" + saidText + + "\"))(utt.synth utt1)" + + "(utt.wave.rescale utt1 " + QString::number(volumeValue) + " t)" + + "(utt.save.wave utt1 \"" + synthFilename + "\")"; + } + sendToFestival(saidText); + } +} + +/** +* If ready for more output, sends the given text to Festival process, otherwise, +* puts it in the queue. +* @param text Text to send or queue. +*/ +void FestivalIntProc::sendToFestival(const QString& text) +{ + if (text.isNull()) return; + m_outputQueue.append(text); + sendIfReady(); +} + +/** +* If Festival is ready for more input and there is more output to send, send it. +* To be ready for more input, the Stdin buffer must be empty and the "festival>" +* prompt must have been received (m_ready = true). +* @return False when Festival is ready for more input +* but there is nothing to be sent, or if Festival +* has exited. +*/ +bool FestivalIntProc::sendIfReady() +{ + if (!m_ready) return true; + if (m_writingStdin) return true; + if (m_outputQueue.isEmpty()) return false; + if (!m_festProc->isRunning()) return false; + QString text = m_outputQueue[0]; + text += "\n"; + QCString encodedText; + if (m_codec) + encodedText = m_codec->fromUnicode(text); + else + encodedText = text.latin1(); // Should not happen, but just in case. + m_outputQueue.pop_front(); + m_ready = false; + // kdDebug() << "FestivalIntProc::sendIfReady: sending to Festival: " << text << endl; + m_writingStdin = true; + m_festProc->writeStdin(encodedText, encodedText.length()); + return true; +} + +/** +* Determine if the text has SABLE tags. If so, we will have to use a different +* synthesis method. +*/ +bool FestivalIntProc::isSable(const QString &text) +{ + return KttsUtils::hasRootElement( text, "SABLE" ); +} + +/** +* 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 FestivalIntProc::getFilename() { return m_synthFilename; } + +/** + * Stop text + */ +void FestivalIntProc::stopText(){ + // kdDebug() << "FestivalIntProc::stopText: Running" << endl; + if (m_festProc) + { + if (m_festProc->isRunning()) + { + if (m_ready) + m_state = psIdle; + else + { + // If using a preloaded voice, killing Festival is a bad idea because of + // huge startup times. So if synthing (not saying), let Festival continue + // synthing. When it completes, we will emit the stopped signal. + if (m_preload && (m_state == psSynthing)) + { + m_waitingStop = true; + // kdDebug() << "FestivalIntProc::stopText: Optimizing stopText() for preloaded voice." << endl; + } + else + { + // kdDebug() << "FestivalIntProc::stopText: killing Festival." << endl; + m_waitingStop = true; + m_festProc->kill(); + } + } + } else m_state = psIdle; + } else m_state = psIdle; +} + +void FestivalIntProc::slotProcessExited(KProcess*) +{ + // kdDebug() << "FestivalIntProc:slotProcessExited: Festival process has exited." << endl; + m_ready = true; + pluginState prevState = m_state; + if (m_waitingStop || m_waitingQueryVoices) + { + if (m_waitingStop) + { + m_waitingStop = false; + m_state = psIdle; + // kdDebug() << "FestivalIntProc::slotProcessExited: emitting stopped signal" << endl; + emit stopped(); + } + if (m_waitingQueryVoices) + { + // kdDebug() << "FestivalIntProc::slotProcessExited: canceling queryVoices operation" << endl; + m_waitingQueryVoices = false; + m_state = psIdle; + } + } else { + if (m_state != psIdle) m_state = psFinished; + if (prevState == psSaying) + { + // kdDebug() << "FestivalIntProc::slotProcessExited: emitting sayFinished signal" << endl; + emit sayFinished(); + } else + if (prevState == psSynthing) + { + // kdDebug() << "FestivalIntProc::slotProcessExited: emitting synthFinished signal" << endl; + emit synthFinished(); + } + } + delete m_festProc; + m_festProc = 0; + m_outputQueue.clear(); +} + +void FestivalIntProc::slotReceivedStdout(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + // kdDebug() << "FestivalIntProc::slotReceivedStdout: Received from Festival: " << buf << endl; + bool promptSeen = (buf.contains("festival>") > 0); + bool emitQueryVoicesFinished = false; + QStringList voiceCodesList; + if (m_waitingQueryVoices && m_outputQueue.isEmpty()) + { + // Look for opening ( and closing ). + buf.simplifyWhiteSpace(); + if (buf.left(3) == "nil") { + emitQueryVoicesFinished = true; + m_waitingQueryVoices = false; + } else { + if (buf.left(1) == "(") + { + int rightParen = buf.find(')'); + if (rightParen > 0) + { + m_waitingQueryVoices = false; + // Extract contents between parens. + buf = buf.mid(1, rightParen - 1); + // Space separated list. + voiceCodesList = QStringList::split(" ", buf, false); + emitQueryVoicesFinished = true; + } + } + } + } + if (promptSeen) + { + // kdDebug() << "FestivalIntProc::slotReceivedStdout: Prompt seen" << endl; + m_ready = true; + if (!sendIfReady()) + { + // kdDebug() << "FestivalIntProc::slotReceivedStdout: All output sent. " << endl; + pluginState prevState = m_state; + if (m_state != psIdle) m_state = psFinished; + if (prevState == psSaying) + { + // kdDebug() << "FestivalIntProc::slotReceivedStdout: emitting sayFinished signal" << endl; + emit sayFinished(); + } else + if (prevState == psSynthing) + { + if (m_waitingStop) + { + m_waitingStop = false; + m_state = psIdle; + // kdDebug() << "FestivalIntProc::slotReceivedStdout: emitting optimized stopped signal" << endl; + emit stopped(); + } + else + { + // kdDebug() << "FestivalIntProc::slotReceivedStdout: emitting synthFinished signal" << endl; + emit synthFinished(); + } + } + } + } + if (emitQueryVoicesFinished) + { + // kdDebug() << "FestivalIntProc::slotReceivedStdout: emitting queryVoicesFinished" << endl; + m_supportsSSML = (voiceCodesList.contains("rab_diphone")) ? ssYes : ssNo; + emit queryVoicesFinished(voiceCodesList); + } +} + +void FestivalIntProc::slotReceivedStderr(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "FestivalIntProc::slotReceivedStderr: Received error from Festival: " << buf << endl; +} + +void FestivalIntProc::slotWroteStdin(KProcess* /*proc*/) +{ + // kdDebug() << "FestivalIntProc::slotWroteStdin: Running" << endl; + m_writingStdin = false; + if (!sendIfReady()) + { + // kdDebug() << "FestivalIntProc::slotWroteStdin: all output sent" << endl; + pluginState prevState = m_state; + if (m_state != psIdle) m_state = psFinished; + if (prevState == psSaying) + { + // kdDebug() << "FestivalIntProc::slotWroteStdin: emitting sayFinished signal" << endl; + emit sayFinished(); + } else + if (prevState == psSynthing) + { + // kdDebug() << "FestivalIntProc::slotWroteStdin: emitting synthFinished signal" << endl; + emit synthFinished(); + } + } +} + + +bool FestivalIntProc::isReady() { return m_ready; } + +/** +* Return the current state of the plugin. +* This function only makes sense in asynchronous mode. +* @return The pluginState of the plugin. +* +* @see pluginState +*/ +pluginState FestivalIntProc::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 FestivalIntProc::ackFinished() +{ + if (m_state == psFinished) + { + m_state = psIdle; + m_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 FestivalIntProc::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 FestivalIntProc::supportsSynth() { return true; } + +/** +* 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 FestivalIntProc::getSsmlXsltFilename() +{ + if (m_supportsSSML == ssYes) + return KGlobal::dirs()->resourceDirs("data").last() + "kttsd/festivalint/xslt/SSMLtoSable.xsl"; + else + return PlugInProc::getSsmlXsltFilename(); +} + diff --git a/kttsd/plugins/festivalint/festivalintproc.h b/kttsd/plugins/festivalint/festivalintproc.h new file mode 100644 index 0000000..a69642c --- /dev/null +++ b/kttsd/plugins/festivalint/festivalintproc.h @@ -0,0 +1,361 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Main speaking functions for the Festival (Interactive) Plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FESTIVALINTPROC_H_ +#define _FESTIVALINTPROC_H_ + +#include <qstringlist.h> +#include <qmutex.h> + +#include <kprocess.h> + +#include <pluginproc.h> + +class QTextCodec; + +class FestivalIntProc : public PlugInProc{ + Q_OBJECT + + public: + enum SupportsSSML { + ssUnknown, + ssYes, + ssNo + }; + + /** + * Constructor + */ + FestivalIntProc( QObject* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** + * Destructor + */ + virtual ~FestivalIntProc(); + + /** + * Initializate the speech engine. + * @param config Settings object. + * @param configGroup Settings group. + */ + virtual bool init(KConfig *config, const QString &configGroup); + + /** + * Returns true when festival is ready to speak a sentence. + */ + bool isReady(); + + /** + * 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 + * 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(); + + /** + * Say or Synthesize text with the given voice code. + * @param festivalExePath Path to the Festival executable, or just "festival". + * @param text The text to be synthesized. + * @param suggestedFilename If not Null, synthesize only to this filename, otherwise + * synthesize and audibilize the text. + * @param voiceCode Voice code. + * @param time Speed percentage. 50 to 200. 200% = 2x normal. + * @param pitch Pitch persentage. 50 to 200. + * @param volume Volume percentage. 50 to 200. + * @param languageCode Language code, for example, "en". + */ + void synth(const QString &festivalExePath, const QString &text, + const QString &synthFilename, const QString& voiceCode, + int time, int pitch, int volume, const QString &languageCode, + QTextCodec* codec); + + /** + * Sends commands to Festival to query for a list of supported voice codes. + * Fires queryVoicesFinished when completed. + * @return False if busy doing something else and therefore cannot + * do the query. + */ + bool queryVoices(const QString &festivalExePath); + + /** + * 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 getSsmlXsltFilename(); + + /** + * Whether Festival supports SSML or not. + * 0 = Unknown + * 1 = Yes + * 2 = No + */ + SupportsSSML supportsSSML() { return m_supportsSSML; } + + signals: + /** + * This signal fires upon completion of a queryVoices operation. + * The list of voice codes do not have "voice_" prefix. + */ + void queryVoicesFinished(const QStringList &voiceCodes); + + 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: + /** + * Start Festival engine. + * @param festivalExePath Path to the Festival executable, or just "festival". + * @param voiceCode Voice code in which to speak text. + * @param languageCode Language code, for example, "en". + */ + void startEngine(const QString &festivalExePath, const QString &voiceCode, + const QString &languageCode, QTextCodec* codec); + + /** + * If ready for more output, sends the given text to Festival process, otherwise, + * puts it in the queue. + * @param text Text to send or queue. + */ + void sendToFestival(const QString& text); + + /** + * If Festival is ready for more input and there is more output to send, send it. + * To be ready for more input, the Stdin buffer must be empty and the "festival>" + * prompt must have been received (m_ready = true). + * @return False when Festival is ready for more input + * but there is nothing to be sent, or if Festival + * has exited. + */ + bool sendIfReady(); + + /** + * Determine if the text has SABLE tags. If so, we will have to use a different + * synthesis method. + */ + bool isSable(const QString &text); + + /** + * We attempt to shorten sentences longer than this by replacing commas with periods. + */ + static const int c_tooLong = 600; + + /** + * Path to the Festival executable. + */ + QString m_festivalExePath; + + /** + * Selected voice (from config). + */ + QString m_voiceCode; + + /** + * True if the voice is preloaded. Also used as a flag to supress killing + * Festival, since startup time will be excessive. + */ + bool m_preload; + + /** + * Selected speed (from config). + */ + int m_time; + + /** + * Selected pitch (frequency) (from config). + */ + int m_pitch; + + /** + * Selected volume (from config). + */ + int m_volume; + + /** + * Running voice. + */ + QString m_runningVoiceCode; + + /** + * Running time (speed). + */ + int m_runningTime; + + /** + * Running pitch (frequency). + */ + int m_runningPitch; + + /** + * Festival process + */ + KProcess* m_festProc; + + /** + * Synthesis filename. + */ + QString m_synthFilename; + + /** + * True when festival is ready for another input. + */ + volatile bool m_ready; + + /** + * Plugin state. + */ + pluginState m_state; + + /** + * True when stopText has been called. Used to force transition to psIdle when + * Festival exits. + */ + bool m_waitingStop; + + /** + * True when queryVoices has been called. + */ + bool m_waitingQueryVoices; + + /** + * A queue of outputs to be sent to the Festival process. + * Since Festival requires us to wait until the "festival>" prompt before + * sending the next command, this queue allows us to queue up multiple + * commands and send each one when the ReceivedStdOut signal fires. + */ + QStringList m_outputQueue; + + bool m_writingStdin; + + /** + * Language code. + */ + QString m_languageCode; + + /** + * Codec. + */ + QTextCodec* m_codec; + + /** + * Flag if SSML is supported. Festival cannot support SABLE (and therefore SSML) + * unless rab_diphone (British male) is installed. Gawd, I hope Festival folks fix this! + */ + SupportsSSML m_supportsSSML; +}; + +#endif // _FESTIVALINTPROC_H_ diff --git a/kttsd/plugins/festivalint/kttsd_festivalintplugin.desktop b/kttsd/plugins/festivalint/kttsd_festivalintplugin.desktop new file mode 100644 index 0000000..e8776da --- /dev/null +++ b/kttsd/plugins/festivalint/kttsd_festivalintplugin.desktop @@ -0,0 +1,72 @@ +[Desktop Entry] +Name=Festival Interactive +Name[da]=Festival Interaktiv +Name[el]=Διαδραστικό Festival +Name[eu]=Festival interaktiboa +Name[fa]=Festival تعاملی +Name[fi]=Interaktiivinen Festival +Name[ga]=Festival Idirghníomhach +Name[gl]=Festival Interactivo +Name[is]=Festival gagnvirkni +Name[it]=Festival Interattivo +Name[ja]=Festival インタラクティブ +Name[ka]=Festival ინტერაქტიული +Name[km]=អន្តរមុខងារ +Name[mk]=Интерактивен festival +Name[ms]=Interaktif Perayaan +Name[ne]=अन्तरक्रियात्मक फेस्टिभल +Name[pa]=ਫੈਸੀਟੇਵਲ ਇੰਟਰੇਕਟਵ +Name[pl]=Festival interaktywny +Name[pt_BR]=Festival Interativo +Name[sv]=Festival interaktiv +Name[ta]=பெஸ்டிவல் உள்செயல் +Name[tg]=Фестивали Интерактив +Name[tr]=Etkileşimli Festival +Name[vi]=Tương tác Festival +Comment=Festival speech synthetizer +Comment[bg]=Синтезатор на глас Festival +Comment[ca]=Sintetitzador de veu Festival +Comment[cs]=Hlasový syntetizér Festival +Comment[da]=Festival tale-synthetizer +Comment[de]=Festival Sprachsynthesizer +Comment[el]=Συνθέτης ομιλίας Festival +Comment[es]=Sintetizador de texto a voz Festival +Comment[et]=Kõnesüntesaator Festival +Comment[eu]=Festival hizketa-sintetizadorea +Comment[fa]=ترکیبدهندۀ گفتار Festival +Comment[fi]=Festival puhesyntetisaattori +Comment[fr]=Synthèse vocale Festival +Comment[ga]=Sintéiseoir cainte Festival +Comment[gl]=Sistetizador de voces Festival +Comment[hu]=Festival beszédszintetizátor +Comment[is]=Festival talgerfill +Comment[it]=Sintetizzatore vocale Festival +Comment[ja]=Festival スピーチシンセサイザ +Comment[ka]=Festival ხმის სინთეზატორი +Comment[km]= មុខងារកម្មវិធីសង្គ្រោះការនិយាយ +Comment[mk]=Синтетизатор за говор festival +Comment[ms]=Pensintesis tutur perayaan +Comment[nb]=Festival talesyntetisering +Comment[nds]=Blicksnuut Festival +Comment[ne]=संवाद सिन्थेसाइजर फेस्टिभल +Comment[nl]=Festival spraaksynthetizer +Comment[pa]=ਫੈਸੀਟੇਵਲ ਬੋਲੀ ਸੰਸਲੇਸ਼ਕ +Comment[pl]=Syntezator mowy Festival +Comment[pt]=O sintetizador de fala Festival +Comment[pt_BR]=Sistema de Sintetizador de Fala Festival +Comment[ru]=Синтезатор речи Festival +Comment[sk]=Syntetizátor reči Festival +Comment[sl]=Sintetizator govora Festival +Comment[sr]=Синтетизатор говора Festival +Comment[sr@Latn]=Sintetizator govora Festival +Comment[sv]=Festival talsyntes +Comment[ta]=பெஸ்டிவல் பேச்சு கூட்டிணைப்பான் +Comment[tg]=Таҳлилгари овози Festival +Comment[tr]=Festival konuşma bireştirici +Comment[uk]=Синтезатор мовлення Festival +Comment[vi]=Trình tổng hợp tiếng nói Festival +Comment[zh_TW]=Festival 語音合成器 +Type=Service +ServiceTypes=KTTSD/SynthPlugin +X-KDE-Library=libkttsd_festivalintplugin +X-KDE-Languages=en,en_US,en_GB,en_CA,es,es_mx,cy,de,fi,cs,pl,ru,it,sw,zu,fr_CA,vi diff --git a/kttsd/plugins/festivalint/sabletowave.scm b/kttsd/plugins/festivalint/sabletowave.scm new file mode 100644 index 0000000..71293b4 --- /dev/null +++ b/kttsd/plugins/festivalint/sabletowave.scm @@ -0,0 +1,92 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-*-mode:scheme-*- +;; ;; +;; Centre for Speech Technology Research ;; +;; University of Edinburgh, UK ;; +;; Copyright (c) 1996,1997 ;; +;; All Rights Reserved. ;; +;; ;; +;; Permission is hereby granted, free of charge, to use and distribute ;; +;; this software and its documentation without restriction, including ;; +;; without limitation the rights to use, copy, modify, merge, publish, ;; +;; distribute, sublicense, and/or sell copies of this work, and to ;; +;; permit persons to whom this work is furnished to do so, subject to ;; +;; the following conditions: ;; +;; 1. The code must retain the above copyright notice, this list of ;; +;; conditions and the following disclaimer. ;; +;; 2. Any modifications must be clearly marked as such. ;; +;; 3. Original authors' names are not deleted. ;; +;; 4. The authors' names are not used to endorse or promote products ;; +;; derived from this software without specific prior written ;; +;; permission. ;; +;; ;; +;; THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK ;; +;; DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ;; +;; ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT ;; +;; SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE ;; +;; FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ;; +;; WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN ;; +;; AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ;; +;; ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF ;; +;; THIS SOFTWARE. ;; +;; ;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Author: Alan W Black +;;; Date: November 1997 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; Adapted from text2wave by Alan Black. Original copyright listed above. +;; +;; Copyright 2004 by Gary Cramblitt <garycramblitt@comcast.net> +;; +;; This scheme module is used by the Festival Interactive plugin, +;; which is part of KTTSD. To use, +;; (load sabletowave.scm) +;; after starting Festival interactively, then to synth text containing +;; SABLE tags to a single wave file. +;; (ktts_sabletowave "sable text" "filename" volume) +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; List of generated intermediate wave files. +(defvar ktts_wavefiles nil) + +(define (ktts_save_record_wave utt) +"Saves the waveform and records its so it can be joined into a +a single waveform at the end." + (let ((fn (make_tmp_filename))) + (utt.save.wave utt fn) + (set! ktts_wavefiles (cons fn ktts_wavefiles)) + utt)) + +(define (ktts_combine_waves outfile volume) + "Join all the waves together into the desired output file +and delete the intermediate ones." + (let ((wholeutt (utt.synth (Utterance Text "")))) + (mapcar + (lambda (d) + (utt.import.wave wholeutt d t) + (delete-file d)) + (reverse ktts_wavefiles)) +;; (if ktts_frequency +;; (utt.wave.resample wholeutt (parse-number ktts_frequency))) + (if (not (equal? volume "1.0")) + (begin + (utt.wave.rescale wholeutt (parse-number volume)))) + (utt.save.wave wholeutt outfile 'riff) + )) + +;;; +;;; Redefine what happens to utterances during text to speech. +;;; Synthesize each utterance and save to a temporary wave file. +;;; +(set! tts_hooks (list utt.synth ktts_save_record_wave)) + +(define (ktts_sabletowave text filename volume) + (set! ktts_wavefiles nil) + ;; Do the synthesis, which creates multiple wave files. + (tts_text text 'sable) + ;; Now put the waveforms together and adjust volume. + (ktts_combine_waves filename volume) +) diff --git a/kttsd/plugins/festivalint/voices b/kttsd/plugins/festivalint/voices new file mode 100644 index 0000000..f6c0d58 --- /dev/null +++ b/kttsd/plugins/festivalint/voices @@ -0,0 +1,738 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<voices> + +<voice> + <code>kal_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>ked_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>us1_mbrola</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Female, MBROLA</name> +</voice> + +<voice> + <code>us2_mbrola</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male, MBROLA</name> +</voice> + +<voice> + <code>us3_mbrola</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male, MBROLA</name> +</voice> + +<voice> + <code>don_diphone</code> + <language>en_GB</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>British Male</name> +</voice> + +<voice> + <code>rab_diphone</code> + <language>en_GB</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>British Male</name> +</voice> + +<voice> + <code>el_diphone</code> + <language>es</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Castilian Spanish Male</name> +</voice> + +<voice> + <code>cmu_us_bdl_arctic_hts</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>American Male, HTS</name> +</voice> + +<voice> + <code>cmu_us_slt_arctic_hts</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>American Female, HTS</name> +</voice> + +<voice> + <code>cmu_us_jmk_arctic_hts</code> + <language>en_CA</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Canadian English Male, HTS</name> +</voice> + +<voice> + <code>cmu_us_awb_arctic_hts</code> + <language>en_GB</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Scottish Male, HTS</name> +</voice> + +<voice> + <code>cmu_us_kal_com_hts</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>American Male, HTS</name> +</voice> + +<voice> + <code>cstr_us_ked_timit_hts</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>American Male, HTS</name> +</voice> + +<voice> + <code>cstr_us_jmk_arctic_multisyn</code> + <language>en_CA</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>true</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>false</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Canadian English Male, MultiSyn</name> +</voice> + +<voice> + <code>cstr_us_awb_arctic_multisyn</code> + <language>en_GB</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>true</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>false</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Scottish Male, MultiSyn</name> +</voice> + +<voice> + <code>german_de1_os</code> + <language>de</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>German Female, Festival</name> +</voice> + +<voice> + <code>german_de2_os</code> + <language>de</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>German Male, Festival</name> +</voice> + +<voice> + <code>german_de3_os</code> + <language>de</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>German Female, Festival</name> +</voice> + +<voice> + <code>abc_diphone</code> + <language>es</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>Mexican Spanish Male, OGC</name> +</voice> + +<voice> + <code>hvs_diphone</code> + <language>es</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>Mexican Spanish Female, OGC</name> +</voice> + +<voice> + <code>mwm_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male, OGC</name> +</voice> + +<voice> + <code>aec_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male, OGC</name> +</voice> + +<voice> + <code>jph_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male, OGC</name> +</voice> + +<voice> + <code>tll_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Female, OGC</name> +</voice> + +<voice> + <code>ogirab_diphone</code> + <language>en_UK</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>British Male, OGC</name> +</voice> + +<voice> + <code>mwm2jph_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Male, OGC</name> +</voice> + +<voice> + <code>mwm2tll_diphone</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>American Female, OGC</name> +</voice> + +<voice> + <code>hy_fi_mv_diphone</code> + <language>fi</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Finnish Male</name> +</voice> + +<voice> + <code>czech_mbrola_cz2</code> + <language>cs_CZ</language> + <codec>ISO 8859-2</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Czech Male, MBROLA</name> +</voice> + +<voice> + <code>cstr_pl_em_diphone</code> + <language>pl</language> + <codec>ISO 8859-2</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>true</pitch-adjustable> + <name>Polish Male</name> +</voice> + +<voice> + <code>msu_ru_nsh_diphone</code> + <language>ru</language> + <codec>KOI8-R</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Russian Male</name> +</voice> + +<voice> + <code>pc_diphone</code> + <language>it</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Italian Male</name> +</voice> + +<voice> + <code>lp_diphone</code> + <language>it</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Italian Female</name> +</voice> + +<voice> + <code>lp_mbrola</code> + <language>it</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Italian Female</name> +</voice> + +<voice> + <code>pc_mbrola</code> + <language>it</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Italian Male</name> +</voice> + +<voice> + <code>pc_ogi_diphone</code> + <language>it</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Italian Male</name> +</voice> + +<voice> + <code>lp_ogi_diphone</code> + <language>it</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Italian Female</name> +</voice> + +<voice> + <code>UON_swahili_kw_multisyn</code> + <language>sw</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>true</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>false</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Kiswahili Male</name> +</voice> + +<voice> + <code>uyo_ibibio_eno_multisyn</code> + <!-- There is no iso 639 (two-letter) code for ibibio. We use Zulu here. + The iso 639-2 (three-letter) code is nic (Niger-Kordofanian) --> + <language>zu</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>true</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>false</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Ibibio Female</name> +</voice> + +<voice> + <code>csir_isizulu_buhle_multisyn</code> + <language>zu</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>true</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>false</rate-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Zulu Male</name> +</voice> + +<voice> + <code>cepstral_david</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>cepstral_diane</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Female</name> +</voice> + +<voice> + <code>cepstral_william</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>cepstral_amy</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Female</name> +</voice> + +<voice> + <code>cepstral_frank</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>cepstral_emily</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Female</name> +</voice> + +<voice> + <code>cepstral_duncan</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>cepstral_linda</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Female</name> +</voice> + +<voice> + <code>cepstral_robin</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Female</name> +</voice> + +<voice> + <code>cepstral_walter</code> + <language>en_US</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>American Male</name> +</voice> + +<voice> + <code>cepstral_lawrence</code> + <language>en_UK</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>British Male</name> +</voice> + +<voice> + <code>cepstral_millie</code> + <language>en_UK</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>British Female</name> +</voice> + +<voice> + <code>cepstral_jean-pierre</code> + <language>fr_CA</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>French Canadian Male</name> +</voice> + +<voice> + <code>cepstral_isabelle</code> + <language>fr_CA</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>French Canadian Female</name> +</voice> + +<voice> + <code>cepstral_matthias</code> + <language>de</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>German Male</name> +</voice> + +<voice> + <code>cepstral_katrin</code> + <language>de</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>German Female</name> +</voice> + +<voice> + <code>cepstral_miguel</code> + <language>es</language> + <codec>ISO 8859-1</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>Americas Spanish Male</name> +</voice> + +<voice> + <code>cepstral_marta</code> + <language>es</language> + <codec>ISO 8859-1</codec> + <gender>female</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <rate-adjustable>true</rate-adjustable> + <pitch-adjustable>false/</pitch-adjustable> + <name>Americas Spanish Female</name> +</voice> + +<voice> + <code>wow_vi_ptn_diphone</code> + <language>vi</language> + <codec>UTF-8</codec> + <gender>male</gender> + <preload>false</preload> + <volume-adjustable>true</volume-adjustable> + <speed-adjustable>true></speed-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Vietnamese Male</name> +</voice> + +<voice> + <code>wow_vi_liz_diphone</code> + <language>vi</language> + <codec>UTF-8</codec> + <gender>female</gender> + <volume-adjustable>true</volume-adjustable> + <speed-adjustable>true</speed-adjustable> + <pitch-adjustable>false</pitch-adjustable> + <name>Vietnamese Female</name> +</voice> + +</voices> diff --git a/kttsd/plugins/flite/Makefile.am b/kttsd/plugins/flite/Makefile.am new file mode 100644 index 0000000..c451be3 --- /dev/null +++ b/kttsd/plugins/flite/Makefile.am @@ -0,0 +1,20 @@ +INCLUDES = \ + -I$(top_srcdir)/kttsd/libkttsd -I$(top_builddir)/kttsd/libkttsd \ + $(all_includes) + +METASOURCES = AUTO + +kde_module_LTLIBRARIES = libkttsd_fliteplugin.la + +libkttsd_fliteplugin_la_SOURCES = \ + fliteconfwidget.ui \ + fliteconf.cpp \ + fliteproc.cpp \ + fliteplugin.cpp +libkttsd_fliteplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_fliteplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_fliteplugin.desktop +servicesdir = $(kde_servicesdir) + +noinst_HEADERS = fliteproc.h fliteconf.h fliteconfwidget.h diff --git a/kttsd/plugins/flite/README b/kttsd/plugins/flite/README new file mode 100644 index 0000000..1bff55b --- /dev/null +++ b/kttsd/plugins/flite/README @@ -0,0 +1,3 @@ +This is the directory containing the Festival Lite plug in. +This plug in is developed and maintained by Gary Cramblitt. +<garycramblitt@comcast.net> diff --git a/kttsd/plugins/flite/configure.in.bot b/kttsd/plugins/flite/configure.in.bot new file mode 100644 index 0000000..54bd84e --- /dev/null +++ b/kttsd/plugins/flite/configure.in.bot @@ -0,0 +1,15 @@ +if test "x$flite_bindir" = "xno"; then + if test "$compile_flite_plugin" = "yes"; then + echo "" + echo "=======================================================" + echo "The Festival Lite (flite) program does not appear to be" + echo "installed on this system. The Flite plugin will" + echo "be built, but you need to install Flite before you" + echo "can use it. You can get it at" + echo " http://www.speech.cs.cmu.edu/flite/index.html" + echo "Debian users: apt-get install flite" + echo "======================================================" + all_tests=bad + fi +fi + diff --git a/kttsd/plugins/flite/configure.in.in b/kttsd/plugins/flite/configure.in.in new file mode 100644 index 0000000..f9b222f --- /dev/null +++ b/kttsd/plugins/flite/configure.in.in @@ -0,0 +1,22 @@ +dnl ========================================= +dnl checks for Festival Lite (Flite) Plug In +dnl ========================================= + +AC_ARG_ENABLE(kttsd-flite, + AC_HELP_STRING([--enable-kttsd-flite], + [build KTTSD Festival Lite (flite) [default=yes]]), + flite_plugin=$enableval, + flite_plugin=yes) + +compile_flite_plugin="yes" + +if test "x$flite_plugin" = "xno"; then + compile_flite_plugin="no" +fi + +dnl Check for flite executable. +dnl Note that flite plugin is always built, unless +dnl user overrides on configure command line. +AC_PATH_PROG(flite_bindir, "flite", "no") + +AM_CONDITIONAL(include_kttsd_flite, test "x$compile_flite_plugin" != "xno") diff --git a/kttsd/plugins/flite/fliteconf.cpp b/kttsd/plugins/flite/fliteconf.cpp new file mode 100644 index 0000000..cbc4b2d --- /dev/null +++ b/kttsd/plugins/flite/fliteconf.cpp @@ -0,0 +1,199 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration widget and functions for Festival (Interactive) plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qlayout.h> +#include <qfile.h> +#include <qapplication.h> + +// KDE includes. +#include <klocale.h> +#include <kdialog.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kprogress.h> + +// KTTS includes. +#include <testplayer.h> + +// Flite Plugin includes. +#include "fliteproc.h" +#include "fliteconf.h" +#include "fliteconf.moc" + +/** Constructor */ +FliteConf::FliteConf( QWidget* parent, const char* name, const QStringList& /*args*/) : + PlugInConf(parent, name) +{ + // kdDebug() << "FliteConf::FliteConf: Running" << endl; + m_fliteProc = 0; + m_progressDlg = 0; + + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "FliteConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new FliteConfWidget(this, "FliteConfigWidget"); + layout->addWidget(m_widget); + + defaults(); + + connect(m_widget->flitePath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->fliteTest, SIGNAL(clicked()), this, SLOT(slotFliteTest_clicked())); +} + +/** Destructor */ +FliteConf::~FliteConf(){ + // kdDebug() << "Running: FliteConf::~FliteConf()" << endl; + if (!m_waveFile.isNull()) QFile::remove(m_waveFile); + delete m_fliteProc; + delete m_progressDlg; +} + +void FliteConf::load(KConfig *config, const QString &configGroup){ + // kdDebug() << "FliteConf::load: Loading configuration for language " << langGroup << " with plug in " << "Festival Lite (flite)" << endl; + + config->setGroup(configGroup); + QString fliteExe = config->readEntry("FliteExePath", QString::null); + if (fliteExe.isEmpty()) + { + config->setGroup("Flite"); + fliteExe = config->readEntry("FliteExePath", "flite"); + } + m_widget->flitePath->setURL(fliteExe); +} + +void FliteConf::save(KConfig *config, const QString &configGroup){ + // kdDebug() << "FliteConf::save: Saving configuration for language " << langGroup << " with plug in " << "Festival Lite (flite)" << endl; + + config->setGroup("Flite"); + config->writeEntry("FliteExePath", + realFilePath(m_widget->flitePath->url())); + config->setGroup(configGroup); + config->writeEntry("FliteExePath", + realFilePath(m_widget->flitePath->url())); +} + +void FliteConf::defaults(){ + // kdDebug() << "FliteConf::defaults: Running" << endl; + m_widget->flitePath->setURL("flite"); +} + +void FliteConf::setDesiredLanguage(const QString &lang) +{ + m_languageCode = lang; +} + +QString FliteConf::getTalkerCode() +{ + QString fliteExe = realFilePath(m_widget->flitePath->url()); + if (!fliteExe.isEmpty()) + { + if (!getLocation(fliteExe).isEmpty()) + { + 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("Festival Lite (flite)"); + } + } + return QString::null; +} + +void FliteConf::slotFliteTest_clicked() +{ + // kdDebug() << "FliteConf::slotFliteTest_clicked(): Running" << endl; + // If currently synthesizing, stop it. + if (m_fliteProc) + m_fliteProc->stopText(); + else + { + m_fliteProc = new FliteProc(); + connect (m_fliteProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + // Create a temp file name for the wave file. + KTempFile tempFile (locateLocal("tmp", "fliteplugin-"), ".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_flite_testdlg", + i18n("Testing"), + i18n("Testing."), + true); + m_progressDlg->progressBar()->hide(); + m_progressDlg->setAllowCancel(true); + + // Play an English test. Flite only supports English. + connect (m_fliteProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + m_fliteProc->synth( + testMsg, + tmpWaveFile, + realFilePath(m_widget->flitePath->url())); + + // Display progress dialog modally. Processing continues when plugin signals synthFinished, + // or if user clicks Cancel button. + m_progressDlg->exec(); + disconnect (m_fliteProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + if (m_progressDlg->wasCancelled()) m_fliteProc->stopText(); + delete m_progressDlg; + m_progressDlg = 0; +} + +void FliteConf::slotSynthFinished() +{ + // If user canceled, progress dialog is gone, so exit. + if (!m_progressDlg) + { + m_fliteProc->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_fliteProc->getFilename(); + // Tell synth we're done. + m_fliteProc->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 FliteConf::slotSynthStopped() +{ + // Clean up after canceling test. + QString filename = m_fliteProc->getFilename(); + if (!filename.isNull()) QFile::remove(filename); +} diff --git a/kttsd/plugins/flite/fliteconf.h b/kttsd/plugins/flite/fliteconf.h new file mode 100644 index 0000000..aa26c7b --- /dev/null +++ b/kttsd/plugins/flite/fliteconf.h @@ -0,0 +1,124 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Configuration widget and functions for Festival (Interactive) plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FLITECONF_H_ +#define _FLITECONF_H_ + +// Qt includes. +#include <qstring.h> + +// KDE includes. +#include <kconfig.h> +#include <kdebug.h> + +// KTTS includes. +#include <pluginconf.h> + +// Flite plugin includes. +#include "fliteconfwidget.h" + +class FliteProc; +class KProgressDialog; + +class FliteConf : public PlugInConf { + Q_OBJECT + + public: + /** Constructor */ + FliteConf( QWidget* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~FliteConf(); + + /** 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() << "FliteConf::configChanged: Running" << endl; + emit changed(true); + }; + void slotFliteTest_clicked(); + void slotSynthFinished(); + void slotSynthStopped(); + + private: + // Language code. + QString m_languageCode; + // Configuration Widget. + FliteConfWidget* m_widget; + // Flite synthesizer. + FliteProc* m_fliteProc; + // Synthesized wave file name. + QString m_waveFile; + // Progress dialog. + KProgressDialog* m_progressDlg; +}; +#endif // _FLITECONF_H_ diff --git a/kttsd/plugins/flite/fliteconfwidget.ui b/kttsd/plugins/flite/fliteconfwidget.ui new file mode 100644 index 0000000..839970e --- /dev/null +++ b/kttsd/plugins/flite/fliteconfwidget.ui @@ -0,0 +1,186 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>FliteConfWidget</class> +<author>Gary Cramblitt</author> +<widget class="QWidget"> + <property name="name"> + <cstring>FliteConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>559</width> + <height>233</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>Flite Config UI</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the configuration dialog for the Festival Lite (Flite) speech synthesis engine.</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>100</height> + </size> + </property> + </spacer> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>fliteConfigurationBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>GroupBoxPanel</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="title"> + <string>Festival &Lite (flite) Configuration</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This is the configuration dialog for the Festival Lite (Flite) speech synthesis engine.</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="QLayoutWidget" row="0" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>flitePathBox</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>flitePathLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Flite executable path:</string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>flitePath</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>If Flite is in your PATH environment variable, simply enter "flite", otherwise specify the complete path to the Flite executable program.</string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>flitePath</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="url" stdset="0"> + <string>flite</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If Flite is in your PATH environment variable, simply enter "flite", otherwise specify the complete path to the Flite executable program.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QPushButton" row="1" column="1"> + <property name="name"> + <cstring>fliteTest</cstring> + </property> + <property name="text"> + <string>&Test</string> + </property> + </widget> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>221</width> + <height>20</height> + </size> + </property> + </spacer> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<includes> + <include location="global" impldecl="in declaration">kurlrequester.h</include> + <include location="global" impldecl="in implementation">kurlrequester.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/plugins/flite/fliteplugin.cpp b/kttsd/plugins/flite/fliteplugin.cpp new file mode 100644 index 0000000..602b5bb --- /dev/null +++ b/kttsd/plugins/flite/fliteplugin.cpp @@ -0,0 +1,31 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Generating the factories so festival lite (flite) can be used as plug in. + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#include <kgenericfactory.h> + +#include "fliteconf.h" +#include "fliteproc.h" + +typedef K_TYPELIST_2( FliteProc, FliteConf ) Flite; +K_EXPORT_COMPONENT_FACTORY( libkttsd_fliteplugin, KGenericFactory<Flite>("kttsd_flite") ) + diff --git a/kttsd/plugins/flite/fliteproc.cpp b/kttsd/plugins/flite/fliteproc.cpp new file mode 100644 index 0000000..54b15e3 --- /dev/null +++ b/kttsd/plugins/flite/fliteproc.cpp @@ -0,0 +1,281 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Main speaking functions for the Festival Lite (Flite) Plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +// Qt includes. +#include <qstring.h> +#include <qstringlist.h> + +// KDE includes. +#include <kdebug.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kprocess.h> + +// Flite Plugin includes. +#include "fliteproc.h" +#include "fliteproc.moc" + +/** Constructor */ +FliteProc::FliteProc( QObject* parent, const char* name, const QStringList& ) : + PlugInProc( parent, name ){ + kdDebug() << "FliteProc::FliteProc: Running" << endl; + m_state = psIdle; + m_waitingStop = false; + m_fliteProc = 0; +} + +/** Destructor */ +FliteProc::~FliteProc(){ + kdDebug() << "FliteProc::~FliteProc:: Running" << endl; + if (m_fliteProc) + { + stopText(); + delete m_fliteProc; + } +} + +/** Initialize the speech */ +bool FliteProc::init(KConfig* config, const QString& configGroup){ + // kdDebug() << "Running: FliteProc::init(const QString &lang)" << endl; + // kdDebug() << "Initializing plug in: Flite" << endl; + // Retrieve path to flite executable. + config->setGroup(configGroup); + m_fliteExePath = config->readEntry("FliteExePath", "flite"); + kdDebug() << "FliteProc::init: path to flite: " << m_fliteExePath << 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 FliteProc::sayText(const QString &text) +{ + synth(text, QString::null, m_fliteExePath); +} + +/** +* 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 FliteProc::synthText(const QString& text, const QString& suggestedFilename) +{ + synth(text, suggestedFilename, m_fliteExePath); +} + +/** +* Say or Synthesize text. +* @param text The text to be synthesized. +* @param suggestedFilename If not Null, synthesize only to this filename, otherwise +* synthesize and audibilize the text. +*/ +void FliteProc::synth( + const QString &text, + const QString &synthFilename, + const QString& fliteExePath) +{ + // kdDebug() << "Running: FliteProc::synth(const QString &text)" << endl; + + if (m_fliteProc) + { + if (m_fliteProc->isRunning()) m_fliteProc->kill(); + delete m_fliteProc; + m_fliteProc = 0; + } + // kdDebug()<< "FliteProc::synth: Creating Flite object" << endl; + m_fliteProc = new KProcess; + connect(m_fliteProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + connect(m_fliteProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_fliteProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + connect(m_fliteProc, SIGNAL(wroteStdin(KProcess*)), + this, SLOT(slotWroteStdin(KProcess* ))); + if (synthFilename.isNull()) + m_state = psSaying; + else + m_state = psSynthing; + + + // Encode quotation characters. + QString saidText = text; +/* + saidText.replace("\\\"", "#!#!"); + saidText.replace("\"", "\\\""); + saidText.replace("#!#!", "\\\""); + // Remove certain comment characters. + saidText.replace("--", ""); + saidText = "\"" + saidText + "\""; +*/ + saidText += "\n"; + + *m_fliteProc << fliteExePath; +// *m_fliteProc << "-t" << saidText; + if (!synthFilename.isNull()) *m_fliteProc << "-o" << synthFilename; + + // Ok, let's rock. + m_synthFilename = synthFilename; + kdDebug() << "FliteProc::synth: Synthing text: '" << saidText << "' using Flite plug in" << endl; + if (!m_fliteProc->start(KProcess::NotifyOnExit, KProcess::All)) + { + kdDebug() << "FliteProc::synth: Error starting Flite process. Is flite in the PATH?" << endl; + m_state = psIdle; + return; + } + kdDebug()<< "FliteProc:synth: Flite initialized" << endl; + m_fliteProc->writeStdin(saidText.latin1(), saidText.length()); +} + +/** +* 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 FliteProc::getFilename() +{ + kdDebug() << "FliteProc::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 FliteProc::stopText(){ + kdDebug() << "FliteProc::stopText:: Running" << endl; + if (m_fliteProc) + { + if (m_fliteProc->isRunning()) + { + kdDebug() << "FliteProc::stopText: killing Flite." << endl; + m_waitingStop = true; + m_fliteProc->kill(); + } else m_state = psIdle; + }else m_state = psIdle; + kdDebug() << "FliteProc::stopText: Flite stopped." << endl; +} + +void FliteProc::slotProcessExited(KProcess*) +{ + kdDebug() << "FliteProc:slotProcessExited: Flite 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 FliteProc::slotReceivedStdout(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "FliteProc::slotReceivedStdout: Received output from Flite: " << buf << endl; +} + +void FliteProc::slotReceivedStderr(KProcess*, char* buffer, int buflen) +{ + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "FliteProc::slotReceivedStderr: Received error from Flite: " << buf << endl; +} + +void FliteProc::slotWroteStdin(KProcess*) +{ + kdDebug() << "FliteProc::slotWroteStdin: closing Stdin" << endl; + m_fliteProc->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 FliteProc::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 FliteProc::ackFinished() +{ + if (m_state == psFinished) + { + m_state = psIdle; + m_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 FliteProc::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 FliteProc::supportsSynth() { return true; } + diff --git a/kttsd/plugins/flite/fliteproc.h b/kttsd/plugins/flite/fliteproc.h new file mode 100644 index 0000000..7737b3a --- /dev/null +++ b/kttsd/plugins/flite/fliteproc.h @@ -0,0 +1,187 @@ +/***************************************************** vim:set ts=4 sw=4 sts=4: + Main speaking functions for the Festival Lite (Flite) Plug in + ------------------- + Copyright: + (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net> + ------------------- + Original author: Gary Cramblitt <garycramblitt@comcast.net> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ******************************************************************************/ + +#ifndef _FLITEPROC_H_ +#define _FLITEPROC_H_ + +// Qt includes. +#include <qstringlist.h> +#include <qmutex.h> + +// KTTS includes. +#include <pluginproc.h> + +class KProcess; + +class FliteProc : public PlugInProc{ + Q_OBJECT + + public: + /** + * Constructor + */ + FliteProc( QObject* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** + * Destructor + */ + virtual ~FliteProc(); + + /** + * Initializate the speech engine. + * @param config Settings object. + * @param configGroup Settings Group. + */ + virtual 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 text The text to be synthesized. + * @param synthFilename If not Null, synthesize only to this filename, otherwise + * synthesize and audibilize the text. + * @param fliteExePath Path to the flite executable. + */ + void synth( + const QString &text, + const QString &synthFilename, + const QString &fliteExePath); + + 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: + + /** + * Path to flite executable (from config). + */ + QString m_fliteExePath; + + /** + * Flite process + */ + KProcess* m_fliteProc; + + /** + * 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 // _FLITEPROC_H_ diff --git a/kttsd/plugins/flite/kttsd_fliteplugin.desktop b/kttsd/plugins/flite/kttsd_fliteplugin.desktop new file mode 100644 index 0000000..2105253 --- /dev/null +++ b/kttsd/plugins/flite/kttsd_fliteplugin.desktop @@ -0,0 +1,59 @@ +[Desktop Entry] +Name=Festival Lite (flite) +Name[el]=Festival ελαφρύ (flite) +Name[fi]=Festival Lite (kevytversio) +Name[ka]=Festival მსუბუქი (flite) +Name[km]=មុខងារ Lite (flite) +Name[nds]=Festival Lite (FLite) +Name[ne]=फेस्टिभल लाइट (एफ लाईट) +Name[pt_BR]=Festival Lite +Name[ta]=பெஸ்டிவல் லைட் (flite) +Name[tr]=Festival Lite(flite) +Name[zh_TW]=Festival Lite (flite)4 +Comment=Festival Lite (flite) speech synthesizer +Comment[bg]=Олекотен синтезатор на глас Festival Lite +Comment[ca]=Sintetitzador de veu Festival Lite (flite) +Comment[cs]=Hlasový syntetizér Festival Lite (flite) +Comment[da]=Festival Lite (flite) tale-synthesizer +Comment[de]=Festival Lite (flite) Sprachsynthesizer +Comment[el]=Συνθέτης ομιλίας Festival ελαφρύ (flite) +Comment[es]=Sintetizador de texto a voz Festival (flite) +Comment[et]=Kõnesüntesaator Festival lite (flite) +Comment[eu]=Festival Lite (flite) hizketa-sintetizadorea +Comment[fa]=ترکیبدهندۀ گفتار Festival Lite (flite) +Comment[fi]=Festival Lite (kevytversio) puhesyntetisaattori +Comment[fr]=Synthèse vocale Festival Lite (flite) +Comment[ga]=Sintéiseoir cainte Festival Lite (flite) +Comment[gl]=Sintetizador de voces Festival Lite (flite) +Comment[hu]=Festival Lite (flite) beszédszintetizátor +Comment[is]=Festival Lite (flite) talgerfill +Comment[it]=Sintetizzatore vocale Festival Lite (flite) +Comment[ja]=Festival Lite (flite) スピーチシンセサイザ +Comment[ka]=Festival მარტივი (flite) ხმის სინთეზატორი +Comment[km]=មុខងារកម្មវិធីសង្គ្រោះការនិយាយ Lite (flite) +Comment[mk]=Festival Lite (flite) синтетизатор на говор +Comment[ms]=Pensintesis tutur Festival Lite (flite) +Comment[nb]=Festival Lite (flite) talesyntetisering +Comment[nds]=Blicksnuut Festival lite (FLite) +Comment[ne]=फेस्टिभल लाइट (एफ लाइट) संवाद सिन्थेसाइजर +Comment[nl]=Festival Lite (flite) spraaksynthesizer +Comment[pa]=Festival Lite (flite) ਬੋਲੀ ਸੰਸਲੇਸ਼ਕ +Comment[pl]=Syntezator mowy Festival Lite (flite) +Comment[pt]=O sintetizador de fala Festival Lite (flite) +Comment[pt_BR]=Sintetizador de fala Festival Lite (leve) +Comment[ru]=Синтезатор речи Festival Lite (flite) +Comment[sk]=Syntetizátor reči Festival Lite (flite) +Comment[sl]=Sintetizator govora Festival Lite (flite) +Comment[sr]=Синтетизатор говора Festival Lite (flite) +Comment[sr@Latn]=Sintetizator govora Festival Lite (flite) +Comment[sv]=Festival Lite (flite) talsyntes +Comment[ta]=பெஸ்டிவல் லைட் (flite) பேச்சு கூட்டிணைப்பான் +Comment[tg]=Таҳлилгари овози Festival Lite (flite) +Comment[tr]=Festival Lite(flite) konuşma bireştirici +Comment[uk]=Синтезатор мовлення Festival Lite (flite) +Comment[vi]=Trình tổng hợp tiếng nói Festival Lite (flite) +Comment[zh_TW]=Festival Lite (flite) 語音合成器 +Type=Service +ServiceTypes=KTTSD/SynthPlugin +X-KDE-Library=libkttsd_fliteplugin +X-KDE-Languages=en,en_US,en_GB diff --git a/kttsd/plugins/freetts/Makefile.am b/kttsd/plugins/freetts/Makefile.am new file mode 100644 index 0000000..be9312b --- /dev/null +++ b/kttsd/plugins/freetts/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_freettsplugin.la + +libkttsd_freettsplugin_la_SOURCES = \ + freettsconfigwidget.ui \ + freettsconf.cpp \ + freettsproc.cpp \ + freettsplugin.cpp +libkttsd_freettsplugin_la_LDFLAGS = $(KDE_PLUGIN) $(all_libraries) +libkttsd_freettsplugin_la_LIBADD = $(top_builddir)/kttsd/libkttsd/libkttsd.la + +services_DATA = kttsd_freettsplugin.desktop +servicesdir = $(kde_servicesdir) diff --git a/kttsd/plugins/freetts/README b/kttsd/plugins/freetts/README new file mode 100644 index 0000000..54d9dc2 --- /dev/null +++ b/kttsd/plugins/freetts/README @@ -0,0 +1,6 @@ +This is the directory containing the FreeTTS plug in. +If you intend to edit the GUI (freettsconfig.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 place. diff --git a/kttsd/plugins/freetts/configure.in.bot b/kttsd/plugins/freetts/configure.in.bot new file mode 100644 index 0000000..b3985b9 --- /dev/null +++ b/kttsd/plugins/freetts/configure.in.bot @@ -0,0 +1,15 @@ +if test "x$freetts_bindir" = "xno"; then + if test "$compile_freetts_plugin" = "yes"; then + echo "" + echo "======================================================" + echo "The freetts.jar Java archive has not been found on" + echo "this system. The FreeTTS plugin will be built, " + echo "but you must install FreeTTS it before you can use it," + echo "or if it already installed you must set the path to" + echo "it in the KDE Text-to-Speech Manager." + echo "FreeTTS is available from freetts.sourceforge.net" + echo "=====================================================" + all_tests=bad + fi +fi + diff --git a/kttsd/plugins/freetts/configure.in.in b/kttsd/plugins/freetts/configure.in.in new file mode 100644 index 0000000..78555fc --- /dev/null +++ b/kttsd/plugins/freetts/configure.in.in @@ -0,0 +1,22 @@ +dnl ================ +dnl checks for FreeTTS +dnl ================ + +AC_ARG_ENABLE(kttsd-freetts, + AC_HELP_STRING([--enable-kttsd-freetts], + [build KTTSD FreeTTS Plugin [default=yes]]), + freetts_plugin=$enableval, + freetts_plugin=yes) + +compile_freetts_plugin="yes" + +if test "x$freetts_plugin" = "xno"; then + compile_freetts_plugin="no" +fi + +dnl Check for festival executable. +dnl Note that Festival Interactive plugin is always built +dnl whether binary is found or not, unless user overrides with -disable-freetts. +AC_PATH_PROG(freetts_bindir, "freetts.jar", "no") + +AM_CONDITIONAL(include_kttsd_freetts, test "x$compile_freetts_plugin" = "xyes") diff --git a/kttsd/plugins/freetts/freettsconf.cpp b/kttsd/plugins/freetts/freettsconf.cpp new file mode 100644 index 0000000..701af5e --- /dev/null +++ b/kttsd/plugins/freetts/freettsconf.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** + Configuration widget and functions for FreeTTS (interactive) plug in + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +// Qt includes. +#include <qlayout.h> +#include <qlabel.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qfile.h> +#include <qapplication.h> + +// KDE includes. +#include <kdialog.h> +#include <ktempfile.h> +#include <kstandarddirs.h> +#include <kmessagebox.h> +#include <klocale.h> +#include <kprogress.h> + +// KTTS includes. +#include <pluginconf.h> +#include <testplayer.h> + +// FreeTTS includes. +#include "freettsconf.h" +#include "freettsconfigwidget.h" + +/** Constructor */ +FreeTTSConf::FreeTTSConf( QWidget* parent, const char* name, const QStringList&/*args*/) : + PlugInConf( parent, name ) { + + // kdDebug() << "FreeTTSConf::FreeTTSConf: Running" << endl; + m_freettsProc = 0; + m_progressDlg = 0; + + QVBoxLayout *layout = new QVBoxLayout(this, KDialog::marginHint(), + KDialog::spacingHint(), "FreeTTSConfigWidgetLayout"); + layout->setAlignment (Qt::AlignTop); + m_widget = new FreeTTSConfWidget(this, "FreeTTSConfigWidget"); + layout->addWidget(m_widget); + + defaults(); + + connect(m_widget->freettsPath, SIGNAL(textChanged(const QString&)), + this, SLOT(configChanged())); + connect(m_widget->freettsTest, SIGNAL(clicked()), this, SLOT(slotFreeTTSTest_clicked())); +} + +/** Destructor */ +FreeTTSConf::~FreeTTSConf() { + // kdDebug() << "Running: FreeTTSConf::~FreeTTSConf()" << endl; + if (!m_waveFile.isNull()) QFile::remove(m_waveFile); + delete m_freettsProc; + delete m_progressDlg; +} + +void FreeTTSConf::load(KConfig *config, const QString &configGroup) { + // kdDebug() << "FreeTTSConf::load: Running" << endl; + + config->setGroup(configGroup); + QString freeTTSJar = config->readEntry("FreeTTSJarPath", QString::null); + if (freeTTSJar.isEmpty()) + { + config->setGroup("FreeTTS"); + freeTTSJar = config->readEntry("FreeTTSJarPath", QString::null); + } + if (freeTTSJar.isEmpty()) + freeTTSJar = getLocation("freetts.jar"); + m_widget->freettsPath->setURL(freeTTSJar); + /// If freettsPath is still empty, then we couldn't find the file in the path. +} + +void FreeTTSConf::save(KConfig *config, const QString &configGroup){ + // kdDebug() << "FreeTTSConf::save: Running" << endl; + + config->setGroup("FreeTTS"); + config->writeEntry("FreeTTSJarPath", + realFilePath(m_widget->freettsPath->url())); + + config->setGroup(configGroup); + if(m_widget->freettsPath->url().isEmpty()) + KMessageBox::sorry(0, i18n("Unable to locate freetts.jar in your path.\nPlease specify the path to freetts.jar in the Properties tab before using KDE Text-to-Speech"), i18n("KDE Text-to-Speech")); + config->writeEntry("FreeTTSJarPath", + realFilePath(m_widget->freettsPath->url())); +} + +void FreeTTSConf::defaults(){ + // kdDebug() << "Running: FreeTTSConf::defaults()" << endl; + m_widget->freettsPath->setURL(""); +} + +void FreeTTSConf::setDesiredLanguage(const QString &lang) +{ + m_languageCode = lang; +} + +QString FreeTTSConf::getTalkerCode() +{ + QString freeTTSJar = realFilePath(m_widget->freettsPath->url()); + if (!freeTTSJar.isEmpty()) + { + if (!getLocation(freeTTSJar).isEmpty()) + { + 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("FreeTTS"); + } + } + return QString::null; +} + +// QString FreeTTSConf::getLocation(const QString &name) { +// /// Iterate over the path and see if 'name' exists in it. Return the +// /// full path to it if it does. Else return an empty QString. +// kdDebug() << "FreeTTSConf::getLocation: Searching for " << name << " in the path... " << endl; +// kdDebug() << m_path << endl; +// for(QStringList::iterator it = m_path.begin(); it != m_path.end(); ++it) { +// QString fullName = *it; +// fullName += "/"; +// fullName += name; +// /// The user either has the directory of the file in the path... +// if(QFile::exists(fullName)) { +// return fullName; +// kdDebug() << fullName << endl; +// } +// /// ....Or the file itself +// else if(QFileInfo(*it).baseName().append(QString(".").append(QFileInfo(*it).extension())) == name) { +// return fullName; +// kdDebug() << fullName << endl; +// } +// } +// return ""; +// } + + +void FreeTTSConf::slotFreeTTSTest_clicked() +{ + // kdDebug() << "FreeTTSConf::slotFreeTTSTest_clicked(): Running" << endl; + // If currently synthesizing, stop it. + if (m_freettsProc) + m_freettsProc->stopText(); + else + { + m_freettsProc = new FreeTTSProc(); + connect (m_freettsProc, SIGNAL(stopped()), this, SLOT(slotSynthStopped())); + } + // Create a temp file name for the wave file. + KTempFile tempFile (locateLocal("tmp", "freettsplugin-"), ".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_freetts_testdlg", + i18n("Testing"), + i18n("Testing."), + true); + m_progressDlg->progressBar()->hide(); + m_progressDlg->setAllowCancel(true); + + // I think FreeTTS only officialy supports English, but if anyone knows of someone + // whos built up a different language lexicon and has it working with FreeTTS gimme an email at ceruleanblaze@gmail.com + connect (m_freettsProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + m_freettsProc->synth( + testMsg, + tmpWaveFile, + realFilePath(m_widget->freettsPath->url())); + + // Display progress dialog modally. Processing continues when plugin signals synthFinished, + // or if user clicks Cancel button. + m_progressDlg->exec(); + disconnect (m_freettsProc, SIGNAL(synthFinished()), this, SLOT(slotSynthFinished())); + if (m_progressDlg->wasCancelled()) m_freettsProc->stopText(); + delete m_progressDlg; + m_progressDlg = 0; +} + +void FreeTTSConf::slotSynthFinished() +{ + // If user canceled, progress dialog is gone, so exit. + if (!m_progressDlg) + { + m_freettsProc->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_freettsProc->getFilename(); + // Tell synth we're done. + m_freettsProc->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 FreeTTSConf::slotSynthStopped() +{ + // Clean up after canceling test. + QString filename = m_freettsProc->getFilename(); + if (!filename.isNull()) QFile::remove(filename); +} + +#include "freettsconf.moc" diff --git a/kttsd/plugins/freetts/freettsconf.h b/kttsd/plugins/freetts/freettsconf.h new file mode 100644 index 0000000..9ab3ae8 --- /dev/null +++ b/kttsd/plugins/freetts/freettsconf.h @@ -0,0 +1,122 @@ +/**************************************************************************** + Configuration widget and functions for FreeTTS (interactive) plug in + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _FREETTSCONF_H_ +#define _FREETTSCONF_H_ + +#include <qstringlist.h> + +#include <kdebug.h> +#include <kconfig.h> + +#include <pluginconf.h> + +#include "freettsconfigwidget.h" +#include "freettsproc.h" + +class QStringList; +class KProgressDialog; + +class FreeTTSConf : public PlugInConf { + Q_OBJECT + + public: + /** Constructor */ + FreeTTSConf( QWidget* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** Destructor */ + ~FreeTTSConf(); + + /** 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(); + + /** + * Function searches the $PATH for a file. If it exists, it returns the full path + * to that file. If not, then it returns an empty QString. + * @param name The name of the file to search for. + * @returns The full path to the file or an empty QString. + */ + + private slots: + void configChanged(bool t = true) { + emit changed(t); + }; + void slotFreeTTSTest_clicked(); + void slotSynthFinished(); + void slotSynthStopped(); + + private: + /// Language code. + QString m_languageCode; + + /// Configuration Widget. + FreeTTSConfWidget *m_widget; + + /// FreeTTS synthesizer. + FreeTTSProc *m_freettsProc; + + /// Synthesized wave file name. + QString m_waveFile; + + /// Progress dialog. + KProgressDialog* m_progressDlg; +}; +#endif diff --git a/kttsd/plugins/freetts/freettsconfigwidget.ui b/kttsd/plugins/freetts/freettsconfigwidget.ui new file mode 100644 index 0000000..1d95751 --- /dev/null +++ b/kttsd/plugins/freetts/freettsconfigwidget.ui @@ -0,0 +1,200 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>FreeTTSConfWidget</class> +<author>Gary Cramblitt <garycramblitt@comcast.net></author> +<widget class="QWidget"> + <property name="name"> + <cstring>FreeTTSConfWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>576</width> + <height>134</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="caption"> + <string>FreeTTS Config UI</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="0"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>16</height> + </size> + </property> + </spacer> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>freettsConfigurationBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>GroupBoxPanel</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + <property name="title"> + <string>FreeTTS Interactive 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="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>voicesPathBox</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>freettsPathLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&FreeTTS jar file:</string> + </property> + <property name="textFormat"> + <enum>AutoText</enum> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + <property name="buddy" stdset="0"> + <cstring>freettsPath</cstring> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>freettsPath</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>selectVoiceBox</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + </hbox> + </widget> + <widget class="QLayoutWidget" row="3" column="0"> + <property name="name"> + <cstring>layout10</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>410</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>freettsTest</cstring> + </property> + <property name="text"> + <string>Test</string> + </property> + </widget> + </hbox> + </widget> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<includes> + <include location="global" impldecl="in declaration">kurlrequester.h</include> + <include location="global" impldecl="in implementation">kurlrequester.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kttsd/plugins/freetts/freettsplugin.cpp b/kttsd/plugins/freetts/freettsplugin.cpp new file mode 100644 index 0000000..c8a75df --- /dev/null +++ b/kttsd/plugins/freetts/freettsplugin.cpp @@ -0,0 +1,26 @@ +/**************************************************************************** + Factory generation for the FreeTTS plugin so it can actually be used + as such. + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#include <kgenericfactory.h> + +#include "freettsconf.h" +#include "freettsproc.h" + +typedef K_TYPELIST_2( FreeTTSProc, FreeTTSConf ) FreeTTS; +K_EXPORT_COMPONENT_FACTORY( libkttsd_freettsplugin, KGenericFactory<FreeTTS>("kttsd_freetts")) + diff --git a/kttsd/plugins/freetts/freettsproc.cpp b/kttsd/plugins/freetts/freettsproc.cpp new file mode 100644 index 0000000..829fee9 --- /dev/null +++ b/kttsd/plugins/freetts/freettsproc.cpp @@ -0,0 +1,275 @@ +/**************************************************************************** + Main speaking functions for the FreeTTS Plug in + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#include <qstring.h> +#include <qstringlist.h> +#include <qfileinfo.h> + +#include <kdebug.h> +#include <kconfig.h> +#include <kstandarddirs.h> +#include <kprocess.h> + +#include "freettsproc.h" + +/** Constructor */ +FreeTTSProc::FreeTTSProc( QObject* parent, const char* name, const QStringList& /*args*/) : + PlugInProc( parent, name ) { + kdDebug() << "Running: FreeTTSProc::FreeTTSProc" << endl; + m_state = psIdle; + m_waitingStop = false; + m_freettsProc = 0; +} + +/** Desctructor */ +FreeTTSProc::~FreeTTSProc() { + kdDebug() << "Running: FreeTTSProc::~FreeTTSProc" << endl; + if (m_freettsProc) { + stopText(); + delete m_freettsProc; + } +} + +/** Initializate the speech */ +bool FreeTTSProc::init(KConfig *config, const QString &configGroup) { + kdDebug() << "Running: FreeTTSProc::init()" << endl; + kdDebug() << "Initializing plug in: FreeTTS" << endl; + config->setGroup(configGroup); + m_freettsJarPath = config->readEntry("FreeTTSJarPath", "freetts.jar"); + kdDebug() << "FreeTTSProc::init: path to freetts.jar: " << m_freettsJarPath << 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 FreeTTSProc::sayText(const QString &text) { + synth(text, QString::null, m_freettsJarPath); +} + +/** + * 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 FreeTTSProc::synthText(const QString& text, const QString& suggestedFilename) { + kdDebug() << "Running: FreeTTSProc::synthText" << endl; + synth(text, suggestedFilename, m_freettsJarPath); +} + +// A little helper function because KDE 3.2 kdDebug() does not support QValueList<QCString>. +QStringList argsToQStringList(const QValueList<QCString> list) +{ + QStringList newList; + QValueList<QCString>::ConstIterator it = list.begin(); + for ( ; it != list.end(); ++it ) newList.append(*it); + return newList; +} + +void FreeTTSProc::synth( + const QString &text, + const QString &synthFilename, + const QString& freettsJarPath) { + + kdDebug() << "Running: FreeTTSProc::synth" << endl; + + if (m_freettsProc) { + if (m_freettsProc->isRunning()) m_freettsProc->kill(); + delete m_freettsProc; + m_freettsProc = 0; + + } + + m_freettsProc = new KProcess; + connect(m_freettsProc, SIGNAL(processExited(KProcess*)), + this, SLOT(slotProcessExited(KProcess*))); + connect(m_freettsProc, SIGNAL(receivedStdout(KProcess*, char*, int)), + this, SLOT(slotReceivedStdout(KProcess*, char*, int))); + connect(m_freettsProc, SIGNAL(receivedStderr(KProcess*, char*, int)), + this, SLOT(slotReceivedStderr(KProcess*, char*, int))); + connect(m_freettsProc, SIGNAL(wroteStdin(KProcess*)), + this, SLOT(slotWroteStdin(KProcess* ))); + if (synthFilename.isNull()) + m_state = psSaying; + else + m_state = psSynthing; + + + QString saidText = text; + saidText += "\n"; + + /// As freetts.jar doesn't seem to like being called from an absolute path, + /// we need to strip off the path to freetts.jar and pass it to + /// KProcess::setWorkingDirectory() + /// We could just strip off 11 characters from the end of the path to freetts.jar, but thats + /// not exactly very portable... + QString filename = QFileInfo(freettsJarPath).baseName().append(QString(".").append(QFileInfo(freettsJarPath).extension())); + QString freettsJarDir = freettsJarPath.left((freettsJarPath.length() - filename.length()) - 1); + + m_freettsProc->setWorkingDirectory(freettsJarDir); + kdDebug() << "FreeTTSProc::synthText: moved to directory '" << freettsJarDir << "'" << endl; + kdDebug() << "FreeTTSProc::synthText: Running file: '" << filename << "'" << endl; + *m_freettsProc << "java" << "-jar" << filename; + + /// Dump audio into synthFilename + + if (!synthFilename.isNull()) *m_freettsProc << "-dumpAudio" << synthFilename; + + m_synthFilename = synthFilename; + + kdDebug() << "FreeTTSProc::synth: Synthing text: '" << saidText << "' using FreeTTS plug in" << endl; + if (!m_freettsProc->start(KProcess::NotifyOnExit, KProcess::All)) { + kdDebug() << "FreeTTSProc::synth: Error starting FreeTTS process. Is freetts.jar in the PATH?" << endl; + m_state = psIdle; + kdDebug() << "KProcess args: " << argsToQStringList(m_freettsProc->args()) << endl; + return; + } + kdDebug()<< "FreeTTSProc:synth: FreeTTS initialized" << endl; + m_freettsProc->writeStdin(saidText.latin1(), saidText.length()); +} + +/** + * Returning the filename of the synth'd text + * @returns The filename of the last synth'd text + */ +QString FreeTTSProc::getFilename() { + kdDebug() << "FreeTTSProc::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 FreeTTSProc::stopText() { + kdDebug() << "FreeTTSProc::stopText:: Running" << endl; + if (m_freettsProc) { + if (m_freettsProc->isRunning()) { + kdDebug() << "FreeTTSProc::stopText: killing FreeTTS." << endl; + m_waitingStop = true; + m_freettsProc->kill(); + } + else m_state = psIdle; + } + else m_state = psIdle; + kdDebug() << "FreeTTSProc::stopText: FreeTTS stopped." << endl; +} + +void FreeTTSProc::slotProcessExited(KProcess*) { + kdDebug() << "FreeTTSProc:slotProcessExited: FreeTTS 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 FreeTTSProc::slotReceivedStdout(KProcess*, char* buffer, int buflen) { + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "FreeTTSProc::slotReceivedStdout: Received output from FreeTTS: " << buf << endl; +} + +void FreeTTSProc::slotReceivedStderr(KProcess*, char* buffer, int buflen) { + QString buf = QString::fromLatin1(buffer, buflen); + kdDebug() << "FreeTTSProc::slotReceivedStderr: Received error from FreeTTS: " << buf << endl; +} + +void FreeTTSProc::slotWroteStdin(KProcess*) { + kdDebug() << "FreeTTSProc::slotWroteStdin: closing Stdin" << endl; + m_freettsProc->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 FreeTTSProc::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 FreeTTSProc::ackFinished() { + if (m_state == psFinished) { + m_state = psIdle; + m_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 FreeTTSProc::supportsAsync() { +// return true; + 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 FreeTTSProc::supportsSynth() { +// return true; + return true; +} + + +#include "freettsproc.moc" diff --git a/kttsd/plugins/freetts/freettsproc.h b/kttsd/plugins/freetts/freettsproc.h new file mode 100644 index 0000000..b2bb631 --- /dev/null +++ b/kttsd/plugins/freetts/freettsproc.h @@ -0,0 +1,180 @@ +/**************************************************************************** + Main speaking functions for the FreeTTS Plug in + ------------------- + Copyright : (C) 2004 Paul Giannaros + ------------------- + Original author: Paul Giannaros <ceruleanblaze@gmail.com> + Current Maintainer: Paul Giannaros <ceruleanblaze@gmail.com> + ******************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; version 2 of the License. * + * * + ***************************************************************************/ + +#ifndef _FREETTSPROC_H_ +#define _FREETTSPROC_H_ + + +#include <qstringlist.h> +#include <qmutex.h> + +#include <pluginproc.h> + +class KProcess; + +class FreeTTSProc : public PlugInProc{ + Q_OBJECT + +public: + /** + * Constructor + */ + FreeTTSProc( QObject* parent = 0, const char* name = 0, const QStringList &args = QStringList()); + + /** + * Destructor + */ + virtual ~FreeTTSProc(); + + /** + * Initializate the speech engine. + * @param config Settings object. + * @param configGroup Settings group. + */ + virtual 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 text The text to be synthesized. + * @param synthFilename If not Null, synthesize only to this filename, otherwise + * synthesize and audibilize the text. + * @param freettsJarPath Path to the freetts jar file. + */ + void synth( + const QString &text, + const QString &synthFilename, + const QString &freettsJarPath); + +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: + + /** + * Path to FreeTTS jar file (from config). + */ + QString m_freettsJarPath; + + /** + * FreeTTS process + */ + KProcess* m_freettsProc; + + /** + * Synthesis filename. + */ + QString m_synthFilename; + + /** + * Plugin state. + */ + pluginState m_state; + + /** + * True when stopText has been called. Used to force transition to psIdle when + * FreeTTS exits. + */ + bool m_waitingStop; + +}; + +#endif // _FREETTSPROC_H_ diff --git a/kttsd/plugins/freetts/kttsd_freettsplugin.desktop b/kttsd/plugins/freetts/kttsd_freettsplugin.desktop new file mode 100644 index 0000000..cdfab63 --- /dev/null +++ b/kttsd/plugins/freetts/kttsd_freettsplugin.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Name=FreeTTS +Name[tr]=Freetts +Name[vi]=Văn bản sang Tiếng nói Tự do +Comment=FreeTTS speech synthesizer +Comment[bg]=Синтезатор на глас FreeTTS +Comment[ca]=Sintetitzador de veu FreeTTS +Comment[cs]=Hlasový syntetizér FreeTTS +Comment[da]=FreeTTS tale-synthesizer +Comment[de]=FreeTTS Sprachsynthesizer +Comment[el]=Συνθέτης ομιλίας FreeTTS +Comment[es]=Sintetizador de texto a voz FreeTTS +Comment[et]=Kõnesüntesaator FreeTTS +Comment[eu]=FreeTTS hizketa-sintetizadorea +Comment[fa]=ترکیبدهندۀ گفتار FreeTTS +Comment[fi]=FreeTTS puhesyntetisaattori +Comment[fr]=Synthèse vocale FreeTTS +Comment[ga]=Sintéiseoir cainte FreeTTS +Comment[gl]=Sintetizador de voces FreeTTS +Comment[hu]=FreeTTS beszédszintetizátor +Comment[is]=FreeTTS talgerfill +Comment[it]=Sintetizzatore vocale FreeTTS +Comment[ja]=FreeTTS スピーチシンセサイザ +Comment[ka]=FreeTTS ხმის სინთეზატორი +Comment[km]=កម្មវិធីសង្គ្រោះការនិយាយ FreeTTS +Comment[mk]=FreeTTS синтетизатор на говор +Comment[ms]=Pensintesis tutur FreeTTS +Comment[nb]=FreeTTS talesyntetisering +Comment[nds]=Blicksnuut FreeTTS +Comment[ne]=FreeTTS संवाद सिन्थेसाइजर +Comment[nl]=FreeTTS spraaksynthesizer +Comment[pa]=FreeTTS ਬੋਲੀ ਸੰਸਲੇਸ਼ਕ +Comment[pl]=Syntezator mowy FreeTTS +Comment[pt]=O sintetizador de fala FreeTTS +Comment[pt_BR]=Sintetizador de fala FreeTTS +Comment[ru]=Синтезатор речи FreeTTS +Comment[sk]=Syntetizátor reči FreeTTS +Comment[sl]=Sintetizator govora FreeTTS +Comment[sr]=Синтетизатор говора FreeTTS +Comment[sr@Latn]=Sintetizator govora FreeTTS +Comment[sv]=FreeTTS talsyntes +Comment[ta]=FreeTTS பேச்சு கூட்டிணைப்பான் +Comment[tg]=Таҳлилгари овози FreeTTS +Comment[tr]=FreeTTS konuşma bireştirici +Comment[uk]=Синтезатор мовлення FreeTTS +Comment[vi]=Trình tổng hợp tiếng nói Tự do +Comment[zh_TW]=FreeTTS 語音合成器 +Type=Service +ServiceTypes=KTTSD/SynthPlugin +X-KDE-Library=libkttsd_freettsplugin +X-KDE-Languages=en,en_US,en_GB 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")); + } +} diff --git a/kttsd/sample.polish b/kttsd/sample.polish new file mode 100644 index 0000000..dfe51ec --- /dev/null +++ b/kttsd/sample.polish @@ -0,0 +1,8 @@ +Cze, nazywam si Mikoaj. +Odwiedz wasz wysp w przyszym tygodniu. +Pno ju jest. + +Gwiazdkowe prezenty mona otworzy dopiero pod choink, wic autorzy MPlayera +zaklinaj, eby opublikowane wczoraj rda nowej wersji trzyma w stanie +nienaruszonym i pod adnym pozorem nie odpakowywa ich do kompilacji wczeniej! +A pokusa jest spora... diff --git a/kttsd/sample.ssml b/kttsd/sample.ssml new file mode 100644 index 0000000..1971343 --- /dev/null +++ b/kttsd/sample.ssml @@ -0,0 +1 @@ +<speak><p><voice gender="male">Hello there.<prosody rate="x-slow">This is some extra slow text</prosody> and here <prosody pitch="high" rate="fast">is some fast high text</prosody></voice></p></speak> |