#!/usr/bin/env python2 # coding: utf8 import datetime import logging import os import re import sys import tempfile import traceback from asterisk import agi from optparse import OptionParser from picotts import PicoTTS from helpers import playback, enable_simulate_mode, get_var, get_env_var, set_var, hangup, check_answered, beep, record_file default_logfile = '/var/log/asterisk/record_message.agi.log' default_lang = 'fr-FR' default_intkey = "any" default_speed = 1.1 default_cachedir = '/var/cache/asterisk/picotts' default_outputdir = '/var/lib/asterisk/sounds/records' default_fileformat = 'wav' default_nameformat = '{datetime}-{calleridname}' default_endmessage = u"Au revoir" ####### # RUN # ####### # Options parser parser = OptionParser() parser.add_option('-d', '--debug', action="store_true", dest="debug", help="Enable debug mode") parser.add_option('-v', '--verbose', action="store_true", dest="verbose", help="Enable verbose mode") parser.add_option('--simulate', action="store_true", dest="simulate", help="Simulate AGI mode") parser.add_option('--simulate-play', action="store_true", dest="simulate_play", help="Simulate mode : play file using mplayer") parser.add_option('--simulate-record', action="store_true", dest="simulate_record", help="Simulate mode : record file using arecord") parser.add_option('-L', '--log-file', action="store", type="string", dest="logfile", default=default_logfile, help="pico2wave path (Default : %s)" % default_logfile) parser.add_option('-l', '--lang', action="store", type="string", dest="lang", default=default_lang, help="Language (Default : %s)" % default_lang) parser.add_option('-i', '--intkey', action="store", type="string", dest="intkey", default=default_intkey, help="Interrupt key(s) (Default : Any)") parser.add_option('-s', '--speed', action="store", type="float", dest="speed", default=default_speed, help="Speed factor (Default : %i)" % default_speed) parser.add_option('-S', '--sample-rate', action="store", type="int", dest="samplerate", help="Sample rate (Default : auto-detect)") parser.add_option('-c', '--cache', action="store_true", dest="cache", help="Enable cache") parser.add_option('-C', '--cache-dir', action="store", type="string", dest="cachedir", default=default_cachedir, help="Cache directory path (Default : %s)" % default_cachedir) parser.add_option('--sox-path', action="store", type="string", dest="sox_path", help="sox path (Default : auto-detec in PATH)") parser.add_option('--pico2wave-path', action="store", type="string", dest="pico2wave_path", help="pico2wave path (Default : auto-detec in PATH)") parser.add_option('-f', '--format', action="store", type="string", dest="fileformat", default=default_fileformat, help="Output file format (Default : %s)" % default_fileformat) parser.add_option('-O', '--output-dir', action="store", type="string", dest="outputdir", default=default_outputdir, help="Output directory path (Default : %s)" % default_outputdir) parser.add_option('-n', '--name-format', action="store", type="string", dest="nameformat", default=default_nameformat, help="Record file name format composed using channel variables and some specials variables : 'date', 'datetime', 'callerid', 'calleridname' (Default : %s)" % default_outputdir) parser.add_option('-m', '--max-duration', action="store", type="int", dest="maxduration", default=-1, help="Max duration (in seconds, Default : no limit)") parser.add_option('-e', '--end-message', action="store", type="string", dest="endmessage", default=default_endmessage, help="End message that will be read to user at the end (Default : %s)" % default_endmessage) (options, args) = parser.parse_args() # Enable logs logformat = '%(levelname)s - %(message)s' if options.simulate: logging.basicConfig(format=logformat, level=logging.DEBUG) enable_simulate_mode() else: if options.debug: loglevel = logging.DEBUG elif options.verbose: loglevel = logging.INFO else: loglevel = logging.WARNING logging.basicConfig(filename=options.logfile, level=loglevel, format=logformat) # Valid intkey parameter if options.intkey != "any" and not re.match('^[0-9#*]*$', options.intkey): logging.warning('Invalid interrupt key(s) provided ("%s"), use any.' % options.intkey) options.intkey = "any" if options.speed <= 0: logging.warning('Invalid speed provided, use default') options.speed=default_speed logging.debug('Call parameters (lang = {lang}, intkey = {intkey}, speed = {speed} and output dir/format :\n{outputdir}/{nameformat}'.format( lang=options.lang, intkey=options.intkey, speed=options.speed, outputdir=options.outputdir, nameformat=options.nameformat) ) ############# # Functions # ############# def clean_exit(exit_code): if 'picotts' in globals(): global picotts picotts.clean_tmp() global asterisk_agi hangup(asterisk_agi) sys.exit(exit_code) def play_msg(msg): global asterisk_agi, options filepath = picotts.getAudioFile(msg) # Playback message logging.debug('Play file %s' % filepath) playback(asterisk_agi, filepath, simulate_play=options.simulate_play, intkey=options.intkey) def play_file(filepath): global asterisk_agi, options logging.debug('Play %s file', filepath) playback(asterisk_agi, filepath, simulate_play=options.simulate_play, intkey=options.intkey) def play_msg_and_hangup(msg=None, exit_code=1): global asterisk_agi if not msg: msg = u"Une erreur empêche ce service de fonctionner correctement. Si le problème persiste, merci de prendre contact avec le support." play_msg(msg) clean_exit(exit_code) try: picotts_args = {} # Start Asterisk AGI client if not options.simulate: asterisk_agi = agi.AGI() picotts_args['asterisk_agi'] = asterisk_agi else: asterisk_agi = None if options.cache: picotts_args['cachedir']=options.cachedir # Start PicoTTS engine picotts = PicoTTS(lang = options.lang, speed=options.speed, **picotts_args) # Check output directory if not os.path.isdir(options.outputdir): logging.error("Ouput directory '%s' doesn't exists !", options.outputdir) play_msg_and_hangup() # Compose output file name and path variables = {} for var in re.findall('\{([^\}]+)\}', options.nameformat): if var == 'date': value = datetime.datetime.now().strftime('%Y-%m-%d') elif var == 'datetime': value = datetime.datetime.now().strftime('%Y-%m-%d-%Hh%Mm%Ss') elif var in ['callerid', 'calleridname']: value = get_env_var(asterisk_agi, 'agi_%s' % var) else: value = get_var(asterisk_agi, var) logging.debug('%s = "%s"', var, value) variables[var] = value filename = options.nameformat.format(**variables).replace(' ', '-') logging.debug(u"Output file name : '%s'", filename) filepath = "{0}/{1}".format(options.outputdir, filename) logging.debug(u"Output file path : '%s'", filename) # Check destination file does not already exist if os.path.exists(filepath): logging.warning("Output file '%s' already exists !", filepath) play_msg_and_hangup() # Say hello :) play_msg(u"Bonjour et bienvenue sur le service d'enregistrement de messages") # Check call is answered check_answered(asterisk_agi) # Intro play_msg(u"Vous allez pouvoir enregistrer un message après le bip sonore. Une fois votre message enregistré, appuyer sur la touche dièse pour terminer l'enregistrement.") # Record file record_file(asterisk_agi, filepath, options.fileformat, escape_digits='#', max_duration=options.maxduration, simulate_record=options.simulate_record) beep(asterisk_agi) # Check destination file now exist full_filepath = "{0}.{1}".format(filepath, options.fileformat.lower()) if not os.path.exists(full_filepath): logging.warning("Output file '%s' does not exists after the record !", full_filepath) play_msg_and_hangup(u"Une erreur est survenue durant l'enregistrement de votre message. Si le problème persiste, merci de prendre contact avec le support.") # Replay message to the caller play_msg(u"Voilà le message que vous avez enregistré :") beep(asterisk_agi) play_file(filepath) beep(asterisk_agi) if options.endmessage: play_msg(options.endmessage) clean_exit(0) except agi.AGIAppError: logging.info('An AGI error stop script', exc_info=True) play_msg_and_hangup() except Exception: logging.error('Unexcepted error occured', exc_info=True) play_msg_and_hangup(exit_code=1)