#!/usr/bin/python # mwsynth - Network speech synthesis in Python # (C) 2001 Rupert Scammell # MWSynth Project Page: http://hobbiton.thisside.net/mwsynth # # 1.0 2001-08-20: Initial version. # 2.0 2001-09-07: Modularize code, add interactive function. # 2004-06-20: Update contact information. No version change. """ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ import os, sys, urllib, string, re, fileinput, time #### CONFIGURATION OPTIONS #### # Change these for your environment!!! # Print debug messages? debug_flag = 1 # Directory to save the retrieved sound files in. save_dir = '/home/rupe/mw/' # Playlist file: pl_file = '/home/rupe/mw/playlist.m3u' # WAV file player (this is only needed if you want to use the !speak or !speak_file commands in # interactive mode). I use dreamplayer, which is available via Source Forge. # Check out the play_file() function for more information about how this string gets used. # Dreamplayer takes a WAV file as the first argument on the command line, so if your player does # differently, you'll have to tweak the function to make this work. wav_player = "/usr/bin/dreamplay" ################################ # Default voice gender voice_gender = 'female' # RPL "Refresh PlayList" determines if we clear out the playlist file # with each new output in interactive mode. It has no bearing on # non-interactive mode. On by default, since this is probably # how most people expect the app to behave. rpl_flag = 'on' # How long to wait in seconds between speaking each word: word_wait = 0.0 # Base url information (no need to change) base_url = 'http://www.m-w.com/cgi-bin/dictionary?va=' base_url_2 = 'http://www.m-w.com/' # Regexes for the first and second HTML pages we retrieve. me_grab = re.compile('(Main Entry\:)(.*)(\'.*\')') cheetah = re.compile('"(http\:\/\/.*\.eb\.com.*)"(.*)') # Regexes to parse interactive requests to synthesize speech from file. int_file = re.compile('(\!file )(.*)') speak_line = re.compile('(\!speak )(.*)') speak_file = re.compile('(\!speak_file )(.*)') word_int = re.compile('(\!word_interval )(.*)') rpl_mode = re.compile('(\!rplaylist )(.*)') voice_gen = re.compile('(\!voice )(.*)') # Set up a small translation table that helps strip punctuation, etc # from the input string: from_list = ',.!?:;[]()-+="\'~`|_{}<>@#$%^&*' to_list = '' for i in range(len(from_list)): to_list = to_list + ' ' ttbl = string.maketrans(from_list, to_list) # Set up mappings to go from cougar -> cheetah, # and vice versa. 'cougar' and 'cheetah' are the servers # used to provide the speech wav files. cougar has all # female voices, cheetah all male, and the m-w.com system # load balances between these. However, in order to get # consistently gendered voice to read speech back, we # can search and replace on the returned server URL strings. female = 'cougar' male = 'cheetah' # Create a file handle for the playlist file: plf = open(pl_file,'w') # Set the WAV file index variable to 0. x = 0 # Take in a string, and if the global variable debug_flag is 1, # print the string to stdout. def debug(dstr): if debug_flag == 1: print dstr # Preprocess a line of speech text. def preproc_speech(spline): # RS, 2001-09-07 # Remove string.lower(line) modification. # Accuracy better with original capitalization intact. speech_string = string.translate(spline, ttbl) speech_token = string.split(speech_string) debug(str(speech_token)) return speech_token # This is the equivalent of the 'else' clause in the interactive mode, # below, but speak_it() gets called on the completed playlist when the synth # task is done. def speak_string(speech_string): splist = preproc_speech(speech_string) synth_line(splist) speak_it() # This is the equivalent of the !file foo_bar clause in the interactive mode, # below, but speak_it() gets called on the completed playlist when the synth # task is done. def speak_the_file(sp_file): dstr = 'Synthesizing ' + sp_file debug(dstr) synth_file(sp_file) speak_it() # This function takes in a WAV filename, builds a command, plays the file, and waits a moment. def play_file(wav_filename): cmd_form = wav_player + ' ' + string.strip(wav_filename) os.system(cmd_form) time.sleep(word_wait) # This function does the actual task of iterating through entries in a playlist, # and speaking each of the files, with a slight delay between them. def speak_it(): for line in fileinput.input([pl_file]): play_file(line) # Synthesize a list of speech words. def synth_line(speech_token): global x i = 0 while (i < len(speech_token)): speech_token[i] = base_url + speech_token[i] debug(speech_token[i]) for line in urllib.urlopen(speech_token[i]).readlines(): if (me_grab.search(line) != None): debug('Got a main entry line in source file.') http_ref = base_url_2 + me_grab.search(line).group(3)[1:-1] debug(http_ref) fname = save_dir + str(x) + '.wav' playlist_name = fname + '\n' plf.write(playlist_name) plf.flush() debug(fname) for line in urllib.urlopen(http_ref).readlines(): #debug(line) if (cheetah.search(line) != None): debug('Found cheetah server reference.') csvr = cheetah.search(line).group(1) if (voice_gender == 'female'): csvr = string.replace(csvr, male, female) if (voice_gender == 'male'): csvr = string.replace(csvr, female, male) debug(csvr) urllib.urlretrieve(csvr, fname) debug('File retrieved.') break i = i + 1 x = x + 1 # Sythesize a whole file. def synth_file(sp_filename): for line in fileinput.input([sp_filename]): stok = preproc_speech(line) synth_line(stok) # This clears out the entries currently in the playlist and starts over. def clear_playlist(): global plf if (rpl_flag == 'on'): plf.close() plf = open(pl_file, 'w') if (len(sys.argv) == 1): # Assume interactive mode if we're not passed a command line argument. intro_text = """ mwsynth 2.0 (C) 2001 Rupert Scammell GNU General Public License. Interactive mode. !help for commands. """ help_text = """ : synthesized and put in playlist. !quit : exits program. !help : prints this help screen. !file : filename synthesizes text from file. !voice m|f|n : Voice gender. m=male, f=female, n=mixed !speak string : speaks the string (no quotes, spaces allowed). !speak_file filename : speaks the text in file filename. !debug on|off : turns on and off debug output. !speak_playlist : Speaks the current contents of the playlist. !word_interval s : changes pause interval (in secs) between words. !rplaylist on|off : if on, playlist is cleared for each output. !clear_playlist : clears whatever is currently in the playlist. """ print intro_text while 1: speech_string = raw_input("speech> ") if (len(speech_string) > 0): # Turn on and off debug text. if (speech_string == '!debug on'): debug_flag = 1 print 'Debug mode on.' if (speech_string == '!debug off'): debug_flag = 0 print 'Debug mode off.' # Print help text. if (speech_string == '!help'): print help_text # Quit if we see !quit if (speech_string == '!quit'): print 'Goodbye!' sys.exit() # Change voice gender. if (voice_gen.search(speech_string) != None): vgen_arg = voice_gen.search(speech_string).group(2) if (vgen_arg == 'm'): print 'Using male voice now.' voice_gender = 'male' if (vgen_arg == 'f'): print 'Using female voice now.' voice_gender = 'female' if (vgen_arg == 'n'): print 'Using mixed (random) voice now.' voice_gender = 'mixed' # Clears whatever is currently in the playlist, without # changing the value of the refresh playlist flag. if (speech_string == '!clear_playlist'): clear_playlist() print 'Playlist cleared.' # If we detect a line starting with !rplaylist, we # look for an 'on' or 'off' to follow. # If on, we clear the playlist each time new speech # phrases are generated. # If off, the playlist grows with each output added. if (rpl_mode.search(speech_string) != None): rpl_old = rpl_flag rpl_f = rpl_mode.search(speech_string).group(2) if (rpl_f == 'on'): rpl_flag = rpl_f print 'Playlist will be cleared for each output.' clear_playlist() if (rpl_f == 'off'): rpl_flag = rpl_f print 'Playlist will not be cleared for each output.' if (rpl_f == None): print 'RPL flag is currently: ', rpl_flag # If we detect a line in format "!word_interval spam", use # spam as a float to pass to the variable word_wait, # which determines how long we pause between each word. if (word_int.search(speech_string) != None): print 'Old word interval: ', str(word_wait) try: ww = word_int.search(speech_string).group(2) word_wait = float(ww) print 'New word interval: ', str(word_wait) except: print 'Sorry. Could not change interval.' # Speaks whatever is currently in the playlist. if (speech_string == '!speak_playlist'): speak_it() # If we detect a line in format "!file spam", use spam # as a filename for speech input. if (int_file.search(speech_string) != None): sp_file = int_file.search(speech_string).group(2) dstr = 'Synthesizing ' + sp_file debug(dstr) synth_file(sp_file) clear_playlist() # If we detect a line in the format "!speak spam eggs", use # spam eggs as a string to speak using a WAV player. if (speak_line.search(speech_string) != None): speech_str = speak_line.search(speech_string).group(2) speak_string(speech_str) clear_playlist() # If we detect a line in the format "!speak_file spam", use # spam as a file to take speech input from, then speak using a WAV # player. if (speak_file.search(speech_string) != None): speech_file = speak_file.search(speech_string).group(2) speak_the_file(speech_file) clear_playlist() if (speech_string[0] != '!'): splist = preproc_speech(speech_string) synth_line(splist) clear_playlist() # If we're started with a command line argument, pass it as a filename # to speak_file() to be synthesized. synth_file(sys.argv[1])