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.
21 * \brief Trivial application to playback a sound file
23 * \author Mark Spencer <markster@digium.com>
25 * \ingroup applications
30 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
32 #include "asterisk/file.h"
33 #include "asterisk/pbx.h"
34 #include "asterisk/module.h"
35 #include "asterisk/app.h"
36 /* This file provides config-file based 'say' functions, and implenents
39 #include "asterisk/say.h" /* provides config-file based 'say' functions */
40 #include "asterisk/cli.h"
42 static char *app
= "Playback";
44 static char *synopsis
= "Play a file";
46 static char *descrip
=
47 " Playback(filename[&filename2...][,option]): Plays back given filenames (do not put\n"
48 "extension). Options may also be included following a comma.\n"
49 "The 'skip' option causes the playback of the message to be skipped if the channel\n"
50 "is not in the 'up' state (i.e. it hasn't been answered yet). If 'skip' is \n"
51 "specified, the application will return immediately should the channel not be\n"
52 "off hook. Otherwise, unless 'noanswer' is specified, the channel will\n"
53 "be answered before the sound is played. Not all channels support playing\n"
54 "messages while still on hook.\n"
55 "This application sets the following channel variable upon completion:\n"
56 " PLAYBACKSTATUS The status of the playback attempt as a text string, one of\n"
58 "See Also: Background (application) -- for playing soundfiles that are interruptible\n"
59 " WaitExten (application) -- wait for digits from caller, optionally play music on hold\n"
63 static struct ast_config
*say_cfg
= NULL
;
64 /* save the say' api calls.
65 * The first entry is NULL if we have the standard source,
66 * otherwise we are sourcing from here.
67 * 'say load [new|old]' will enable the new or old method, or report status
69 static const void *say_api_buf
[40];
70 static const char *say_old
= "old";
71 static const char *say_new
= "new";
73 static void save_say_mode(const void *arg
)
76 say_api_buf
[i
++] = arg
;
78 say_api_buf
[i
++] = ast_say_number_full
;
79 say_api_buf
[i
++] = ast_say_enumeration_full
;
80 say_api_buf
[i
++] = ast_say_digit_str_full
;
81 say_api_buf
[i
++] = ast_say_character_str_full
;
82 say_api_buf
[i
++] = ast_say_phonetic_str_full
;
83 say_api_buf
[i
++] = ast_say_datetime
;
84 say_api_buf
[i
++] = ast_say_time
;
85 say_api_buf
[i
++] = ast_say_date
;
86 say_api_buf
[i
++] = ast_say_datetime_from_now
;
87 say_api_buf
[i
++] = ast_say_date_with_format
;
90 static void restore_say_mode(void *arg
)
93 say_api_buf
[i
++] = arg
;
95 ast_say_number_full
= say_api_buf
[i
++];
96 ast_say_enumeration_full
= say_api_buf
[i
++];
97 ast_say_digit_str_full
= say_api_buf
[i
++];
98 ast_say_character_str_full
= say_api_buf
[i
++];
99 ast_say_phonetic_str_full
= say_api_buf
[i
++];
100 ast_say_datetime
= say_api_buf
[i
++];
101 ast_say_time
= say_api_buf
[i
++];
102 ast_say_date
= say_api_buf
[i
++];
103 ast_say_datetime_from_now
= say_api_buf
[i
++];
104 ast_say_date_with_format
= say_api_buf
[i
++];
108 * Typical 'say' arguments in addition to the date or number or string
109 * to say. We do not include 'options' because they may be different
110 * in recursive calls, and so they are better left as an external
114 struct ast_channel
*chan
;
116 const char *language
;
121 static int s_streamwait3(const say_args_t
*a
, const char *fn
)
123 int res
= ast_streamfile(a
->chan
, fn
, a
->language
);
125 ast_log(LOG_WARNING
, "Unable to play message %s\n", fn
);
128 res
= (a
->audiofd
> -1 && a
->ctrlfd
> -1) ?
129 ast_waitstream_full(a
->chan
, a
->ints
, a
->audiofd
, a
->ctrlfd
) :
130 ast_waitstream(a
->chan
, a
->ints
);
131 ast_stopstream(a
->chan
);
136 * the string is 'prefix:data' or prefix:fmt:data'
137 * with ':' being invalid in strings.
139 static int do_say(say_args_t
*a
, const char *s
, const char *options
, int depth
)
141 struct ast_variable
*v
;
142 char *lang
, *x
, *rule
= NULL
;
144 struct varshead head
= { .first
= NULL
, .last
= NULL
};
147 ast_debug(2, "string <%s> depth <%d>\n", s
, depth
);
149 ast_log(LOG_WARNING
, "recursion too deep, exiting\n");
151 } else if (!say_cfg
) {
152 ast_log(LOG_WARNING
, "no say.conf, cannot spell '%s'\n", s
);
156 /* scan languages same as in file.c */
157 if (a
->language
== NULL
)
158 a
->language
= "en"; /* default */
159 ast_debug(2, "try <%s> in <%s>\n", s
, a
->language
);
160 lang
= ast_strdupa(a
->language
);
162 for (v
= ast_variable_browse(say_cfg
, lang
); v
; v
= v
->next
) {
163 if (ast_extension_match(v
->name
, s
)) {
164 rule
= ast_strdupa(v
->value
);
170 if ( (x
= strchr(lang
, '_')) )
171 *x
= '\0'; /* try without suffix */
172 else if (strcmp(lang
, "en"))
173 lang
= "en"; /* last resort, try 'en' if not done yet */
180 /* skip up to two prefixes to get the value */
181 if ( (x
= strchr(s
, ':')) )
183 if ( (x
= strchr(s
, ':')) )
185 ast_debug(2, "value is <%s>\n", s
);
186 n
= ast_var_assign("SAY", s
);
187 AST_LIST_INSERT_HEAD(&head
, n
, entries
);
189 /* scan the body, one piece at a time */
190 while ( !ret
&& (x
= strsep(&rule
, ",")) ) { /* exit on key */
192 const char *p
, *fmt
, *data
; /* format and data pointers */
194 /* prepare a decent file name */
195 x
= ast_skip_blanks(x
);
198 /* replace variables */
199 pbx_substitute_variables_varshead(&head
, x
, fn
, sizeof(fn
));
200 ast_debug(2, "doing [%s]\n", fn
);
202 /* locate prefix and data, if any */
203 fmt
= index(fn
, ':');
204 if (!fmt
|| fmt
== fn
) { /* regular filename */
205 ret
= s_streamwait3(a
, fn
);
209 data
= index(fmt
, ':'); /* colon before data */
210 if (!data
|| data
== fmt
) { /* simple prefix-fmt */
211 ret
= do_say(a
, fn
, options
, depth
);
214 /* prefix:fmt:data */
215 for (p
= fmt
; p
< data
&& ret
<= 0; p
++) {
216 char fn2
[sizeof(fn
)];
217 if (*p
== ' ' || *p
== '\t') /* skip blanks */
219 if (*p
== '\'') {/* file name - we trim them */
221 strcpy(fn2
, ast_skip_blanks(p
+1)); /* make a full copy */
222 y
= index(fn2
, '\'');
224 p
= data
; /* invalid. prepare to end */
228 ast_trim_blanks(fn2
);
229 p
= index(p
+1, '\'');
230 ret
= s_streamwait3(a
, fn2
);
233 strcpy(fn2
, fn
); /* copy everything */
234 /* after prefix, append the format */
236 strcpy(fn2
+ l
, data
);
237 ret
= do_say(a
, fn2
, options
, depth
);
249 static int say_full(struct ast_channel
*chan
, const char *string
,
250 const char *ints
, const char *lang
, const char *options
,
251 int audiofd
, int ctrlfd
)
253 say_args_t a
= { chan
, ints
, lang
, audiofd
, ctrlfd
};
254 return do_say(&a
, string
, options
, 0);
257 static int say_number_full(struct ast_channel
*chan
, int num
,
258 const char *ints
, const char *lang
, const char *options
,
259 int audiofd
, int ctrlfd
)
262 say_args_t a
= { chan
, ints
, lang
, audiofd
, ctrlfd
};
263 snprintf(buf
, sizeof(buf
), "num:%d", num
);
264 return do_say(&a
, buf
, options
, 0);
267 static int say_enumeration_full(struct ast_channel
*chan
, int num
,
268 const char *ints
, const char *lang
, const char *options
,
269 int audiofd
, int ctrlfd
)
272 say_args_t a
= { chan
, ints
, lang
, audiofd
, ctrlfd
};
273 snprintf(buf
, sizeof(buf
), "enum:%d", num
);
274 return do_say(&a
, buf
, options
, 0);
277 static int say_date_generic(struct ast_channel
*chan
, time_t t
,
278 const char *ints
, const char *lang
, const char *format
, const char *timezone
, const char *prefix
)
282 struct timeval tv
= { t
, 0 };
283 say_args_t a
= { chan
, ints
, lang
, -1, -1 };
287 ast_localtime(&tv
, &tm
, NULL
);
288 snprintf(buf
, sizeof(buf
), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
299 return do_say(&a
, buf
, NULL
, 0);
302 static int say_date_with_format(struct ast_channel
*chan
, time_t t
,
303 const char *ints
, const char *lang
, const char *format
, const char *timezone
)
305 return say_date_generic(chan
, t
, ints
, lang
, format
, timezone
, "datetime");
308 static int say_date(struct ast_channel
*chan
, time_t t
, const char *ints
, const char *lang
)
310 return say_date_generic(chan
, t
, ints
, lang
, "", NULL
, "date");
313 static int say_time(struct ast_channel
*chan
, time_t t
, const char *ints
, const char *lang
)
315 return say_date_generic(chan
, t
, ints
, lang
, "", NULL
, "time");
318 static int say_datetime(struct ast_channel
*chan
, time_t t
, const char *ints
, const char *lang
)
320 return say_date_generic(chan
, t
, ints
, lang
, "", NULL
, "datetime");
324 * remap the 'say' functions to use those in this file
326 static int say_init_mode(const char *mode
) {
327 if (!strcmp(mode
, say_new
)) {
328 if (say_cfg
== NULL
) {
329 ast_log(LOG_ERROR
, "There is no say.conf file to use new mode\n");
332 save_say_mode(say_new
);
333 ast_say_number_full
= say_number_full
;
335 ast_say_enumeration_full
= say_enumeration_full
;
337 ast_say_digits_full
= say_digits_full
;
338 ast_say_digit_str_full
= say_digit_str_full
;
339 ast_say_character_str_full
= say_character_str_full
;
340 ast_say_phonetic_str_full
= say_phonetic_str_full
;
341 ast_say_datetime_from_now
= say_datetime_from_now
;
343 ast_say_datetime
= say_datetime
;
344 ast_say_time
= say_time
;
345 ast_say_date
= say_date
;
346 ast_say_date_with_format
= say_date_with_format
;
347 } else if (!strcmp(mode
, say_old
) && say_api_buf
[0] == say_new
) {
348 restore_say_mode(NULL
);
349 } else if (strcmp(mode
, say_old
)) {
350 ast_log(LOG_WARNING
, "unrecognized mode %s\n", mode
);
357 static char *__say_cli_init(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
359 const char *old_mode
= say_api_buf
[0] ? say_new
: say_old
;
363 e
->command
= "say load [new|old]";
365 "Usage: say load [new|old]\n"
367 " Report status of current say mode\n"
369 " Set say method, configured in say.conf\n"
371 " Set old say method, coded in asterisk core\n";
377 ast_cli(a
->fd
, "say mode is [%s]\n", old_mode
);
379 } else if (a
->argc
!= e
->args
)
380 return CLI_SHOWUSAGE
;
382 if (!strcmp(mode
, old_mode
))
383 ast_cli(a
->fd
, "say mode is %s already\n", mode
);
385 if (say_init_mode(mode
) == 0)
386 ast_cli(a
->fd
, "setting say mode from %s to %s\n", old_mode
, mode
);
391 static struct ast_cli_entry cli_playback
[] = {
392 AST_CLI_DEFINE(__say_cli_init
, "Set or show the say mode"),
395 static int playback_exec(struct ast_channel
*chan
, void *data
)
402 int option_noanswer
= 0;
404 AST_DECLARE_APP_ARGS(args
,
405 AST_APP_ARG(filenames
);
406 AST_APP_ARG(options
);
409 if (ast_strlen_zero(data
)) {
410 ast_log(LOG_WARNING
, "Playback requires an argument (filename)\n");
414 tmp
= ast_strdupa(data
);
415 AST_STANDARD_APP_ARGS(args
, tmp
);
418 if (strcasestr(args
.options
, "skip"))
420 if (strcasestr(args
.options
, "say"))
422 if (strcasestr(args
.options
, "noanswer"))
425 if (chan
->_state
!= AST_STATE_UP
) {
427 /* At the user's option, skip if the line is not up */
429 } else if (!option_noanswer
)
430 /* Otherwise answer unless we're supposed to send this while on-hook */
431 res
= ast_answer(chan
);
434 char *back
= args
.filenames
;
437 ast_stopstream(chan
);
438 while (!res
&& (front
= strsep(&back
, "&"))) {
440 res
= say_full(chan
, front
, "", chan
->language
, NULL
, -1, -1);
442 res
= ast_streamfile(chan
, front
, chan
->language
);
444 res
= ast_waitstream(chan
, "");
445 ast_stopstream(chan
);
447 ast_log(LOG_WARNING
, "ast_streamfile failed on %s for %s\n", chan
->name
, (char *)data
);
454 pbx_builtin_setvar_helper(chan
, "PLAYBACKSTATUS", mres
? "FAILED" : "SUCCESS");
458 static int reload(void)
460 struct ast_variable
*v
;
461 struct ast_flags config_flags
= { CONFIG_FLAG_FILEUNCHANGED
};
462 struct ast_config
*newcfg
;
464 if ((newcfg
= ast_config_load("say.conf", config_flags
)) == CONFIG_STATUS_FILEUNCHANGED
)
468 ast_config_destroy(say_cfg
);
469 ast_log(LOG_NOTICE
, "Reloading say.conf\n");
474 for (v
= ast_variable_browse(say_cfg
, "general"); v
; v
= v
->next
) {
475 if (ast_extension_match(v
->name
, "mode")) {
476 say_init_mode(v
->value
);
483 * XXX here we should sort rules according to the same order
484 * we have in pbx.c so we have the same matching behaviour.
489 static int unload_module(void)
493 res
= ast_unregister_application(app
);
495 ast_cli_unregister_multiple(cli_playback
, sizeof(cli_playback
) / sizeof(struct ast_cli_entry
));
498 ast_config_destroy(say_cfg
);
503 static int load_module(void)
505 struct ast_variable
*v
;
506 struct ast_flags config_flags
= { 0 };
508 say_cfg
= ast_config_load("say.conf", config_flags
);
510 for (v
= ast_variable_browse(say_cfg
, "general"); v
; v
= v
->next
) {
511 if (ast_extension_match(v
->name
, "mode")) {
512 say_init_mode(v
->value
);
518 ast_cli_register_multiple(cli_playback
, sizeof(cli_playback
) / sizeof(struct ast_cli_entry
));
519 return ast_register_application(app
, playback_exec
, synopsis
, descrip
);
522 AST_MODULE_INFO(ASTERISK_GPL_KEY
, AST_MODFLAG_DEFAULT
, "Sound File Playback Application",
524 .unload
= unload_module
,