@ -44,6 +44,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
# include "asterisk/options.h"
# include "asterisk/app.h"
# include "asterisk/cli.h"
# include "asterisk/localtime.h"
# include "asterisk/say.h"
static char * tdesc = " Sound File Playback Application " ;
static char * app = " Playback " ;
@ -68,12 +72,322 @@ static char *descrip =
LOCAL_USER_DECL ;
static struct ast_config * say_cfg ;
/* save the say' api calls.
* The first entry is NULL if we have the standard source ,
* otherwise we are sourcing from here .
* ' say load [ new | old ] ' will enable the new or old method , or report status
*/
static const void * say_api_buf [ 40 ] ;
static const char * say_old = " old " ;
static const char * say_new = " new " ;
static void save_say_mode ( const void * arg )
{
int i = 0 ;
say_api_buf [ i + + ] = arg ;
say_api_buf [ i + + ] = ast_say_number_full ;
say_api_buf [ i + + ] = ast_say_enumeration_full ;
say_api_buf [ i + + ] = ast_say_digit_str_full ;
say_api_buf [ i + + ] = ast_say_character_str_full ;
say_api_buf [ i + + ] = ast_say_phonetic_str_full ;
say_api_buf [ i + + ] = ast_say_datetime ;
say_api_buf [ i + + ] = ast_say_time ;
say_api_buf [ i + + ] = ast_say_date ;
say_api_buf [ i + + ] = ast_say_datetime_from_now ;
say_api_buf [ i + + ] = ast_say_date_with_format ;
}
static void restore_say_mode ( void * arg )
{
int i = 0 ;
say_api_buf [ i + + ] = arg ;
ast_say_number_full = say_api_buf [ i + + ] ;
ast_say_enumeration_full = say_api_buf [ i + + ] ;
ast_say_digit_str_full = say_api_buf [ i + + ] ;
ast_say_character_str_full = say_api_buf [ i + + ] ;
ast_say_phonetic_str_full = say_api_buf [ i + + ] ;
ast_say_datetime = say_api_buf [ i + + ] ;
ast_say_time = say_api_buf [ i + + ] ;
ast_say_date = say_api_buf [ i + + ] ;
ast_say_datetime_from_now = say_api_buf [ i + + ] ;
ast_say_date_with_format = say_api_buf [ i + + ] ;
}
/*
* Typical ' say ' arguments in addition to the date or number or string
* to say . We do not include ' options ' because they may be different
* in recursive calls , and so they are better left as an external
* parameter .
*/
typedef struct {
struct ast_channel * chan ;
const char * ints ;
const char * language ;
int audiofd ;
int ctrlfd ;
} say_args_t ;
static int s_streamwait3 ( const say_args_t * a , const char * fn )
{
int res = ast_streamfile ( a - > chan , fn , a - > language ) ;
if ( res ) {
ast_log ( LOG_WARNING , " Unable to play message %s \n " , fn ) ;
return res ;
}
res = ( a - > audiofd > - 1 & & a - > ctrlfd > - 1 ) ?
ast_waitstream_full ( a - > chan , a - > ints , a - > audiofd , a - > ctrlfd ) :
ast_waitstream ( a - > chan , a - > ints ) ;
ast_stopstream ( a - > chan ) ;
return res ;
}
/*
* the string is ' prefix : data ' or prefix : fmt : data '
* with ' : ' being invalid in strings .
*/
static int do_say ( say_args_t * a , const char * s , const char * options , int depth )
{
struct ast_variable * v ;
char * lang , * x , * rule = NULL ;
int ret = 0 ;
struct varshead head = { . first = NULL , . last = NULL } ;
struct ast_var_t * n ;
ast_log ( LOG_WARNING , " string <%s> depth <%d> \n " , s , depth ) ;
if ( depth + + > 10 ) {
ast_log ( LOG_WARNING , " recursion too deep, exiting \n " ) ;
return - 1 ;
}
/* scan languages same as in file.c */
if ( a - > language = = NULL )
a - > language = " en " ; /* default */
ast_log ( LOG_WARNING , " try <%s> in <%s> \n " , s , a - > language ) ;
lang = ast_strdupa ( a - > language ) ;
if ( ! lang ) /* no memory! */
return - 1 ;
for ( ; ; ) {
for ( v = ast_variable_browse ( say_cfg , lang ) ; v ; v = v - > next ) {
if ( ast_extension_match ( v - > name , s ) ) {
rule = ast_strdupa ( v - > value ) ;
break ;
}
}
if ( rule )
break ;
if ( ( x = strchr ( lang , ' _ ' ) ) )
* x = ' \0 ' ; /* try without suffix */
else if ( strcmp ( lang , " en " ) )
lang = " en " ; /* last resort, try 'en' if not done yet */
else
break ;
}
if ( ! rule )
return 0 ;
/* skip up to two prefixes to get the value */
if ( ( x = strchr ( s , ' : ' ) ) )
s = x + 1 ;
if ( ( x = strchr ( s , ' : ' ) ) )
s = x + 1 ;
ast_log ( LOG_WARNING , " value is <%s> \n " , s ) ;
n = ast_var_assign ( " SAY " , s ) ;
AST_LIST_INSERT_HEAD ( & head , n , entries ) ;
/* scan the body, one piece at a time */
while ( ret < = 0 & & ( x = strsep ( & rule , " , " ) ) ) { /* exit on key */
char fn [ 128 ] ;
const char * p , * fmt , * data ; /* format and data pointers */
/* prepare a decent file name */
x = ast_skip_blanks ( x ) ;
ast_trim_blanks ( x ) ;
/* replace variables */
memset ( fn , 0 , sizeof ( fn ) ) ; /* XXX why isn't done in pbx_substitute_variables_helper! */
pbx_substitute_variables_varshead ( & head , x , fn , sizeof ( fn ) ) ;
ast_log ( LOG_WARNING , " doing [%s] \n " , fn ) ;
/* locate prefix and data, if any */
fmt = index ( fn , ' : ' ) ;
if ( ! fmt | | fmt = = fn ) { /* regular filename */
ret = s_streamwait3 ( a , fn ) ;
continue ;
}
fmt + + ;
data = index ( fmt , ' : ' ) ; /* colon before data */
if ( ! data | | data = = fmt ) { /* simple prefix-fmt */
ret = do_say ( a , fn , options , depth ) ;
continue ;
}
/* prefix:fmt:data */
for ( p = fmt ; p < data & & ret < = 0 ; p + + ) {
char fn2 [ 128 ] ;
if ( * p = = ' ' | | * p = = ' \t ' ) /* skip blanks */
continue ;
if ( * p = = ' \' ' ) { /* file name - we trim them */
char * y ;
strcpy ( fn2 , ast_skip_blanks ( p + 1 ) ) ; /* make a full copy */
y = index ( fn2 , ' \' ' ) ;
if ( ! y ) {
p = data ; /* invalid. prepare to end */
break ;
}
* y = ' \0 ' ;
ast_trim_blanks ( fn2 ) ;
p = index ( p + 1 , ' \' ' ) ;
ret = s_streamwait3 ( a , fn2 ) ;
} else {
int l = fmt - fn ;
strcpy ( fn2 , fn ) ; /* copy everything */
/* after prefix, append the format */
fn2 [ l + + ] = * p ;
strcpy ( fn2 + l , data ) ;
ret = do_say ( a , fn2 , options , depth ) ;
}
}
}
ast_var_delete ( n ) ;
return ret ;
}
static int say_full ( struct ast_channel * chan , const char * string ,
const char * ints , const char * lang , const char * options ,
int audiofd , int ctrlfd )
{
say_args_t a = { chan , ints , lang , audiofd , ctrlfd } ;
if ( ! say_cfg ) {
ast_log ( LOG_WARNING , " no say.conf, cannot spell '%s' \n " , string ) ;
return - 1 ;
}
return do_say ( & a , string , options , 0 ) ;
}
static int say_number_full ( struct ast_channel * chan , int num ,
const char * ints , const char * lang , const char * options ,
int audiofd , int ctrlfd )
{
char buf [ 64 ] ;
say_args_t a = { chan , ints , lang , audiofd , ctrlfd } ;
snprintf ( buf , sizeof ( buf ) , " num:%d " , num ) ;
return do_say ( & a , buf , options , 0 ) ;
}
static int say_enumeration_full ( struct ast_channel * chan , int num ,
const char * ints , const char * lang , const char * options ,
int audiofd , int ctrlfd )
{
char buf [ 64 ] ;
say_args_t a = { chan , ints , lang , audiofd , ctrlfd } ;
snprintf ( buf , sizeof ( buf ) , " enum:%d " , num ) ;
return do_say ( & a , buf , options , 0 ) ;
}
static int say_date_generic ( struct ast_channel * chan , time_t t ,
const char * ints , const char * lang , const char * format , const char * timezone , const char * prefix )
{
char buf [ 128 ] ;
struct tm tm ;
say_args_t a = { chan , ints , lang , - 1 , - 1 } ;
if ( format = = NULL )
format = " " ;
ast_localtime ( & t , & tm , NULL ) ;
snprintf ( buf , sizeof ( buf ) , " %s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d " ,
prefix ,
format ,
tm . tm_year + 1900 ,
tm . tm_mon + 1 ,
tm . tm_mday ,
tm . tm_hour ,
tm . tm_min ,
tm . tm_sec ,
tm . tm_wday ,
tm . tm_yday ) ;
return do_say ( & a , buf , NULL , 0 ) ;
}
static int say_date_with_format ( struct ast_channel * chan , time_t t ,
const char * ints , const char * lang , const char * format , const char * timezone )
{
return say_date_generic ( chan , t , ints , lang , format , timezone , " datetime " ) ;
}
static int say_date ( struct ast_channel * chan , time_t t , const char * ints , const char * lang )
{
return say_date_generic ( chan , t , ints , lang , " " , NULL , " date " ) ;
}
static int say_time ( struct ast_channel * chan , time_t t , const char * ints , const char * lang )
{
return say_date_generic ( chan , t , ints , lang , " " , NULL , " time " ) ;
}
static int say_datetime ( struct ast_channel * chan , time_t t , const char * ints , const char * lang )
{
return say_date_generic ( chan , t , ints , lang , " " , NULL , " datetime " ) ;
}
/*
* remap the ' say ' functions to use those in this file
*/
static int __say_init ( int fd , int argc , char * argv [ ] )
{
const char * old_mode = say_api_buf [ 0 ] ? say_new : say_old ;
char * mode ;
if ( argc = = 2 ) {
ast_cli ( fd , " say mode is [%s] \n " , old_mode ) ;
return RESULT_SUCCESS ;
} else if ( argc ! = 3 )
return RESULT_SHOWUSAGE ;
mode = argv [ 2 ] ;
ast_log ( LOG_WARNING , " init say.c from %s to %s " , old_mode , mode ) ;
if ( ! strcmp ( mode , old_mode ) ) {
ast_log ( LOG_WARNING , " say mode is %s already \n " , mode ) ;
} else if ( ! strcmp ( mode , say_new ) ) {
if ( say_cfg = = NULL )
say_cfg = ast_config_load ( " say.conf " ) ;
save_say_mode ( say_new ) ;
ast_say_number_full = say_number_full ;
ast_say_enumeration_full = say_enumeration_full ;
#if 0
ast_say_digits_full = say_digits_full ;
ast_say_digit_str_full = say_digit_str_full ;
ast_say_character_str_full = say_character_str_full ;
ast_say_phonetic_str_full = say_phonetic_str_full ;
ast_say_datetime_from_now = say_datetime_from_now ;
# endif
ast_say_datetime = say_datetime ;
ast_say_time = say_time ;
ast_say_date = say_date ;
ast_say_date_with_format = say_date_with_format ;
} else if ( ! strcmp ( mode , say_old ) & & say_api_buf [ 0 ] = = say_new ) {
restore_say_mode ( NULL ) ;
} else {
ast_log ( LOG_WARNING , " unrecognized mode %s \n " , mode ) ;
}
return RESULT_SUCCESS ;
}
static struct ast_cli_entry myclis [ ] = {
{ { " say " , " load " , NULL } , __say_init , " set/show the say mode " , " say load new|old " } ,
} ;
static int playback_exec ( struct ast_channel * chan , void * data )
{
int res = 0 ;
struct localuser * u ;
char * tmp ;
int option_skip = 0 ;
int option_say = 0 ;
int option_noanswer = 0 ;
int priority_jump = 0 ;
@ -96,6 +410,8 @@ static int playback_exec(struct ast_channel *chan, void *data)
if ( args . options ) {
if ( strcasestr ( args . options , " skip " ) )
option_skip = 1 ;
if ( strcasestr ( args . options , " say " ) )
option_say = 1 ;
if ( strcasestr ( args . options , " noanswer " ) )
option_noanswer = 1 ;
if ( strchr ( args . options , ' j ' ) )
@ -105,8 +421,7 @@ static int playback_exec(struct ast_channel *chan, void *data)
if ( chan - > _state ! = AST_STATE_UP ) {
if ( option_skip ) {
/* At the user's option, skip if the line is not up */
LOCAL_USER_REMOVE ( u ) ;
return 0 ;
goto done ;
} else if ( ! option_noanswer )
/* Otherwise answer unless we're supposed to send this while on-hook */
res = ast_answer ( chan ) ;
@ -117,7 +432,10 @@ static int playback_exec(struct ast_channel *chan, void *data)
ast_stopstream ( chan ) ;
while ( ! res & & ( front = strsep ( & tmp , " & " ) ) ) {
res = ast_streamfile ( chan , front , chan - > language ) ;
if ( option_say )
res = say_full ( chan , front , " " , chan - > language , NULL , - 1 , - 1 ) ;
else
res = ast_streamfile ( chan , front , chan - > language ) ;
if ( ! res ) {
res = ast_waitstream ( chan , " " ) ;
ast_stopstream ( chan ) ;
@ -131,6 +449,7 @@ static int playback_exec(struct ast_channel *chan, void *data)
}
pbx_builtin_setvar_helper ( chan , " PLAYBACKSTATUS " , mres ? " FAILED " : " SUCCESS " ) ;
}
done :
LOCAL_USER_REMOVE ( u ) ;
return res ;
}
@ -148,6 +467,7 @@ static int unload_module(void *mod)
static int load_module ( void * mod )
{
ast_cli_register_multiple ( myclis , sizeof ( myclis ) / sizeof ( struct ast_cli_entry ) ) ;
return ast_register_application ( app , playback_exec , synopsis , descrip ) ;
}