Merged revisions 43933 via svnmerge from
[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 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;
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                                 ast_log(LOG_DEBUG, "User pressed a key\n");
214                                 if (intkeys && strchr(intkeys, f->subclass)) {
215                                         res = f->subclass;
216                                         ast_frfree(f);
217                                         break;
218                                 }
219                         }
220                         if (f->frametype == AST_FRAME_VOICE) {
221                                 /* Treat as a generator */
222                                 needed = f->samples * 2;
223                                 if (needed > sizeof(myf.frdata)) {
224                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
225                                                 (int)sizeof(myf.frdata) / 2, needed/2);
226                                         needed = sizeof(myf.frdata);
227                                 }
228                                 res = read(fds[0], myf.frdata, needed);
229                                 if (res > 0) {
230                                         myf.f.frametype = AST_FRAME_VOICE;
231                                         myf.f.subclass = AST_FORMAT_SLINEAR;
232                                         myf.f.datalen = res;
233                                         myf.f.samples = res / 2;
234                                         myf.f.mallocd = 0;
235                                         myf.f.offset = AST_FRIENDLY_OFFSET;
236                                         myf.f.src = __PRETTY_FUNCTION__;
237                                         myf.f.data = myf.frdata;
238                                         if (ast_write(chan, &myf.f) < 0) {
239                                                 res = -1;
240                                                 ast_frfree(f);
241                                                 break;
242                                         }
243                                         if (res < needed) { /* last frame */
244                                                 ast_log(LOG_DEBUG, "Last frame\n");
245                                                 res=0;
246                                                 ast_frfree(f);
247                                                 break;
248                                         }
249                                 } else {
250                                         ast_log(LOG_DEBUG, "No more waveform\n");
251                                         res = 0;
252                                 }
253                         }
254                         ast_frfree(f);
255                 }
256         }
257         close(fds[0]);
258         close(fds[1]);
259
260 /*      if (pid > -1) */
261 /*              kill(pid, SIGKILL); */
262         if (!res && owriteformat)
263                 ast_set_write_format(chan, owriteformat);
264         return res;
265 }
266
267 #define MAXLEN 180
268 #define MAXFESTLEN 2048
269
270
271
272
273 static int festival_exec(struct ast_channel *chan, void *vdata)
274 {
275         int usecache;
276         int res=0;
277         struct ast_module_user *u;
278         struct sockaddr_in serv_addr;
279         struct hostent *serverhost;
280         struct ast_hostent ahp;
281         int fd;
282         FILE *fs;
283         const char *host;
284         const char *cachedir;
285         const char *temp;
286         const char *festivalcommand;
287         int port=1314;
288         int n;
289         char ack[4];
290         char *waveform;
291         int filesize;
292         int wave;
293         char bigstring[MAXFESTLEN];
294         int i;
295         struct MD5Context md5ctx;
296         unsigned char MD5Res[16];
297         char MD5Hex[33] = "";
298         char koko[4] = "";
299         char cachefile[MAXFESTLEN]="";
300         int readcache=0;
301         int writecache=0;
302         int strln;
303         int fdesc = -1;
304         char buffer[16384];
305         int seekpos = 0;        
306         char *data;     
307         char *intstr;
308         struct ast_config *cfg;
309         char *newfestivalcommand;
310
311         if (ast_strlen_zero(vdata)) {
312                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
313                 return -1;
314         }
315
316         u = ast_module_user_add(chan);
317
318         cfg = ast_config_load(FESTIVAL_CONFIG);
319         if (!cfg) {
320                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
321                 ast_module_user_remove(u);
322                 return -1;
323         }
324         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
325                 host = "localhost";
326         }
327         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
328                 port = 1314;
329         } else {
330                 port = atoi(temp);
331         }
332         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
333                 usecache=0;
334         } else {
335                 usecache = ast_true(temp);
336         }
337         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
338                 cachedir = "/tmp/";
339         }
340         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
341                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
342         } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
343                 int i, j;
344                 newfestivalcommand = alloca(strlen(festivalcommand) + 1);
345
346                 for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
347                         if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
348                                 newfestivalcommand[j++] = '\n';
349                                 i++;
350                         } else if (festivalcommand[i] == '\\') {
351                                 newfestivalcommand[j++] = festivalcommand[i + 1];
352                                 i++;
353                         } else
354                                 newfestivalcommand[j++] = festivalcommand[i];
355                 }
356                 newfestivalcommand[j] = '\0';
357                 festivalcommand = newfestivalcommand;
358         }
359         
360         data = ast_strdupa(vdata);
361
362         intstr = strchr(data, '|');
363         if (intstr) {   
364                 *intstr = '\0';
365                 intstr++;
366                 if (!strcasecmp(intstr, "any"))
367                         intstr = AST_DIGIT_ANY;
368         }
369         
370         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
371         /* Connect to local festival server */
372         
373         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
374
375         if (fd < 0) {
376                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
377                 ast_config_destroy(cfg);
378                 ast_module_user_remove(u);
379                 return -1;
380         }
381         memset(&serv_addr, 0, sizeof(serv_addr));
382         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
383                 /* its a name rather than an ipnum */
384                 serverhost = ast_gethostbyname(host, &ahp);
385                 if (serverhost == (struct hostent *)0) {
386                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
387                         ast_config_destroy(cfg);
388                         ast_module_user_remove(u);
389                         return -1;
390                 }
391                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
392         }
393         serv_addr.sin_family = AF_INET;
394         serv_addr.sin_port = htons(port);
395
396         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
397                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
398                 ast_config_destroy(cfg);
399                 ast_module_user_remove(u);
400                 return -1;
401         }
402         
403         /* Compute MD5 sum of string */
404         MD5Init(&md5ctx);
405         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
406         MD5Final(MD5Res,&md5ctx);
407                 MD5Hex[0] = '\0';
408         
409         /* Convert to HEX and look if there is any matching file in the cache 
410                 directory */
411         for (i=0;i<16;i++) {
412                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
413                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
414         }
415         readcache=0;
416         writecache=0;
417         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
418                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
419                 fdesc=open(cachefile,O_RDWR);
420                 if (fdesc==-1) {
421                         fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
422                         if (fdesc!=-1) {
423                                 writecache=1;
424                                 strln=strlen((char *)data);
425                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
426                                 write(fdesc,&strln,sizeof(int));
427                                 write(fdesc,data,strln);
428                                 seekpos=lseek(fdesc,0,SEEK_CUR);
429                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
430                         }
431                 } else {
432                         read(fdesc,&strln,sizeof(int));
433                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
434                         if (strlen((char *)data)==strln) {
435                                 ast_log(LOG_DEBUG,"Size OK\n");
436                                 read(fdesc,&bigstring,strln);
437                                 bigstring[strln] = 0;
438                                 if (strcmp(bigstring,data)==0) { 
439                                         readcache=1;
440                                 } else {
441                                         ast_log(LOG_WARNING,"Strings do not match\n");
442                                 }
443                         } else {
444                                 ast_log(LOG_WARNING,"Size mismatch\n");
445                         }
446                 }
447         }
448                         
449         if (readcache==1) {
450                 close(fd);
451                 fd=fdesc;
452                 ast_log(LOG_DEBUG,"Reading from cache...\n");
453         } else {
454                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
455                 fs=fdopen(dup(fd),"wb");
456                 fprintf(fs,festivalcommand,(char *)data);
457                 fflush(fs);
458                 fclose(fs);
459         }
460         
461         /* Write to cache and then pass it down */
462         if (writecache==1) {
463                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
464                 while ((strln=read(fd,buffer,16384))!=0) {
465                         write(fdesc,buffer,strln);
466                 }
467                 close(fd);
468                 close(fdesc);
469                 fd=open(cachefile,O_RDWR);
470                 lseek(fd,seekpos,SEEK_SET);
471         }
472         
473         ast_log(LOG_DEBUG,"Passing data to channel...\n");
474         
475         /* Read back info from server */
476         /* This assumes only one waveform will come back, also LP is unlikely */
477         wave = 0;
478         do {
479                int read_data;
480                 for (n=0; n < 3; )
481                {
482                        read_data = read(fd,ack+n,3-n);
483                        /* this avoids falling in infinite loop
484                         * in case that festival server goes down
485                         * */
486                        if ( read_data == -1 )
487                        {
488                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd\n");
489                                close(fd);
490                                ast_config_destroy(cfg);
491                                ast_module_user_remove(u);
492                                return -1;
493                        }
494                        n += read_data;
495                }
496                 ack[3] = '\0';
497                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
498                         ast_log(LOG_DEBUG,"Festival WV command\n");
499                         waveform = socket_receive_file_to_buff(fd,&filesize);
500                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
501                         free(waveform);
502                         break;
503                 }
504                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
505                         ast_log(LOG_DEBUG,"Festival LP command\n");
506                         waveform = socket_receive_file_to_buff(fd,&filesize);
507                         waveform[filesize]='\0';
508                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
509                         free(waveform);
510                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
511                         ast_log(LOG_WARNING,"Festival returned ER\n");
512                         res=-1;
513                         break;
514                 }
515         } while (strcmp(ack,"OK\n") != 0);
516         close(fd);
517         ast_config_destroy(cfg);
518         ast_module_user_remove(u);
519         return res;
520
521 }
522
523 static int unload_module(void)
524 {
525         int res;
526
527         res = ast_unregister_application(app);
528
529         ast_module_user_hangup_all();
530
531         return res;
532 }
533
534 static int load_module(void)
535 {
536         struct ast_config *cfg = ast_config_load(FESTIVAL_CONFIG);
537         if (!cfg) {
538                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
539                 return AST_MODULE_LOAD_DECLINE;
540         }
541         ast_config_destroy(cfg);
542         return ast_register_application(app, festival_exec, synopsis, descrip);
543 }
544
545 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Simple Festival Interface");