Currently, I'm using wine and Windows TTSApp.exe application for text-to-speech.

It is GUI application and works well with SAPI-5 voices. I will choose a text file with a few clicks and convert it to WAV file.

But I'd like to do something different as well.

I'd like to write a command-line script for conversion and run it something like follows:

wine ttsUtil.exe text.txt -voice=nick -output=speech.wav

Is this possible under wine? I need to use wine because my favorite voices run only under Windows. I'd like to use some ttsUtil.exe (name does not matter) instead of my GUI TTSApp.exe.

I really need to automate this task because I don't have time for every little text file to do so much clicking for the conversion.


My recommendation is to eliminate wine and use the Linux pico2wave program.

In Ubuntu 14.04 pico2wave is part of libttspico-utils

The command would look something like:

pico2wave --wave=test.wav "$(cat filename.txt)"
| improve this answer | |
  • Thank you. I knew about pico2wave. But I need to use voices that are Windows files. I can do it as above (with manual clicking) – xralf Feb 16 '15 at 19:09

If you are saying that the command you have works, you only need to automate it, then you have a few options:

If you have a directory of files that you want to convert, you can do something like this:


for i in "${ARG}"/* ;
wine ttsUtil.exe "${i}" -voice=nick -output="${i}".wav

Save that as a file (maybe call it ttsconvert.sh) and make it executable:

chmod +x ttsconvert.sh

Now you can run the script by providing it with the path to the directory of files that you want to convert:

./ttsconvert.sh ~/path/to/stash/of/files

If you want it on a file-by-file basis, you could create your own launcher using .desktop files.

For instance, create a file called ttsconvert.desktop:

[Desktop Entry]
Exec=wine ttsUtil.exe %U -voice=nick -output=speech.wav

As root, place this file to /usr/share/applications and you should then be able to use the Open With option of your right-click menu to open a text file with your new converter. It won't give you any notice that it is working; a fancier script could be written to use a GUI notification, but this isn't the place for all that yet.

| improve this answer | |
  • You work with abstract ttsUtil.exe and this is what I need to find. What works is a gui application TTSApp.exe – xralf Feb 21 '15 at 3:01

I would try with a SAPI5 commandline utility like: http://www.nirsoft.net/articles/speak_from_command_line.html

Try also: http://jampal.sourceforge.net/ptts.html

| improve this answer | |
  • For nirsoft: I can't find the command for specifying the voices I need. For jambal: I've stuck with JRE installation. If I would overcome this problem, ptts could be the way. – xralf Feb 21 '15 at 1:33

Have you seen this? It's a command line program and it seems to run under windows, so it will be easy to launch it in batch.

| improve this answer | |
  • When you install espeak in wine, you can run TTSApp.exe (the one I already use) and it offers me the voices I need. I can run command-line espeak.exe where you can list voices with --voices, but the voices I need aren't offered and I can't find out the command switch for them. I can specify for example wine espeak.exe -ven-us for us english but can't find the right switch for the other voices TTSApp.exe offers me. – xralf Feb 21 '15 at 1:31

This is a kludge but I got it working reliably. It scripts TTSApp.exe in a Xephyr window and simulates the mouse and keyboard input.

Install Ubuntu packages: xserver-xephyr metacity xdotool libav-tools

If you want something other than the default speech rate, add this to the beginning of each text file: <prosody rate="medium"><prosody rate="+36%"> and the corresponding close at the end: </prosody></prosody> (many more XML options in chapters 8-9 of AT&T Natural Voices System Developers Guide).

Replace i in key --delay 100 i in the script for key(s) needed in TTSApp.exe to select your preferred voice.

If you want to delete the source file upon successful conversion, uncomment the unlink() at the end of the script.

Run this script via something like:

find . -name 'chapter*.txt' -print0 |xargs -0 txt2ogg

Here is the Perl script txt2ogg (don't forget to chmod +x):

#!/usr/bin/perl -CS -w
use strict;
use warnings;
use utf8;

my $homeDir=$ENV{HOME};
$homeDir .= '/' if(substr($homeDir,length($homeDir)-1,1) ne '/');

my $oldDir = `pwd`;
$oldDir .= '/' if(substr($oldDir,length($oldDir)-1,1) ne '/');
chdir($homeDir) or die($!);
system( q(Xephyr :4 -screen 600x480 >/dev/null 2>/dev/null &) ); # using the user's display works until you try to get other work done or the screensaver starts
system( q(DISPLAY=:4 metacity >/dev/null 2>/dev/null &) ); # xdotool needs a window manager
foreach(@ARGV) {
  my $thisArg = $_;
  my $ttsIn = $oldDir.$thisArg; # make path absolute
  (my $ttsOut = $ttsIn) =~ s|\.[^\./]*$||; # strip file extension
  $ttsOut .= '.ogg';
  my $attempt = 0;
  my $errorCodes = ""; # list of codes for recoverable errors
  my $closeDialogCmd = q(export DISPLAY=:4; xdotool search --name "File Saved" windowactivate --sync %@ key space 2>/dev/null);
  my $ExitCmd = q(export DISPLAY=:4; xdotool search --name "SAPI5 TTSAPP" windowactivate --sync %@ windowkill 2>/dev/null);
  while(1) {
    print("\r$thisArg ... ");
    symlink($ttsIn,"ttsin") or die($!);
    #xdotool is sometimes too fast, even with ''--delay 100'', so BackSpace makes sure the full name gets entered
    my $stallLimit = 10;
    my $seconds = 0;
    my $priorWavSize = 0;
    my $stalledTime = 0;
    my $wavSize = 0;
    #start TTSApp.exe in the background
    system( q(DISPLAY=:4 wine "C:\Program Files\eSpeak\TTSApp.exe" 2>/dev/null >/dev/null &) );
    #in TTSApp.exe, enable XML; select proper voice; open "ttsin"; and save as "ttsout.wav"
    system( q(export DISPLAY=:4; xdotool search --sync --name "SAPI5 TTSAPP" windowactivate --sync %@ mousemove --window %@ 36 339 click 1 mousemove --window %@ 426 233 click 1 key --delay 100 i mousemove --window %@ 500 37 click 1 key --delay 100 BackSpace BackSpace t t s i n Return mousemove --window %@ 500 288 click 1 key --delay 100 BackSpace BackSpace t t s o u t Return 2>/dev/null >/dev/null) );
    while(1) { # wait for "File Saved" dialog
      $seconds += 2;
      #check if "File Saved" dialog exists yet
      last if(system( q(export DISPLAY=:4; xdotool search --name "File Saved" >/dev/null) ) == 0);
      my $wavSizeCmd = q(stat --printf '%s' ttsout.wav 2>/dev/null);
      $wavSize = `$wavSizeCmd`;
      $wavSize = 0 if(!defined($wavSize) or length($wavSize) == 0);
      if($wavSize <= $priorWavSize) {
        $stalledTime += 2;
        if($stalledTime >= $stallLimit) {
          $errorCodes .= " 282"; # TTSApp.exe not responding
          if(system($ExitCmd) != 0) { # kill TTSApp.exe and try again
            $errorCodes .= " 443"; # TTSApp.exe still not responding
      } else {
        $stalledTime = 0;
      $priorWavSize = $wavSize;
      print("\r$thisArg ...$wavSize bytes");
    if(($stalledTime < $stallLimit)) { # above loop not stalled
      if($wavSize == 11639) {
        $errorCodes .= " 639"; # size of .wav is exactly the size for "Enter text you whish spoken here" in the default voice
      } else {
        last; # success
    if($attempt++ >= 5) {
      die("unable to process file with TTSApp.exe");
  #close "File Saved" dialog and exit TTSApp.exe
  if(system($closeDialogCmd) != 0) {
    $errorCodes .= " 934"; # closing dialog failed
    if(system($closeDialogCmd) != 0) {
      $errorCodes .= " 818"; # closing dialog failed again
  if(system($ExitCmd) != 0) {
    $errorCodes .= " 245"; # closing TTSApp.exe failed
    if(system($ExitCmd) != 0) {
      $errorCodes .= " 871"; # closing TTSApp.exe failed again
  print("\r$thisArg ... converting to .ogg  ");
  #''-qscale 0'' (24Kbps) has noticable whisper-like overtones and ''1'' (30Kbps) and ''2'' (35Kbps) are quite close, so I decided on ''-qscale 1''
  system('cat ttsout.wav |avconv -i pipe:0 -codec:a libvorbis -qscale 1 ttsout.ogg 2>/dev/null >/dev/null') == 0 or die($!);
  rename("ttsout.ogg",$ttsOut) or die($!);
  if(length($errorCodes) == 0) {
    print("\r$thisArg ... done                \n");
  } else {
    print("\r$thisArg ... done (recovered from: $errorCodes)            \n");
  #unlink($ttsIn); # delete original only after .ogg is in place
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.