Fix a few things I missed to ensure zt_chan_conf structure is not modified in mkintf
[asterisk-bristuff.git] / apps / app_festival.c
blob2aceceefdb0ced2bbd9742a1006e830354fc2581
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2002, Christos Ricudis
6 * Christos Ricudis <ricudis@itc.auth.gr>
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 Connect to festival
23 * \author Christos Ricudis <ricudis@itc.auth.gr>
25 * \ingroup applications
28 #include "asterisk.h"
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32 #include <sys/types.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <netdb.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include <stdio.h>
43 #include <signal.h>
44 #include <stdlib.h>
45 #include <unistd.h>
46 #include <fcntl.h>
47 #include <ctype.h>
49 #include "asterisk/file.h"
50 #include "asterisk/logger.h"
51 #include "asterisk/channel.h"
52 #include "asterisk/pbx.h"
53 #include "asterisk/module.h"
54 #include "asterisk/md5.h"
55 #include "asterisk/config.h"
56 #include "asterisk/utils.h"
57 #include "asterisk/lock.h"
58 #include "asterisk/options.h"
60 #define FESTIVAL_CONFIG "festival.conf"
62 static char *app = "Festival";
64 static char *synopsis = "Say text to the user";
66 static char *descrip =
67 " Festival(text[|intkeys]): Connect to Festival, send the argument, get back the waveform,"
68 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
69 "the value, or 'any' to allow any number back (useful in dialplan)\n";
72 static char *socket_receive_file_to_buff(int fd,int *size)
74 /* Receive file (probably a waveform file) from socket using */
75 /* Festival key stuff technique, but long winded I know, sorry */
76 /* but will receive any file without closeing the stream or */
77 /* using OOB data */
78 static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
79 char *buff;
80 int bufflen;
81 int n,k,i;
82 char c;
84 bufflen = 1024;
85 if (!(buff = ast_malloc(bufflen)))
87 /* TODO: Handle memory allocation failure */
89 *size=0;
91 for (k=0; file_stuff_key[k] != '\0';)
93 n = read(fd,&c,1);
94 if (n==0) break; /* hit stream eof before end of file */
95 if ((*size)+k+1 >= bufflen)
96 { /* +1 so you can add a NULL if you want */
97 bufflen += bufflen/4;
98 if (!(buff = ast_realloc(buff, bufflen)))
100 /* TODO: Handle memory allocation failure */
103 if (file_stuff_key[k] == c)
104 k++;
105 else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
106 { /* It looked like the key but wasn't */
107 for (i=0; i < k; i++,(*size)++)
108 buff[*size] = file_stuff_key[i];
109 k=0;
110 /* omit the stuffed 'X' */
112 else
114 for (i=0; i < k; i++,(*size)++)
115 buff[*size] = file_stuff_key[i];
116 k=0;
117 buff[*size] = c;
118 (*size)++;
123 return buff;
126 static int send_waveform_to_fd(char *waveform, int length, int fd) {
128 int res;
129 int x;
130 #ifdef __PPC__
131 char c;
132 #endif
133 sigset_t fullset, oldset;
135 sigfillset(&fullset);
136 pthread_sigmask(SIG_BLOCK, &fullset, &oldset);
138 res = fork();
139 if (res < 0)
140 ast_log(LOG_WARNING, "Fork failed\n");
141 if (res) {
142 pthread_sigmask(SIG_SETMASK, &oldset, NULL);
143 return res;
145 for (x=0;x<256;x++) {
146 if (x != fd)
147 close(x);
149 if (ast_opt_high_priority)
150 ast_set_priority(0);
151 signal(SIGPIPE, SIG_DFL);
152 pthread_sigmask(SIG_UNBLOCK, &fullset, NULL);
153 /*IAS */
154 #ifdef __PPC__
155 for( x=0; x<length; x+=2)
157 c = *(waveform+x+1);
158 *(waveform+x+1)=*(waveform+x);
159 *(waveform+x)=c;
161 #endif
163 write(fd,waveform,length);
164 close(fd);
165 exit(0);
169 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
170 int res=0;
171 int fds[2];
172 int ms = -1;
173 int pid = -1;
174 int needed = 0;
175 int owriteformat;
176 struct ast_frame *f;
177 struct myframe {
178 struct ast_frame f;
179 char offset[AST_FRIENDLY_OFFSET];
180 char frdata[2048];
181 } myf = {
182 .f = { 0, },
185 if (pipe(fds)) {
186 ast_log(LOG_WARNING, "Unable to create pipe\n");
187 return -1;
190 /* Answer if it's not already going */
191 if (chan->_state != AST_STATE_UP)
192 ast_answer(chan);
193 ast_stopstream(chan);
194 ast_indicate(chan, -1);
196 owriteformat = chan->writeformat;
197 res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
198 if (res < 0) {
199 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
200 return -1;
203 res=send_waveform_to_fd(waveform,length,fds[1]);
204 if (res >= 0) {
205 pid = res;
206 /* Order is important -- there's almost always going to be mp3... we want to prioritize the
207 user */
208 for (;;) {
209 ms = 1000;
210 res = ast_waitfor(chan, ms);
211 if (res < 1) {
212 res = -1;
213 break;
215 f = ast_read(chan);
216 if (!f) {
217 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
218 res = -1;
219 break;
221 if (f->frametype == AST_FRAME_DTMF) {
222 ast_log(LOG_DEBUG, "User pressed a key\n");
223 if (intkeys && strchr(intkeys, f->subclass)) {
224 res = f->subclass;
225 ast_frfree(f);
226 break;
229 if (f->frametype == AST_FRAME_VOICE) {
230 /* Treat as a generator */
231 needed = f->samples * 2;
232 if (needed > sizeof(myf.frdata)) {
233 ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
234 (int)sizeof(myf.frdata) / 2, needed/2);
235 needed = sizeof(myf.frdata);
237 res = read(fds[0], myf.frdata, needed);
238 if (res > 0) {
239 myf.f.frametype = AST_FRAME_VOICE;
240 myf.f.subclass = AST_FORMAT_SLINEAR;
241 myf.f.datalen = res;
242 myf.f.samples = res / 2;
243 myf.f.offset = AST_FRIENDLY_OFFSET;
244 myf.f.src = __PRETTY_FUNCTION__;
245 myf.f.data = myf.frdata;
246 if (ast_write(chan, &myf.f) < 0) {
247 res = -1;
248 ast_frfree(f);
249 break;
251 if (res < needed) { /* last frame */
252 ast_log(LOG_DEBUG, "Last frame\n");
253 res=0;
254 ast_frfree(f);
255 break;
257 } else {
258 ast_log(LOG_DEBUG, "No more waveform\n");
259 res = 0;
262 ast_frfree(f);
265 close(fds[0]);
266 close(fds[1]);
268 /* if (pid > -1) */
269 /* kill(pid, SIGKILL); */
270 if (!res && owriteformat)
271 ast_set_write_format(chan, owriteformat);
272 return res;
275 #define MAXLEN 180
276 #define MAXFESTLEN 2048
281 static int festival_exec(struct ast_channel *chan, void *vdata)
283 int usecache;
284 int res=0;
285 struct ast_module_user *u;
286 struct sockaddr_in serv_addr;
287 struct hostent *serverhost;
288 struct ast_hostent ahp;
289 int fd;
290 FILE *fs;
291 const char *host;
292 const char *cachedir;
293 const char *temp;
294 const char *festivalcommand;
295 int port=1314;
296 int n;
297 char ack[4];
298 char *waveform;
299 int filesize;
300 int wave;
301 char bigstring[MAXFESTLEN];
302 int i;
303 struct MD5Context md5ctx;
304 unsigned char MD5Res[16];
305 char MD5Hex[33] = "";
306 char koko[4] = "";
307 char cachefile[MAXFESTLEN]="";
308 int readcache=0;
309 int writecache=0;
310 int strln;
311 int fdesc = -1;
312 char buffer[16384];
313 int seekpos = 0;
314 char *data;
315 char *intstr;
316 struct ast_config *cfg;
317 char *newfestivalcommand;
319 if (ast_strlen_zero(vdata)) {
320 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
321 return -1;
324 u = ast_module_user_add(chan);
326 cfg = ast_config_load(FESTIVAL_CONFIG);
327 if (!cfg) {
328 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
329 ast_module_user_remove(u);
330 return -1;
332 if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
333 host = "localhost";
335 if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
336 port = 1314;
337 } else {
338 port = atoi(temp);
340 if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
341 usecache=0;
342 } else {
343 usecache = ast_true(temp);
345 if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
346 cachedir = "/tmp/";
348 if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
349 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
350 } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
351 int i, j;
352 newfestivalcommand = alloca(strlen(festivalcommand) + 1);
354 for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
355 if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
356 newfestivalcommand[j++] = '\n';
357 i++;
358 } else if (festivalcommand[i] == '\\') {
359 newfestivalcommand[j++] = festivalcommand[i + 1];
360 i++;
361 } else
362 newfestivalcommand[j++] = festivalcommand[i];
364 newfestivalcommand[j] = '\0';
365 festivalcommand = newfestivalcommand;
368 data = ast_strdupa(vdata);
370 intstr = strchr(data, '|');
371 if (intstr) {
372 *intstr = '\0';
373 intstr++;
374 if (!strcasecmp(intstr, "any"))
375 intstr = AST_DIGIT_ANY;
378 ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
379 /* Connect to local festival server */
381 fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
383 if (fd < 0) {
384 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
385 ast_config_destroy(cfg);
386 ast_module_user_remove(u);
387 return -1;
389 memset(&serv_addr, 0, sizeof(serv_addr));
390 if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
391 /* its a name rather than an ipnum */
392 serverhost = ast_gethostbyname(host, &ahp);
393 if (serverhost == (struct hostent *)0) {
394 ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
395 ast_config_destroy(cfg);
396 ast_module_user_remove(u);
397 return -1;
399 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
401 serv_addr.sin_family = AF_INET;
402 serv_addr.sin_port = htons(port);
404 if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
405 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
406 ast_config_destroy(cfg);
407 ast_module_user_remove(u);
408 return -1;
411 /* Compute MD5 sum of string */
412 MD5Init(&md5ctx);
413 MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
414 MD5Final(MD5Res,&md5ctx);
415 MD5Hex[0] = '\0';
417 /* Convert to HEX and look if there is any matching file in the cache
418 directory */
419 for (i=0;i<16;i++) {
420 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
421 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
423 readcache=0;
424 writecache=0;
425 if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
426 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
427 fdesc=open(cachefile,O_RDWR);
428 if (fdesc==-1) {
429 fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
430 if (fdesc!=-1) {
431 writecache=1;
432 strln=strlen((char *)data);
433 ast_log(LOG_DEBUG,"line length : %d\n",strln);
434 write(fdesc,&strln,sizeof(int));
435 write(fdesc,data,strln);
436 seekpos=lseek(fdesc,0,SEEK_CUR);
437 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
439 } else {
440 read(fdesc,&strln,sizeof(int));
441 ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
442 if (strlen((char *)data)==strln) {
443 ast_log(LOG_DEBUG,"Size OK\n");
444 read(fdesc,&bigstring,strln);
445 bigstring[strln] = 0;
446 if (strcmp(bigstring,data)==0) {
447 readcache=1;
448 } else {
449 ast_log(LOG_WARNING,"Strings do not match\n");
451 } else {
452 ast_log(LOG_WARNING,"Size mismatch\n");
457 if (readcache==1) {
458 close(fd);
459 fd=fdesc;
460 ast_log(LOG_DEBUG,"Reading from cache...\n");
461 } else {
462 ast_log(LOG_DEBUG,"Passing text to festival...\n");
463 fs=fdopen(dup(fd),"wb");
464 fprintf(fs,festivalcommand,(char *)data);
465 fflush(fs);
466 fclose(fs);
469 /* Write to cache and then pass it down */
470 if (writecache==1) {
471 ast_log(LOG_DEBUG,"Writing result to cache...\n");
472 while ((strln=read(fd,buffer,16384))!=0) {
473 write(fdesc,buffer,strln);
475 close(fd);
476 close(fdesc);
477 fd=open(cachefile,O_RDWR);
478 lseek(fd,seekpos,SEEK_SET);
481 ast_log(LOG_DEBUG,"Passing data to channel...\n");
483 /* Read back info from server */
484 /* This assumes only one waveform will come back, also LP is unlikely */
485 wave = 0;
486 do {
487 int read_data;
488 for (n=0; n < 3; )
490 read_data = read(fd,ack+n,3-n);
491 /* this avoids falling in infinite loop
492 * in case that festival server goes down
493 * */
494 if ( read_data == -1 )
496 ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
497 close(fd);
498 ast_config_destroy(cfg);
499 ast_module_user_remove(u);
500 return -1;
502 n += read_data;
504 ack[3] = '\0';
505 if (strcmp(ack,"WV\n") == 0) { /* receive a waveform */
506 ast_log(LOG_DEBUG,"Festival WV command\n");
507 waveform = socket_receive_file_to_buff(fd,&filesize);
508 res = send_waveform_to_channel(chan,waveform,filesize, intstr);
509 free(waveform);
510 break;
512 else if (strcmp(ack,"LP\n") == 0) { /* receive an s-expr */
513 ast_log(LOG_DEBUG,"Festival LP command\n");
514 waveform = socket_receive_file_to_buff(fd,&filesize);
515 waveform[filesize]='\0';
516 ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
517 free(waveform);
518 } else if (strcmp(ack,"ER\n") == 0) { /* server got an error */
519 ast_log(LOG_WARNING,"Festival returned ER\n");
520 res=-1;
521 break;
523 } while (strcmp(ack,"OK\n") != 0);
524 close(fd);
525 ast_config_destroy(cfg);
526 ast_module_user_remove(u);
527 return res;
531 static int unload_module(void)
533 int res;
535 res = ast_unregister_application(app);
537 ast_module_user_hangup_all();
539 return res;
542 static int load_module(void)
544 struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
545 if (!cfg) {
546 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
547 return AST_MODULE_LOAD_DECLINE;
549 ast_config_destroy(cfg);
550 return ast_register_application(app, festival_exec, synopsis, descrip);
553 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");