diff --git a/scripts/py_modules/__init__.py b/scripts/py_modules/__init__.py new file mode 100644 index 0000000000..1bb8bf6d7f --- /dev/null +++ b/scripts/py_modules/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/scripts/py_modules/speechtools.py b/scripts/py_modules/speechtools.py new file mode 100644 index 0000000000..88e030db9b --- /dev/null +++ b/scripts/py_modules/speechtools.py @@ -0,0 +1,198 @@ +from freeswitch import * +from xml.dom import minidom + +VOICE_ENGINE = "cepstral" +VOICE = "William" + +""" +A few classes that make it easier to write speech applications +using Python. It is roughly modelled after the equivalent that +is written in JavaScript. + +Status: should work, but not yet complete. some pending items +are mentioned in comments +""" + +class Grammar: + def __init__(self, name, path, obj_path, + min_score=1, confirm_score=400, halt=False): + """ + @param name - name of grammar to reference it later + @param path - path to xml grammar file + @param obj_path - xml path to find interpretation from root + in result xml, eg, 'interpretation' + @param min_score - score threshold to accept result + @param confirm_score - if score below this threshold, ask user + if they are sure this is correct + @param halt - not sure what was used for in js, currently unused + """ + self.name=name + self.path=path + self.obj_path=obj_path + self.min_score=min_score + self.confirm_score=confirm_score + self.halt=halt + + +class SpeechDetect: + + def __init__(self, session, module_name, ip_addr): + self.session=session + self.module_name=module_name + self.ip_addr=ip_addr + self.grammars = {} + + def addGrammar(self, grammar): + self.grammars[grammar.name]=grammar + + def setGrammar(self, name): + self.grammar = self.grammars[name] + + def detectSpeech(self): + # TODO: we might not always want to call detect_speech + # with this cmd, see js version for other options + # also see detect_speech_function() in mod_dptools.c + cmd = "%s %s %s %s" % (self.module_name, + self.grammar.name, + self.grammar.path, + self.ip_addr) + console_log("debug", "calling detect_speech with: %s\n" % cmd) + self.session.execute("detect_speech", cmd) + console_log("debug", "finished calling detect_speech\n") + +class SpeechObtainer: + + def __init__(self, speech_detect, required_phrases, wait_time, max_tries): + """ + @param speech_detect - the speech detect object, which holds a + reference to underlying session and can + be re-used by many SpeechObtainers + @param required_phrases - the number of required phrases from the + grammar. for example if its prompting for + the toppings on a sandwhich and min toppings + is 3, use 3. normally will be 1. + @param wait_time - the time, in millisconds, to wait for + input during each loop iteration + @param max_tries - this number multiplied by wait time gives the + 'total wait time' before we give up and return + partial or no result + """ + self.speech_detect=speech_detect + self.required_phrases=required_phrases + self.wait_time=wait_time + self.max_tries=max_tries + + self.detected_phrases = [] + + def setGrammar(self, grammar): + """ + @param grammar - instance of grammar class + """ + self.grammar=grammar + self.speech_detect.addGrammar(grammar) + self.speech_detect.setGrammar(self.grammar.name) + + def detectSpeech(self): + self.speech_detect.detectSpeech() + + def run(self): + """ + start speech detection with the current grammar, + and listen for results from asr engine. once a result + has been returned, return it to caller + """ + + def dtmf_handler(input, itype, funcargs): + console_log("INFO","\n\nDTMF itype: %s\n" % itype) + if itype == 1: # TODO!! use names for comparison instead of number + return self.handle_event(input, funcargs) + elif itype== 0: + console_log("INFO","\n\nDTMF input: %s\n" % input) + else: + console_log("INFO","\n\nUnknown input type: %s\n" % itype) + return None + + + num_tries = 0 + + session = self.speech_detect.session + + console_log("debug", "setting dtmf callback\n") + session.setDTMFCallback(dtmf_handler, "") + console_log("debug", "calling getDigits\n") + + console_log("debug", "starting run() while loop\n") + while (session.ready() and + num_tries < self.max_tries and + len(self.detected_phrases) < self.required_phrases): + console_log("debug", "top of run() while loop\n") + session.collectDigits(self.wait_time) + num_tries += 1 + + console_log("debug", "while loop finished\n") + return self.detected_phrases + + def handle_event(self, event, funcargs): + """ + when the dtmf handler receives an event, it calls back + this method. event is a dictionary with subdictionaries .. + + Example 1 + ========= + + {'body': None, 'headers': {'Speech-Type': 'begin-speaking'}} + + Example 2 + ========= + {'body': ' + waffles', + 'headers': {'Speech-Type': 'detected-speech'}} + + This dictionary is constructed in run_dtmf_callback() in + freeswitch_python.cpp + + """ + + # what kind of event? + headers = event['headers'] + speech_type = headers['Speech-Type'] + if speech_type == "begin-speaking": + # not sure what to do with this, try returning "stop" + # so that it might stop playing a sound file once + # speech has been detected + return "stop" + elif speech_type == "detected-speech": + # extract the detected phrase. from result + # BUG: this assumes only ONE interpretation in the xml + # result. rest will get igored + # NOTE: have to wrap everything with str() (at least + # calls to console_log because otherwise it chokes on + # unicode strings. + # TODO: check the score + body = event['body'] + dom = minidom.parseString(body) + phrase = dom.getElementsByTagName(self.grammar.obj_path)[0] + phrase_text = self.getText(phrase) + if phrase_text: + self.detected_phrases.append(str(phrase_text)) + # do we want to return stop? what should we return? + return "stop" + else: + raise Exception("Unknown speech event: %s" % speech_type) + + + def getText(self, elt): + + """ given an element, get its text. if there is more than + one text node child, just append all the text together. + """ + + result = "" + children = elt.childNodes + for child in children: + if child.nodeType == child.TEXT_NODE: + result += str(child.nodeValue) + return result + diff --git a/scripts/recipewizard.py b/scripts/recipewizard.py new file mode 100644 index 0000000000..797a381f3f --- /dev/null +++ b/scripts/recipewizard.py @@ -0,0 +1,87 @@ +from freeswitch import * +from py_modules.speechtools import Grammar, SpeechDetect +from py_modules.speechtools import SpeechObtainer + +import time, os + +VOICE_ENGINE = "cepstral" +VOICE = "William" +GRAMMAR_ROOT = "/usr/src/freeswitch_trunk/scripts" + +""" +Example speech recognition application in python. + +How to make this work: + +* Get mod_openmrcp working along with an MRCP asr server +* Add /usr/src/freeswitch/scripts or equivalent to your PYTHONPATH +* Restart freeswitch +* Create $GRAMMAR_ROOT/mainmenu.xml from contents in mainmenu() comments + +""" + +class RecipeWizard: + + def __init__(self, session): + self.session=session + self.session.set_tts_parms(VOICE_ENGINE, VOICE) + self.main() + + def main(self): + + console_log("debug", "recipe wizard main()\n") + self.speechdetect = SpeechDetect(self.session, "openmrcp", "127.0.0.1"); + self.speechobtainer = SpeechObtainer(speech_detect=self.speechdetect, + required_phrases=1, + wait_time=5000, + max_tries=3) + gfile = os.path.join(GRAMMAR_ROOT, "mainmenu.xml") + self.grammar = Grammar("mainmenu", gfile,"input",80,90) + self.speechobtainer.setGrammar(self.grammar); + console_log("debug", "calling speechobtainer.run()\n") + self.speechobtainer.detectSpeech() + self.session.speak("Hello. Welcome to the recipe wizard. Drinks or food?") + result = self.speechobtainer.run() + console_log("debug", "speechobtainer.run() result: %s\n" % result) + if result: + self.session.speak("Received result. Result is: %s" % result[0]) + else: + self.session.speak("Sorry, I did not hear you") + + console_log("debug", "speechobtainer.run() finished\n") + +def mainmenu(): + """ + + + + + + + + + + drinks + food + + + + + + + + """ + pass + +def handler(uuid): + session = PySession(uuid) + session.answer() + rw = RecipeWizard(session) + session.hangup("1") + +