diff options
Diffstat (limited to 'apps/app_playback.c')
-rw-r--r-- | apps/app_playback.c | 326 |
1 files changed, 323 insertions, 3 deletions
diff --git a/apps/app_playback.c b/apps/app_playback.c index fc5dcf20a..2816d53df 100644 --- a/apps/app_playback.c +++ b/apps/app_playback.c @@ -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); } |