add experimental code for new-style "say" application.
[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 <string.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31
32 #include "asterisk.h"
33
34 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
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
47 #include "asterisk/cli.h"
48 #include "asterisk/localtime.h"
49 #include "asterisk/say.h"
50
51 static char *tdesc = "Sound File Playback Application";
52
53 static char *app = "Playback";
54
55 static char *synopsis = "Play a file";
56
57 static char *descrip = 
58 "  Playback(filename[&filename2...][|option]):  Plays back given filenames (do not put\n"
59 "extension). Options may also be included following a pipe symbol. The 'skip'\n"
60 "option causes the playback of the message to be skipped if the channel\n"
61 "is not in the 'up' state (i.e. it hasn't been  answered  yet). If 'skip' is \n"
62 "specified, the application will return immediately should the channel not be\n"
63 "off hook.  Otherwise, unless 'noanswer' is specified, the channel will\n"
64 "be answered before the sound is played. Not all channels support playing\n"
65 "messages while still on hook. If 'j' is specified, the application\n"
66 "will jump to priority n+101 if present when a file specified to be played\n"
67 "does not exist.\n"
68 "This application sets the following channel variable upon completion:\n"
69 " PLAYBACKSTATUS    The status of the playback attempt as a text string, one of\n"
70 "               SUCCESS | FAILED\n"
71 ;
72
73 LOCAL_USER_DECL;
74
75 static struct ast_config *say_cfg;
76 /* save the say' api calls.
77  * The first entry is NULL if we have the standard source,
78  * otherwise we are sourcing from here.
79  * 'say load [new|old]' will enable the new or old method, or report status
80  */
81 static const void * say_api_buf[40];
82 static const char *say_old = "old";
83 static const char *say_new = "new";
84
85 static void save_say_mode(const void *arg)
86 {
87         int i = 0;
88         say_api_buf[i++] = arg;
89
90         say_api_buf[i++] = ast_say_number_full;
91         say_api_buf[i++] = ast_say_enumeration_full;
92         say_api_buf[i++] = ast_say_digit_str_full;
93         say_api_buf[i++] = ast_say_character_str_full;
94         say_api_buf[i++] = ast_say_phonetic_str_full;
95         say_api_buf[i++] = ast_say_datetime;
96         say_api_buf[i++] = ast_say_time;
97         say_api_buf[i++] = ast_say_date;
98         say_api_buf[i++] = ast_say_datetime_from_now;
99         say_api_buf[i++] = ast_say_date_with_format;
100 }
101
102 static void restore_say_mode(void *arg)
103 {
104         int i = 0;
105         say_api_buf[i++] = arg;
106
107         ast_say_number_full = say_api_buf[i++];
108         ast_say_enumeration_full = say_api_buf[i++];
109         ast_say_digit_str_full = say_api_buf[i++];
110         ast_say_character_str_full = say_api_buf[i++];
111         ast_say_phonetic_str_full = say_api_buf[i++];
112         ast_say_datetime = say_api_buf[i++];
113         ast_say_time = say_api_buf[i++];
114         ast_say_date = say_api_buf[i++];
115         ast_say_datetime_from_now = say_api_buf[i++];
116         ast_say_date_with_format = say_api_buf[i++];
117 }
118
119 /* 
120  * Typical 'say' arguments in addition to the date or number or string
121  * to say. We do not include 'options' because they may be different
122  * in recursive calls, and so they are better left as an external
123  * parameter.
124  */
125 typedef struct {
126         struct ast_channel *chan;
127         const char *ints;
128         const char *language;
129         int audiofd;
130         int ctrlfd;
131 } say_args_t;
132
133 static int s_streamwait3(const say_args_t *a, const char *fn)
134 {
135         int res = ast_streamfile(a->chan, fn, a->language);
136         if (res) {
137                 ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
138                 return res;
139         }
140         res = (a->audiofd  > -1 && a->ctrlfd > -1) ?
141                 ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
142                 ast_waitstream(a->chan, a->ints);
143         ast_stopstream(a->chan);
144         return res;  
145 }
146
147 /*
148  * the string is 'prefix:data' or prefix:fmt:data'
149  * with ':' being invalid in strings.
150  */
151 static int do_say(say_args_t *a, const char *s, const char *options, int depth)
152 {
153         struct ast_variable *v;
154         char *lang, *x, *rule = NULL;
155         int ret = 0;   
156         struct varshead head = { .first = NULL, .last = NULL };
157         struct ast_var_t *n;
158
159         ast_log(LOG_WARNING, "string <%s> depth <%d>\n", s, depth);
160         if (depth++ > 10) {
161                 ast_log(LOG_WARNING, "recursion too deep, exiting\n");
162                 return -1;
163         }
164
165         /* scan languages same as in file.c */
166         if (a->language == NULL)
167                 a->language = "en";     /* default */
168         ast_log(LOG_WARNING, "try <%s> in <%s>\n", s, a->language);
169         lang = ast_strdupa(a->language);
170         if (!lang)      /* no memory! */
171                 return -1;
172         for (;;) {
173                 for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
174                         if (ast_extension_match(v->name, s)) {
175                                 rule = ast_strdupa(v->value);
176                                 break;
177                         }
178                 }
179                 if (rule)
180                         break;
181                 if ( (x = strchr(lang, '_')) )
182                         *x = '\0';      /* try without suffix */
183                 else if (strcmp(lang, "en"))
184                         lang = "en";    /* last resort, try 'en' if not done yet */
185                 else
186                         break;
187         }
188         if (!rule)
189                 return 0;
190
191         /* skip up to two prefixes to get the value */
192         if ( (x = strchr(s, ':')) )
193                 s = x + 1;
194         if ( (x = strchr(s, ':')) )
195                 s = x + 1;
196         ast_log(LOG_WARNING, "value is <%s>\n", s);
197         n = ast_var_assign("SAY", s);
198         AST_LIST_INSERT_HEAD(&head, n, entries);
199
200         /* scan the body, one piece at a time */
201         while ( ret <= 0 && (x = strsep(&rule, ",")) ) { /* exit on key */
202                 char fn[128];
203                 const char *p, *fmt, *data; /* format and data pointers */
204
205                 /* prepare a decent file name */
206                 x = ast_skip_blanks(x);
207                 ast_trim_blanks(x);
208
209                 /* replace variables */
210                 memset(fn, 0, sizeof(fn)); /* XXX why isn't done in pbx_substitute_variables_helper! */
211                 pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
212                 ast_log(LOG_WARNING, "doing [%s]\n", fn);
213
214                 /* locate prefix and data, if any */
215                 fmt = index(fn, ':');
216                 if (!fmt || fmt == fn)  {       /* regular filename */
217                         ret = s_streamwait3(a, fn);
218                         continue;
219                 }
220                 fmt++;
221                 data = index(fmt, ':'); /* colon before data */
222                 if (!data || data == fmt) {     /* simple prefix-fmt */
223                         ret = do_say(a, fn, options, depth);
224                         continue;
225                 }
226                 /* prefix:fmt:data */
227                 for (p = fmt; p < data && ret <= 0; p++) {
228                         char fn2[128];
229                         if (*p == ' ' || *p == '\t')    /* skip blanks */
230                                 continue;
231                         if (*p == '\'') {/* file name - we trim them */
232                                 char *y;
233                                 strcpy(fn2, ast_skip_blanks(p+1));      /* make a full copy */
234                                 y = index(fn2, '\'');
235                                 if (!y) {
236                                         p = data;       /* invalid. prepare to end */
237                                         break;
238                                 }
239                                 *y = '\0';
240                                 ast_trim_blanks(fn2);
241                                 p = index(p+1, '\'');
242                                 ret = s_streamwait3(a, fn2);
243                         } else {
244                                 int l = fmt-fn;
245                                 strcpy(fn2, fn); /* copy everything */
246                                 /* after prefix, append the format */
247                                 fn2[l++] = *p;
248                                 strcpy(fn2 + l, data);
249                                 ret = do_say(a, fn2, options, depth);
250                         }
251                 }
252         }
253         ast_var_delete(n);
254         return ret;
255 }
256
257 static int say_full(struct ast_channel *chan, const char *string,
258         const char *ints, const char *lang, const char *options,
259         int audiofd, int ctrlfd)
260 {
261         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
262   
263         if (!say_cfg) {
264                 ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", string);
265                 return -1;   
266         }
267         return do_say(&a, string, options, 0);
268 }
269
270 static int say_number_full(struct ast_channel *chan, int num,
271         const char *ints, const char *lang, const char *options,
272         int audiofd, int ctrlfd)
273 {
274         char buf[64];
275         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
276         snprintf(buf, sizeof(buf), "num:%d", num);
277         return do_say(&a, buf, options, 0);
278 }
279
280 static int say_enumeration_full(struct ast_channel *chan, int num,
281         const char *ints, const char *lang, const char *options,
282         int audiofd, int ctrlfd)
283 {
284         char buf[64];
285         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
286         snprintf(buf, sizeof(buf), "enum:%d", num);
287         return do_say(&a, buf, options, 0);
288 }
289
290 static int say_date_generic(struct ast_channel *chan, time_t t,
291         const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix)
292 {
293         char buf[128];
294         struct tm tm;
295         say_args_t a = { chan, ints, lang, -1, -1 };
296         if (format == NULL)
297                 format = "";
298
299         ast_localtime(&t, &tm, NULL);
300         snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
301                 prefix,
302                 format,
303                 tm.tm_year+1900,
304                 tm.tm_mon+1,
305                 tm.tm_mday,
306                 tm.tm_hour,
307                 tm.tm_min,
308                 tm.tm_sec,
309                 tm.tm_wday,
310                 tm.tm_yday);
311         return do_say(&a, buf, NULL, 0);
312 }
313
314 static int say_date_with_format(struct ast_channel *chan, time_t t,
315         const char *ints, const char *lang, const char *format, const char *timezone)
316 {
317         return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
318 }
319
320 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
321 {
322         return say_date_generic(chan, t, ints, lang, "", NULL, "date");
323 }
324
325 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
326 {
327         return say_date_generic(chan, t, ints, lang, "", NULL, "time");
328 }
329
330 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
331 {
332         return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
333 }
334
335 /*
336  * remap the 'say' functions to use those in this file
337  */
338 static int __say_init(int fd, int argc, char *argv[])
339 {
340         const char *old_mode = say_api_buf[0] ? say_new : say_old;
341         char *mode;
342
343         if (argc == 2) {
344                 ast_cli(fd, "say mode is [%s]\n", old_mode);
345                 return RESULT_SUCCESS;
346         } else if (argc != 3)
347                 return RESULT_SHOWUSAGE;
348         mode = argv[2];
349
350         ast_log(LOG_WARNING, "init say.c from %s to %s", old_mode, mode);
351
352         if (!strcmp(mode, old_mode)) {
353                 ast_log(LOG_WARNING, "say mode is %s already\n", mode);
354         } else if (!strcmp(mode, say_new)) {
355                 if (say_cfg == NULL)
356                         say_cfg = ast_config_load("say.conf");
357                 save_say_mode(say_new);
358                 ast_say_number_full = say_number_full;
359
360                 ast_say_enumeration_full = say_enumeration_full;
361 #if 0
362                 ast_say_digits_full = say_digits_full;
363                 ast_say_digit_str_full = say_digit_str_full;
364                 ast_say_character_str_full = say_character_str_full;
365                 ast_say_phonetic_str_full = say_phonetic_str_full;
366                 ast_say_datetime_from_now = say_datetime_from_now;
367 #endif
368                 ast_say_datetime = say_datetime;
369                 ast_say_time = say_time;
370                 ast_say_date = say_date;
371                 ast_say_date_with_format = say_date_with_format;
372         } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
373                 restore_say_mode(NULL);
374         } else {
375                 ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
376         }
377         return RESULT_SUCCESS;
378 }
379
380 static struct ast_cli_entry myclis[] = {
381         { { "say", "load", NULL }, __say_init, "set/show the say mode", "say load new|old" },
382 };
383
384 static int playback_exec(struct ast_channel *chan, void *data)
385 {
386         int res = 0;
387         struct localuser *u;
388         char *tmp;
389         int option_skip=0;
390         int option_say=0;
391         int option_noanswer = 0;
392         int priority_jump = 0;
393
394         AST_DECLARE_APP_ARGS(args,
395                 AST_APP_ARG(filenames);
396                 AST_APP_ARG(options);
397         );
398         
399         if (ast_strlen_zero(data)) {
400                 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
401                 return -1;
402         }
403
404         if (!(tmp = ast_strdupa(data)))
405                 return -1;      
406
407         LOCAL_USER_ADD(u);
408         AST_STANDARD_APP_ARGS(args, tmp);
409
410         if (args.options) {
411                 if (strcasestr(args.options, "skip"))
412                         option_skip = 1;
413                 if (strcasestr(args.options, "say"))
414                         option_say = 1;
415                 if (strcasestr(args.options, "noanswer"))
416                         option_noanswer = 1;
417                 if (strchr(args.options, 'j'))
418                         priority_jump = 1;
419         }
420         
421         if (chan->_state != AST_STATE_UP) {
422                 if (option_skip) {
423                         /* At the user's option, skip if the line is not up */
424                         goto done;
425                 } else if (!option_noanswer)
426                         /* Otherwise answer unless we're supposed to send this while on-hook */
427                         res = ast_answer(chan);
428         }
429         if (!res) {
430                 int mres = 0;
431                 char *front;
432
433                 ast_stopstream(chan);
434                 while (!res && (front = strsep(&tmp, "&"))) {
435                         if (option_say)
436                                 res = say_full(chan, front, "", chan->language, NULL, -1, -1);
437                         else
438                                 res = ast_streamfile(chan, front, chan->language);
439                         if (!res) { 
440                                 res = ast_waitstream(chan, ""); 
441                                 ast_stopstream(chan);
442                         } else {
443                                 ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
444                                 if (priority_jump || ast_opt_priority_jumping)
445                                         ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
446                                 res = 0;
447                                 mres = 1;
448                         }
449                 }
450                 pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
451         }
452 done:
453         LOCAL_USER_REMOVE(u);
454         return res;
455 }
456
457 static int unload_module(void *mod)
458 {
459         int res;
460
461         res = ast_unregister_application(app);
462
463         STANDARD_HANGUP_LOCALUSERS;
464
465         return res;     
466 }
467
468 static int load_module(void *mod)
469 {
470         ast_cli_register_multiple(myclis, sizeof(myclis)/sizeof(struct ast_cli_entry));
471         return ast_register_application(app, playback_exec, synopsis, descrip);
472 }
473
474 static const char *description(void)
475 {
476         return tdesc;
477 }
478
479 static const char *key(void)
480 {
481         return ASTERISK_GPL_KEY;
482 }
483
484 STD_MOD1;