e0245987387820b2321ef0402f538fa6353d51c0
[asterisk/asterisk.git] / apps / app_festival.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2002, Christos Ricudis
5  *
6  * Christos Ricudis <ricudis@itc.auth.gr>
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 Connect to festival
22  *
23  * \author Christos Ricudis <ricudis@itc.auth.gr>
24  * 
25  * \ingroup applications
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
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>
48
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"
59
60 #define FESTIVAL_CONFIG "festival.conf"
61
62 static char *app = "Festival";
63
64 static char *synopsis = "Say text to the user";
65
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";
70
71
72 static char *socket_receive_file_to_buff(int fd,int *size)
73 {
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 closing 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;
83
84     bufflen = 1024;
85     if (!(buff = ast_malloc(bufflen)))
86     {
87         /* TODO: Handle memory allocation failure */
88     }
89     *size=0;
90
91     for (k=0; file_stuff_key[k] != '\0';)
92     {
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)))
99             {
100                 /* TODO: Handle memory allocation failure */
101             }
102         }
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' */
111         }
112         else
113         {
114             for (i=0; i < k; i++,(*size)++)
115                 buff[*size] = file_stuff_key[i];
116             k=0;
117             buff[*size] = c;
118             (*size)++;
119         }
120
121     }
122
123     return buff;
124 }
125
126 static int send_waveform_to_fd(char *waveform, int length, int fd) {
127
128         int res;
129         int x;
130 #ifdef __PPC__ 
131         char c;
132 #endif
133
134         res = fork();
135         if (res < 0)
136                 ast_log(LOG_WARNING, "Fork failed\n");
137         if (res)
138                 return res;
139         for (x=0;x<256;x++) {
140                 if (x != fd)
141                         close(x);
142         }
143         if (ast_opt_high_priority)
144                 ast_set_priority(0);
145
146 /*IAS */
147 #ifdef __PPC__  
148         for( x=0; x<length; x+=2)
149         {
150                 c = *(waveform+x+1);
151                 *(waveform+x+1)=*(waveform+x);
152                 *(waveform+x)=c;
153         }
154 #endif
155         
156         write(fd,waveform,length);
157         close(fd);
158         exit(0);
159 }
160
161
162 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
163         int res=0;
164         int fds[2];
165         int ms = -1;
166         int pid = -1;
167         int needed = 0;
168         int owriteformat;
169         struct ast_frame *f;
170         struct myframe {
171                 struct ast_frame f;
172                 char offset[AST_FRIENDLY_OFFSET];
173                 char frdata[2048];
174         } myf;
175         
176         if (pipe(fds)) {
177                  ast_log(LOG_WARNING, "Unable to create pipe\n");
178                 return -1;
179         }
180                                                         
181         /* Answer if it's not already going */
182         if (chan->_state != AST_STATE_UP)
183                 ast_answer(chan);
184         ast_stopstream(chan);
185         ast_indicate(chan, -1);
186         
187         owriteformat = chan->writeformat;
188         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
189         if (res < 0) {
190                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
191                 return -1;
192         }
193         
194         res=send_waveform_to_fd(waveform,length,fds[1]);
195         if (res >= 0) {
196                 pid = res;
197                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
198                    user */
199                 for (;;) {
200                         ms = 1000;
201                         res = ast_waitfor(chan, ms);
202                         if (res < 1) {
203                                 res = -1;
204                                 break;
205                         }
206                         f = ast_read(chan);
207                         if (!f) {
208                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
209                                 res = -1;
210                                 break;
211                         }
212                         if (f->frametype == AST_FRAME_DTMF) {
213                                 if (option_debug)
214                                         ast_log(LOG_DEBUG, "User pressed a key\n");
215                                 if (intkeys && strchr(intkeys, f->subclass)) {
216                                         res = f->subclass;
217                                         ast_frfree(f);
218                                         break;
219                                 }
220                         }
221                         if (f->frametype == AST_FRAME_VOICE) {
222                                 /* Treat as a generator */
223                                 needed = f->samples * 2;
224                                 if (needed > sizeof(myf.frdata)) {
225                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
226                                                 (int)sizeof(myf.frdata) / 2, needed/2);
227                                         needed = sizeof(myf.frdata);
228                                 }
229                                 res = read(fds[0], myf.frdata, needed);
230                                 if (res > 0) {
231                                         myf.f.frametype = AST_FRAME_VOICE;
232                                         myf.f.subclass = AST_FORMAT_SLINEAR;
233                                         myf.f.datalen = res;
234                                         myf.f.samples = res / 2;
235                                         myf.f.mallocd = 0;
236                                         myf.f.offset = AST_FRIENDLY_OFFSET;
237                                         myf.f.src = __PRETTY_FUNCTION__;
238                                         myf.f.data = myf.frdata;
239                                         if (ast_write(chan, &myf.f) < 0) {
240                                                 res = -1;
241                                                 ast_frfree(f);
242                                                 break;
243                                         }
244                                         if (res < needed) { /* last frame */
245                                                 if (option_debug)
246                                                         ast_log(LOG_DEBUG, "Last frame\n");
247                                                 res=0;
248                                                 ast_frfree(f);
249                                                 break;
250                                         }
251                                 } else {
252                                         if (option_debug)
253                                                 ast_log(LOG_DEBUG, "No more waveform\n");
254                                         res = 0;
255                                 }
256                         }
257                         ast_frfree(f);
258                 }
259         }
260         close(fds[0]);
261         close(fds[1]);
262
263 /*      if (pid > -1) */
264 /*              kill(pid, SIGKILL); */
265         if (!res && owriteformat)
266                 ast_set_write_format(chan, owriteformat);
267         return res;
268 }
269
270 #define MAXLEN 180
271 #define MAXFESTLEN 2048
272
273
274
275
276 static int festival_exec(struct ast_channel *chan, void *vdata)
277 {
278         int usecache;
279         int res=0;
280         struct ast_module_user *u;
281         struct sockaddr_in serv_addr;
282         struct hostent *serverhost;
283         struct ast_hostent ahp;
284         int fd;
285         FILE *fs;
286         const char *host;
287         const char *cachedir;
288         const char *temp;
289         const char *festivalcommand;
290         int port=1314;
291         int n;
292         char ack[4];
293         char *waveform;
294         int filesize;
295         int wave;
296         char bigstring[MAXFESTLEN];
297         int i;
298         struct MD5Context md5ctx;
299         unsigned char MD5Res[16];
300         char MD5Hex[33] = "";
301         char koko[4] = "";
302         char cachefile[MAXFESTLEN]="";
303         int readcache=0;
304         int writecache=0;
305         int strln;
306         int fdesc = -1;
307         char buffer[16384];
308         int seekpos = 0;        
309         char *data;     
310         char *intstr;
311         struct ast_config *cfg;
312         char *newfestivalcommand;
313
314         if (ast_strlen_zero(vdata)) {
315                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
316                 return -1;
317         }
318
319         u = ast_module_user_add(chan);
320
321         cfg = ast_config_load(FESTIVAL_CONFIG);
322         if (!cfg) {
323                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
324                 ast_module_user_remove(u);
325                 return -1;
326         }
327         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
328                 host = "localhost";
329         }
330         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
331                 port = 1314;
332         } else {
333                 port = atoi(temp);
334         }
335         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
336                 usecache=0;
337         } else {
338                 usecache = ast_true(temp);
339         }
340         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
341                 cachedir = "/tmp/";
342         }
343         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
344                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
345         } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
346                 int i, j;
347                 newfestivalcommand = alloca(strlen(festivalcommand) + 1);
348
349                 for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
350                         if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
351                                 newfestivalcommand[j++] = '\n';
352                                 i++;
353                         } else if (festivalcommand[i] == '\\') {
354                                 newfestivalcommand[j++] = festivalcommand[i + 1];
355                                 i++;
356                         } else
357                                 newfestivalcommand[j++] = festivalcommand[i];
358                 }
359                 newfestivalcommand[j] = '\0';
360                 festivalcommand = newfestivalcommand;
361         }
362         
363         data = ast_strdupa(vdata);
364
365         intstr = strchr(data, '|');
366         if (intstr) {   
367                 *intstr = '\0';
368                 intstr++;
369                 if (!strcasecmp(intstr, "any"))
370                         intstr = AST_DIGIT_ANY;
371         }
372         
373         if (option_debug)
374                 ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
375         /* Connect to local festival server */
376         
377         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
378
379         if (fd < 0) {
380                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
381                 ast_config_destroy(cfg);
382                 ast_module_user_remove(u);
383                 return -1;
384         }
385
386         memset(&serv_addr, 0, sizeof(serv_addr));
387
388         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
389                 /* its a name rather than an ipnum */
390                 serverhost = ast_gethostbyname(host, &ahp);
391
392                 if (serverhost == (struct hostent *)0) {
393                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
394                         ast_config_destroy(cfg);
395                         ast_module_user_remove(u);
396                         return -1;
397                 }
398                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
399         }
400
401         serv_addr.sin_family = AF_INET;
402         serv_addr.sin_port = htons(port);
403
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;
409         }
410         
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';
416         
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);
422         }
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                                 if (option_debug)
434                                         ast_log(LOG_DEBUG,"line length : %d\n",strln);
435                                 write(fdesc,&strln,sizeof(int));
436                                 write(fdesc,data,strln);
437                                 seekpos=lseek(fdesc,0,SEEK_CUR);
438                                 if (option_debug)
439                                         ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
440                         }
441                 } else {
442                         read(fdesc,&strln,sizeof(int));
443                         if (option_debug)
444                                 ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
445                         if (strlen((char *)data)==strln) {
446                                 if (option_debug)
447                                         ast_log(LOG_DEBUG,"Size OK\n");
448                                 read(fdesc,&bigstring,strln);
449                                 bigstring[strln] = 0;
450                                 if (strcmp(bigstring,data)==0) { 
451                                         readcache=1;
452                                 } else {
453                                         ast_log(LOG_WARNING,"Strings do not match\n");
454                                 }
455                         } else {
456                                 ast_log(LOG_WARNING,"Size mismatch\n");
457                         }
458                 }
459         }
460                         
461         if (readcache==1) {
462                 close(fd);
463                 fd=fdesc;
464                 if (option_debug)
465                         ast_log(LOG_DEBUG,"Reading from cache...\n");
466         } else {
467                 if (option_debug)
468                         ast_log(LOG_DEBUG,"Passing text to festival...\n");
469                 fs=fdopen(dup(fd),"wb");
470                 fprintf(fs,festivalcommand,(char *)data);
471                 fflush(fs);
472                 fclose(fs);
473         }
474         
475         /* Write to cache and then pass it down */
476         if (writecache==1) {
477                 if (option_debug)
478                         ast_log(LOG_DEBUG,"Writing result to cache...\n");
479                 while ((strln=read(fd,buffer,16384))!=0) {
480                         write(fdesc,buffer,strln);
481                 }
482                 close(fd);
483                 close(fdesc);
484                 fd=open(cachefile,O_RDWR);
485                 lseek(fd,seekpos,SEEK_SET);
486         }
487         
488         if (option_debug)
489                 ast_log(LOG_DEBUG,"Passing data to channel...\n");
490         
491         /* Read back info from server */
492         /* This assumes only one waveform will come back, also LP is unlikely */
493         wave = 0;
494         do {
495                int read_data;
496                 for (n=0; n < 3; )
497                {
498                        read_data = read(fd,ack+n,3-n);
499                        /* this avoids falling in infinite loop
500                         * in case that festival server goes down
501                         * */
502                        if ( read_data == -1 )
503                        {
504                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
505                                close(fd);
506                                ast_config_destroy(cfg);
507                                ast_module_user_remove(u);
508                                return -1;
509                        }
510                        n += read_data;
511                }
512                 ack[3] = '\0';
513                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
514                         if (option_debug)
515                                 ast_log(LOG_DEBUG,"Festival WV command\n");
516                         waveform = socket_receive_file_to_buff(fd,&filesize);
517                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
518                         free(waveform);
519                         break;
520                 }
521                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
522                         if (option_debug)
523                                 ast_log(LOG_DEBUG,"Festival LP command\n");
524                         waveform = socket_receive_file_to_buff(fd,&filesize);
525                         waveform[filesize]='\0';
526                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
527                         free(waveform);
528                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
529                         ast_log(LOG_WARNING,"Festival returned ER\n");
530                         res=-1;
531                         break;
532                 }
533         } while (strcmp(ack,"OK\n") != 0);
534         close(fd);
535         ast_config_destroy(cfg);
536         ast_module_user_remove(u);
537         return res;
538
539 }
540
541 static int unload_module(void)
542 {
543         int res;
544
545         res = ast_unregister_application(app);
546
547         ast_module_user_hangup_all();
548
549         return res;
550 }
551
552 static int load_module(void)
553 {
554         struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
555         if (!cfg) {
556                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
557                 return AST_MODULE_LOAD_DECLINE;
558         }
559         ast_config_destroy(cfg);
560         return ast_register_application(app, festival_exec, synopsis, descrip);
561 }
562
563 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");