5dfb589f712a6536e6f00c4044c39d693d862413
[asterisk/asterisk.git] / apps / app_playback.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief Trivial application to playback a sound file
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27  
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35
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"
49
50 static char *app = "Playback";
51
52 static char *synopsis = "Play a file";
53
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 ;
69
70
71 static struct ast_config *say_cfg;
72 /* save the say' api calls.
73  * The first entry is NULL if we have the standard source,
74  * otherwise we are sourcing from here.
75  * 'say load [new|old]' will enable the new or old method, or report status
76  */
77 static const void * say_api_buf[40];
78 static const char *say_old = "old";
79 static const char *say_new = "new";
80
81 static void save_say_mode(const void *arg)
82 {
83         int i = 0;
84         say_api_buf[i++] = arg;
85
86         say_api_buf[i++] = ast_say_number_full;
87         say_api_buf[i++] = ast_say_enumeration_full;
88         say_api_buf[i++] = ast_say_digit_str_full;
89         say_api_buf[i++] = ast_say_character_str_full;
90         say_api_buf[i++] = ast_say_phonetic_str_full;
91         say_api_buf[i++] = ast_say_datetime;
92         say_api_buf[i++] = ast_say_time;
93         say_api_buf[i++] = ast_say_date;
94         say_api_buf[i++] = ast_say_datetime_from_now;
95         say_api_buf[i++] = ast_say_date_with_format;
96 }
97
98 static void restore_say_mode(void *arg)
99 {
100         int i = 0;
101         say_api_buf[i++] = arg;
102
103         ast_say_number_full = say_api_buf[i++];
104         ast_say_enumeration_full = say_api_buf[i++];
105         ast_say_digit_str_full = say_api_buf[i++];
106         ast_say_character_str_full = say_api_buf[i++];
107         ast_say_phonetic_str_full = say_api_buf[i++];
108         ast_say_datetime = say_api_buf[i++];
109         ast_say_time = say_api_buf[i++];
110         ast_say_date = say_api_buf[i++];
111         ast_say_datetime_from_now = say_api_buf[i++];
112         ast_say_date_with_format = say_api_buf[i++];
113 }
114
115 /* 
116  * Typical 'say' arguments in addition to the date or number or string
117  * to say. We do not include 'options' because they may be different
118  * in recursive calls, and so they are better left as an external
119  * parameter.
120  */
121 typedef struct {
122         struct ast_channel *chan;
123         const char *ints;
124         const char *language;
125         int audiofd;
126         int ctrlfd;
127 } say_args_t;
128
129 static int s_streamwait3(const say_args_t *a, const char *fn)
130 {
131         int res = ast_streamfile(a->chan, fn, a->language);
132         if (res) {
133                 ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
134                 return res;
135         }
136         res = (a->audiofd  > -1 && a->ctrlfd > -1) ?
137                 ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
138                 ast_waitstream(a->chan, a->ints);
139         ast_stopstream(a->chan);
140         return res;  
141 }
142
143 /*
144  * the string is 'prefix:data' or prefix:fmt:data'
145  * with ':' being invalid in strings.
146  */
147 static int do_say(say_args_t *a, const char *s, const char *options, int depth)
148 {
149         struct ast_variable *v;
150         char *lang, *x, *rule = NULL;
151         int ret = 0;   
152         struct varshead head = { .first = NULL, .last = NULL };
153         struct ast_var_t *n;
154
155         ast_log(LOG_WARNING, "string <%s> depth <%d>\n", s, depth);
156         if (depth++ > 10) {
157                 ast_log(LOG_WARNING, "recursion too deep, exiting\n");
158                 return -1;
159         }
160
161         /* scan languages same as in file.c */
162         if (a->language == NULL)
163                 a->language = "en";     /* default */
164         ast_log(LOG_WARNING, "try <%s> in <%s>\n", s, a->language);
165         lang = ast_strdupa(a->language);
166         for (;;) {
167                 for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
168                         if (ast_extension_match(v->name, s)) {
169                                 rule = ast_strdupa(v->value);
170                                 break;
171                         }
172                 }
173                 if (rule)
174                         break;
175                 if ( (x = strchr(lang, '_')) )
176                         *x = '\0';      /* try without suffix */
177                 else if (strcmp(lang, "en"))
178                         lang = "en";    /* last resort, try 'en' if not done yet */
179                 else
180                         break;
181         }
182         if (!rule)
183                 return 0;
184
185         /* skip up to two prefixes to get the value */
186         if ( (x = strchr(s, ':')) )
187                 s = x + 1;
188         if ( (x = strchr(s, ':')) )
189                 s = x + 1;
190         ast_log(LOG_WARNING, "value is <%s>\n", s);
191         n = ast_var_assign("SAY", s);
192         AST_LIST_INSERT_HEAD(&head, n, entries);
193
194         /* scan the body, one piece at a time */
195         while ( ret <= 0 && (x = strsep(&rule, ",")) ) { /* exit on key */
196                 char fn[128];
197                 const char *p, *fmt, *data; /* format and data pointers */
198
199                 /* prepare a decent file name */
200                 x = ast_skip_blanks(x);
201                 ast_trim_blanks(x);
202
203                 /* replace variables */
204                 memset(fn, 0, sizeof(fn)); /* XXX why isn't done in pbx_substitute_variables_helper! */
205                 pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
206                 ast_log(LOG_WARNING, "doing [%s]\n", fn);
207
208                 /* locate prefix and data, if any */
209                 fmt = index(fn, ':');
210                 if (!fmt || fmt == fn)  {       /* regular filename */
211                         ret = s_streamwait3(a, fn);
212                         continue;
213                 }
214                 fmt++;
215                 data = index(fmt, ':'); /* colon before data */
216                 if (!data || data == fmt) {     /* simple prefix-fmt */
217                         ret = do_say(a, fn, options, depth);
218                         continue;
219                 }
220                 /* prefix:fmt:data */
221                 for (p = fmt; p < data && ret <= 0; p++) {
222                         char fn2[sizeof(fn)];
223                         if (*p == ' ' || *p == '\t')    /* skip blanks */
224                                 continue;
225                         if (*p == '\'') {/* file name - we trim them */
226                                 char *y;
227                                 strcpy(fn2, ast_skip_blanks(p+1));      /* make a full copy */
228                                 y = index(fn2, '\'');
229                                 if (!y) {
230                                         p = data;       /* invalid. prepare to end */
231                                         break;
232                                 }
233                                 *y = '\0';
234                                 ast_trim_blanks(fn2);
235                                 p = index(p+1, '\'');
236                                 ret = s_streamwait3(a, fn2);
237                         } else {
238                                 int l = fmt-fn;
239                                 strcpy(fn2, fn); /* copy everything */
240                                 /* after prefix, append the format */
241                                 fn2[l++] = *p;
242                                 strcpy(fn2 + l, data);
243                                 ret = do_say(a, fn2, options, depth);
244                         }
245                 }
246         }
247         ast_var_delete(n);
248         return ret;
249 }
250
251 static int say_full(struct ast_channel *chan, const char *string,
252         const char *ints, const char *lang, const char *options,
253         int audiofd, int ctrlfd)
254 {
255         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
256   
257         if (!say_cfg) {
258                 ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", string);
259                 return -1;   
260         }
261         return do_say(&a, string, options, 0);
262 }
263
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)
267 {
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);
272 }
273
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)
277 {
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);
282 }
283
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)
286 {
287         char buf[128];
288         struct tm tm;
289         say_args_t a = { chan, ints, lang, -1, -1 };
290         if (format == NULL)
291                 format = "";
292
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);
306 }
307
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)
310 {
311         return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
312 }
313
314 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
315 {
316         return say_date_generic(chan, t, ints, lang, "", NULL, "date");
317 }
318
319 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
320 {
321         return say_date_generic(chan, t, ints, lang, "", NULL, "time");
322 }
323
324 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
325 {
326         return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
327 }
328
329 /*
330  * remap the 'say' functions to use those in this file
331  */
332 static int __say_init(int fd, int argc, char *argv[])
333 {
334         const char *old_mode = say_api_buf[0] ? say_new : say_old;
335         char *mode;
336
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];
343
344         ast_log(LOG_WARNING, "init say.c from %s to %s\n", old_mode, mode);
345
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;
353
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);
370         }
371         return RESULT_SUCCESS;
372 }
373
374 static struct ast_cli_entry cli_playback[] = {
375         { { "say", "load", NULL },
376         __say_init, "set/show the say mode",
377         "say load new|old" },
378 };
379
380 static int playback_exec(struct ast_channel *chan, void *data)
381 {
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;
390
391         AST_DECLARE_APP_ARGS(args,
392                 AST_APP_ARG(filenames);
393                 AST_APP_ARG(options);
394         );
395         
396         if (ast_strlen_zero(data)) {
397                 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
398                 return -1;
399         }
400
401         tmp = ast_strdupa(data);
402
403         u = ast_module_user_add(chan);
404         AST_STANDARD_APP_ARGS(args, tmp);
405
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;
415         }
416         
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);
424         }
425         if (!res) {
426                 char *back = args.filenames;
427                 char *front;
428
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;
444                         }
445                 }
446         }
447 done:
448         pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
449         ast_module_user_remove(u);
450         return res;
451 }
452
453 static int reload(void)
454 {
455         if (say_cfg) {
456                 ast_config_destroy(say_cfg);
457                 ast_log(LOG_NOTICE, "Reloading say.conf\n");
458         }
459         say_cfg = ast_config_load("say.conf");
460         /*
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.
463          */
464         return 0;
465 }
466
467 static int unload_module(void)
468 {
469         int res;
470
471         res = ast_unregister_application(app);
472
473         ast_module_user_hangup_all();
474
475         if (say_cfg)
476                 ast_config_destroy(say_cfg);
477
478         return res;     
479 }
480
481 static int load_module(void)
482 {
483         reload();
484         ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
485         return ast_register_application(app, playback_exec, synopsis, descrip);
486 }
487
488 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
489                 .load = load_module,
490                 .unload = unload_module,
491                 .reload = reload,
492                );