(closes issue #12846)
[asterisk-bristuff.git] / apps / app_playback.c
blob8f78bef78618257b47b3f7ebebad8682d6f65861
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
19 /*! \file
21 * \brief Trivial application to playback a sound file
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
28 #include "asterisk.h"
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stdio.h>
36 #include "asterisk/lock.h"
37 #include "asterisk/file.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/pbx.h"
41 #include "asterisk/module.h"
42 #include "asterisk/translate.h"
43 #include "asterisk/utils.h"
44 #include "asterisk/options.h"
45 #include "asterisk/app.h"
46 #include "asterisk/cli.h"
47 #include "asterisk/localtime.h"
48 #include "asterisk/say.h"
50 static char *app = "Playback";
52 static char *synopsis = "Play a file";
54 static char *descrip =
55 " Playback(filename[&filename2...][|option]): Plays back given filenames (do not put\n"
56 "extension). Options may also be included following a pipe symbol. The 'skip'\n"
57 "option causes the playback of the message to be skipped if the channel\n"
58 "is not in the 'up' state (i.e. it hasn't been answered yet). If 'skip' is \n"
59 "specified, the application will return immediately should the channel not be\n"
60 "off hook. Otherwise, unless 'noanswer' is specified, the channel will\n"
61 "be answered before the sound is played. Not all channels support playing\n"
62 "messages while still on hook. If 'j' is specified, the application\n"
63 "will jump to priority n+101 if present when a file specified to be played\n"
64 "does not exist.\n"
65 "This application sets the following channel variable upon completion:\n"
66 " PLAYBACKSTATUS The status of the playback attempt as a text string, one of\n"
67 " SUCCESS | FAILED\n"
68 "See Also: Background (application) -- for playing soundfiles that are interruptible\n"
69 " WaitExten (application) -- wait for digits from caller, optionally play music on hold\n"
73 static struct ast_config *say_cfg = NULL;
74 /* save the say' api calls.
75 * The first entry is NULL if we have the standard source,
76 * otherwise we are sourcing from here.
77 * 'say load [new|old]' will enable the new or old method, or report status
79 static const void * say_api_buf[40];
80 static const char *say_old = "old";
81 static const char *say_new = "new";
83 static void save_say_mode(const void *arg)
85 int i = 0;
86 say_api_buf[i++] = arg;
88 say_api_buf[i++] = ast_say_number_full;
89 say_api_buf[i++] = ast_say_enumeration_full;
90 say_api_buf[i++] = ast_say_digit_str_full;
91 say_api_buf[i++] = ast_say_character_str_full;
92 say_api_buf[i++] = ast_say_phonetic_str_full;
93 say_api_buf[i++] = ast_say_datetime;
94 say_api_buf[i++] = ast_say_time;
95 say_api_buf[i++] = ast_say_date;
96 say_api_buf[i++] = ast_say_datetime_from_now;
97 say_api_buf[i++] = ast_say_date_with_format;
100 static void restore_say_mode(void *arg)
102 int i = 0;
103 say_api_buf[i++] = arg;
105 ast_say_number_full = say_api_buf[i++];
106 ast_say_enumeration_full = say_api_buf[i++];
107 ast_say_digit_str_full = say_api_buf[i++];
108 ast_say_character_str_full = say_api_buf[i++];
109 ast_say_phonetic_str_full = say_api_buf[i++];
110 ast_say_datetime = say_api_buf[i++];
111 ast_say_time = say_api_buf[i++];
112 ast_say_date = say_api_buf[i++];
113 ast_say_datetime_from_now = say_api_buf[i++];
114 ast_say_date_with_format = say_api_buf[i++];
118 * Typical 'say' arguments in addition to the date or number or string
119 * to say. We do not include 'options' because they may be different
120 * in recursive calls, and so they are better left as an external
121 * parameter.
123 typedef struct {
124 struct ast_channel *chan;
125 const char *ints;
126 const char *language;
127 int audiofd;
128 int ctrlfd;
129 } say_args_t;
131 static int s_streamwait3(const say_args_t *a, const char *fn)
133 int res = ast_streamfile(a->chan, fn, a->language);
134 if (res) {
135 ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
136 return res;
138 res = (a->audiofd > -1 && a->ctrlfd > -1) ?
139 ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
140 ast_waitstream(a->chan, a->ints);
141 ast_stopstream(a->chan);
142 return res;
146 * the string is 'prefix:data' or prefix:fmt:data'
147 * with ':' being invalid in strings.
149 static int do_say(say_args_t *a, const char *s, const char *options, int depth)
151 struct ast_variable *v;
152 char *lang, *x, *rule = NULL;
153 int ret = 0;
154 struct varshead head = { .first = NULL, .last = NULL };
155 struct ast_var_t *n;
157 if (depth++ > 10) {
158 ast_log(LOG_WARNING, "recursion too deep, exiting\n");
159 return -1;
160 } else if (!say_cfg) {
161 ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", s);
162 return -1;
165 /* scan languages same as in file.c */
166 if (a->language == NULL)
167 a->language = "en"; /* default */
168 lang = ast_strdupa(a->language);
169 for (;;) {
170 for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
171 if (ast_extension_match(v->name, s)) {
172 rule = ast_strdupa(v->value);
173 break;
176 if (rule)
177 break;
178 if ( (x = strchr(lang, '_')) )
179 *x = '\0'; /* try without suffix */
180 else if (strcmp(lang, "en"))
181 lang = "en"; /* last resort, try 'en' if not done yet */
182 else
183 break;
185 if (!rule)
186 return 0;
188 /* skip up to two prefixes to get the value */
189 if ( (x = strchr(s, ':')) )
190 s = x + 1;
191 if ( (x = strchr(s, ':')) )
192 s = x + 1;
193 n = ast_var_assign("SAY", s);
194 AST_LIST_INSERT_HEAD(&head, n, entries);
196 /* scan the body, one piece at a time */
197 while ( !ret && (x = strsep(&rule, ",")) ) { /* exit on key */
198 char fn[128];
199 const char *p, *fmt, *data; /* format and data pointers */
201 /* prepare a decent file name */
202 x = ast_skip_blanks(x);
203 ast_trim_blanks(x);
205 /* replace variables */
206 memset(fn, 0, sizeof(fn)); /* XXX why isn't done in pbx_substitute_variables_helper! */
207 pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
209 /* locate prefix and data, if any */
210 fmt = index(fn, ':');
211 if (!fmt || fmt == fn) { /* regular filename */
212 ret = s_streamwait3(a, fn);
213 continue;
215 fmt++;
216 data = index(fmt, ':'); /* colon before data */
217 if (!data || data == fmt) { /* simple prefix-fmt */
218 ret = do_say(a, fn, options, depth);
219 continue;
221 /* prefix:fmt:data */
222 for (p = fmt; p < data && ret <= 0; p++) {
223 char fn2[sizeof(fn)];
224 if (*p == ' ' || *p == '\t') /* skip blanks */
225 continue;
226 if (*p == '\'') {/* file name - we trim them */
227 char *y;
228 strcpy(fn2, ast_skip_blanks(p+1)); /* make a full copy */
229 y = index(fn2, '\'');
230 if (!y) {
231 p = data; /* invalid. prepare to end */
232 break;
234 *y = '\0';
235 ast_trim_blanks(fn2);
236 p = index(p+1, '\'');
237 ret = s_streamwait3(a, fn2);
238 } else {
239 int l = fmt-fn;
240 strcpy(fn2, fn); /* copy everything */
241 /* after prefix, append the format */
242 fn2[l++] = *p;
243 strcpy(fn2 + l, data);
244 ret = do_say(a, fn2, options, depth);
247 if (ret) {
248 break;
252 ast_var_delete(n);
253 return ret;
256 static int say_full(struct ast_channel *chan, const char *string,
257 const char *ints, const char *lang, const char *options,
258 int audiofd, int ctrlfd)
260 say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
261 return do_say(&a, string, options, 0);
264 static int say_number_full(struct ast_channel *chan, int num,
265 const char *ints, const char *lang, const char *options,
266 int audiofd, int ctrlfd)
268 char buf[64];
269 say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
270 snprintf(buf, sizeof(buf), "num:%d", num);
271 return do_say(&a, buf, options, 0);
274 static int say_enumeration_full(struct ast_channel *chan, int num,
275 const char *ints, const char *lang, const char *options,
276 int audiofd, int ctrlfd)
278 char buf[64];
279 say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
280 snprintf(buf, sizeof(buf), "enum:%d", num);
281 return do_say(&a, buf, options, 0);
284 static int say_date_generic(struct ast_channel *chan, time_t t,
285 const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix)
287 char buf[128];
288 struct tm tm;
289 say_args_t a = { chan, ints, lang, -1, -1 };
290 if (format == NULL)
291 format = "";
293 ast_localtime(&t, &tm, NULL);
294 snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
295 prefix,
296 format,
297 tm.tm_year+1900,
298 tm.tm_mon+1,
299 tm.tm_mday,
300 tm.tm_hour,
301 tm.tm_min,
302 tm.tm_sec,
303 tm.tm_wday,
304 tm.tm_yday);
305 return do_say(&a, buf, NULL, 0);
308 static int say_date_with_format(struct ast_channel *chan, time_t t,
309 const char *ints, const char *lang, const char *format, const char *timezone)
311 return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
314 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
316 return say_date_generic(chan, t, ints, lang, "", NULL, "date");
319 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
321 return say_date_generic(chan, t, ints, lang, "", NULL, "time");
324 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
326 return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
330 * remap the 'say' functions to use those in this file
332 static int __say_init(int fd, int argc, char *argv[])
334 const char *old_mode = say_api_buf[0] ? say_new : say_old;
335 char *mode;
337 if (argc == 2) {
338 ast_cli(fd, "say mode is [%s]\n", old_mode);
339 return RESULT_SUCCESS;
340 } else if (argc != 3)
341 return RESULT_SHOWUSAGE;
342 mode = argv[2];
344 ast_log(LOG_WARNING, "init say.c from %s to %s\n", old_mode, mode);
346 if (!strcmp(mode, old_mode)) {
347 ast_log(LOG_WARNING, "say mode is %s already\n", mode);
348 } else if (!strcmp(mode, say_new)) {
349 if (say_cfg == NULL)
350 say_cfg = ast_config_load("say.conf");
351 save_say_mode(say_new);
352 ast_say_number_full = say_number_full;
354 ast_say_enumeration_full = say_enumeration_full;
355 #if 0
356 ast_say_digits_full = say_digits_full;
357 ast_say_digit_str_full = say_digit_str_full;
358 ast_say_character_str_full = say_character_str_full;
359 ast_say_phonetic_str_full = say_phonetic_str_full;
360 ast_say_datetime_from_now = say_datetime_from_now;
361 #endif
362 ast_say_datetime = say_datetime;
363 ast_say_time = say_time;
364 ast_say_date = say_date;
365 ast_say_date_with_format = say_date_with_format;
366 } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
367 restore_say_mode(NULL);
368 } else {
369 ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
371 return RESULT_SUCCESS;
374 static struct ast_cli_entry cli_playback[] = {
375 { { "say", "load", NULL },
376 __say_init, "set/show the say mode",
377 "Usage: say load [new|old]\n Set/show the say mode\n" },
380 static int playback_exec(struct ast_channel *chan, void *data)
382 int res = 0;
383 int mres = 0;
384 struct ast_module_user *u;
385 char *tmp;
386 int option_skip=0;
387 int option_say=0;
388 int option_noanswer = 0;
389 int priority_jump = 0;
391 AST_DECLARE_APP_ARGS(args,
392 AST_APP_ARG(filenames);
393 AST_APP_ARG(options);
396 if (ast_strlen_zero(data)) {
397 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
398 return -1;
401 tmp = ast_strdupa(data);
403 u = ast_module_user_add(chan);
404 AST_STANDARD_APP_ARGS(args, tmp);
406 if (args.options) {
407 if (strcasestr(args.options, "skip"))
408 option_skip = 1;
409 if (strcasestr(args.options, "say"))
410 option_say = 1;
411 if (strcasestr(args.options, "noanswer"))
412 option_noanswer = 1;
413 if (strchr(args.options, 'j'))
414 priority_jump = 1;
417 if (chan->_state != AST_STATE_UP) {
418 if (option_skip) {
419 /* At the user's option, skip if the line is not up */
420 goto done;
421 } else if (!option_noanswer)
422 /* Otherwise answer unless we're supposed to send this while on-hook */
423 res = ast_answer(chan);
425 if (!res) {
426 char *back = args.filenames;
427 char *front;
429 ast_stopstream(chan);
430 while (!res && (front = strsep(&back, "&"))) {
431 if (option_say)
432 res = say_full(chan, front, "", chan->language, NULL, -1, -1);
433 else
434 res = ast_streamfile(chan, front, chan->language);
435 if (!res) {
436 res = ast_waitstream(chan, "");
437 ast_stopstream(chan);
438 } else {
439 ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
440 if (priority_jump || ast_opt_priority_jumping)
441 ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
442 res = 0;
443 mres = 1;
447 done:
448 pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
449 ast_module_user_remove(u);
450 return res;
453 static int reload(void)
455 if (say_cfg) {
456 ast_config_destroy(say_cfg);
457 ast_log(LOG_NOTICE, "Reloading say.conf\n");
459 say_cfg = ast_config_load("say.conf");
461 * XXX here we should sort rules according to the same order
462 * we have in pbx.c so we have the same matching behaviour.
464 return 0;
467 static int unload_module(void)
469 int res;
471 res = ast_unregister_application(app);
473 ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
475 ast_module_user_hangup_all();
477 if (say_cfg)
478 ast_config_destroy(say_cfg);
480 return res;
483 static int load_module(void)
485 say_cfg = ast_config_load("say.conf");
486 ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
487 return ast_register_application(app, playback_exec, synopsis, descrip);
490 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
491 .load = load_module,
492 .unload = unload_module,
493 .reload = reload,