Merged revisions 33513 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 LOCAL_USER_DECL;
72
73 static char *socket_receive_file_to_buff(int fd,int *size)
74 {
75     /* Receive file (probably a waveform file) from socket using   */
76     /* Festival key stuff technique, but long winded I know, sorry */
77     /* but will receive any file without closeing the stream or    */
78     /* using OOB data                                              */
79     static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
80     char *buff;
81     int bufflen;
82     int n,k,i;
83     char c;
84
85     bufflen = 1024;
86     if (!(buff = ast_malloc(bufflen)))
87     {
88         /* TODO: Handle memory allocation failure */
89     }
90     *size=0;
91
92     for (k=0; file_stuff_key[k] != '\0';)
93     {
94         n = read(fd,&c,1);
95         if (n==0) break;  /* hit stream eof before end of file */
96         if ((*size)+k+1 >= bufflen)
97         {   /* +1 so you can add a NULL if you want */
98             bufflen += bufflen/4;
99             if (!(buff = ast_realloc(buff, bufflen)))
100             {
101                 /* TODO: Handle memory allocation failure */
102             }
103         }
104         if (file_stuff_key[k] == c)
105             k++;
106         else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
107         {   /* It looked like the key but wasn't */
108             for (i=0; i < k; i++,(*size)++)
109                 buff[*size] = file_stuff_key[i];
110             k=0;
111             /* omit the stuffed 'X' */
112         }
113         else
114         {
115             for (i=0; i < k; i++,(*size)++)
116                 buff[*size] = file_stuff_key[i];
117             k=0;
118             buff[*size] = c;
119             (*size)++;
120         }
121
122     }
123
124     return buff;
125 }
126
127 static int send_waveform_to_fd(char *waveform, int length, int fd) {
128
129         int res;
130         int x;
131 #ifdef __PPC__ 
132         char c;
133 #endif
134
135         res = fork();
136         if (res < 0)
137                 ast_log(LOG_WARNING, "Fork failed\n");
138         if (res)
139                 return res;
140         for (x=0;x<256;x++) {
141                 if (x != fd)
142                         close(x);
143         }
144         if (ast_opt_high_priority)
145                 ast_set_priority(0);
146
147 /*IAS */
148 #ifdef __PPC__  
149         for( x=0; x<length; x+=2)
150         {
151                 c = *(waveform+x+1);
152                 *(waveform+x+1)=*(waveform+x);
153                 *(waveform+x)=c;
154         }
155 #endif
156         
157         write(fd,waveform,length);
158         close(fd);
159         exit(0);
160 }
161
162
163 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
164         int res=0;
165         int fds[2];
166         int ms = -1;
167         int pid = -1;
168         int needed = 0;
169         int owriteformat;
170         struct ast_frame *f;
171         struct myframe {
172                 struct ast_frame f;
173                 char offset[AST_FRIENDLY_OFFSET];
174                 char frdata[2048];
175         } myf;
176         
177         if (pipe(fds)) {
178                  ast_log(LOG_WARNING, "Unable to create pipe\n");
179                 return -1;
180         }
181                                                         
182         /* Answer if it's not already going */
183         if (chan->_state != AST_STATE_UP)
184                 ast_answer(chan);
185         ast_stopstream(chan);
186         ast_indicate(chan, -1);
187         
188         owriteformat = chan->writeformat;
189         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
190         if (res < 0) {
191                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
192                 return -1;
193         }
194         
195         res=send_waveform_to_fd(waveform,length,fds[1]);
196         if (res >= 0) {
197                 pid = res;
198                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
199                    user */
200                 for (;;) {
201                         ms = 1000;
202                         res = ast_waitfor(chan, ms);
203                         if (res < 1) {
204                                 res = -1;
205                                 break;
206                         }
207                         f = ast_read(chan);
208                         if (!f) {
209                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
210                                 res = -1;
211                                 break;
212                         }
213                         if (f->frametype == AST_FRAME_DTMF) {
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                                                 ast_log(LOG_DEBUG, "Last frame\n");
246                                                 res=0;
247                                                 ast_frfree(f);
248                                                 break;
249                                         }
250                                 } else {
251                                         ast_log(LOG_DEBUG, "No more waveform\n");
252                                         res = 0;
253                                 }
254                         }
255                         ast_frfree(f);
256                 }
257         }
258         close(fds[0]);
259         close(fds[1]);
260
261 /*      if (pid > -1) */
262 /*              kill(pid, SIGKILL); */
263         if (!res && owriteformat)
264                 ast_set_write_format(chan, owriteformat);
265         return res;
266 }
267
268 #define MAXLEN 180
269 #define MAXFESTLEN 2048
270
271
272
273
274 static int festival_exec(struct ast_channel *chan, void *vdata)
275 {
276         int usecache;
277         int res=0;
278         struct localuser *u;
279         struct sockaddr_in serv_addr;
280         struct hostent *serverhost;
281         struct ast_hostent ahp;
282         int fd;
283         FILE *fs;
284         char *host;
285         char *cachedir;
286         char *temp;
287         char *festivalcommand;
288         int port=1314;
289         int n;
290         char ack[4];
291         char *waveform;
292         int filesize;
293         int wave;
294         char bigstring[MAXFESTLEN];
295         int i;
296         struct MD5Context md5ctx;
297         unsigned char MD5Res[16];
298         char MD5Hex[33] = "";
299         char koko[4] = "";
300         char cachefile[MAXFESTLEN]="";
301         int readcache=0;
302         int writecache=0;
303         int strln;
304         int fdesc = -1;
305         char buffer[16384];
306         int seekpos = 0;        
307         char *data;     
308         char *intstr;
309         struct ast_config *cfg;
310         char *newfestivalcommand;
311
312         if (ast_strlen_zero(vdata)) {
313                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
314                 return -1;
315         }
316
317         LOCAL_USER_ADD(u);
318
319         cfg = ast_config_load(FESTIVAL_CONFIG);
320         if (!cfg) {
321                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
322                 LOCAL_USER_REMOVE(u);
323                 return -1;
324         }
325         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
326                 host = "localhost";
327         }
328         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
329                 port = 1314;
330         } else {
331                 port = atoi(temp);
332         }
333         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
334                 usecache=0;
335         } else {
336                 usecache = ast_true(temp);
337         }
338         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
339                 cachedir = "/tmp/";
340         }
341         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
342                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
343         } else { /* This else parses the festivalcommand that we're sent from the config file for \n's, etc */
344                 int i, j;
345                 newfestivalcommand = alloca(strlen(festivalcommand) + 1);
346
347                 for (i = 0, j = 0; i < strlen(festivalcommand); i++) {
348                         if (festivalcommand[i] == '\\' && festivalcommand[i + 1] == 'n') {
349                                 newfestivalcommand[j++] = '\n';
350                                 i++;
351                         } else if (festivalcommand[i] == '\\') {
352                                 newfestivalcommand[j++] = festivalcommand[i + 1];
353                                 i++;
354                         } else
355                                 newfestivalcommand[j++] = festivalcommand[i];
356                 }
357                 newfestivalcommand[j] = '\0';
358                 festivalcommand = newfestivalcommand;
359         }
360         
361         data = ast_strdupa(vdata);
362
363         intstr = strchr(data, '|');
364         if (intstr) {   
365                 *intstr = '\0';
366                 intstr++;
367                 if (!strcasecmp(intstr, "any"))
368                         intstr = AST_DIGIT_ANY;
369         }
370         
371         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
372         /* Connect to local festival server */
373         
374         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
375
376         if (fd < 0) {
377                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
378                 ast_config_destroy(cfg);
379                 LOCAL_USER_REMOVE(u);
380                 return -1;
381         }
382         memset(&serv_addr, 0, sizeof(serv_addr));
383         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
384                 /* its a name rather than an ipnum */
385                 serverhost = ast_gethostbyname(host, &ahp);
386                 if (serverhost == (struct hostent *)0) {
387                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
388                         ast_config_destroy(cfg);
389                         LOCAL_USER_REMOVE(u);
390                         return -1;
391                 }
392                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
393         }
394         serv_addr.sin_family = AF_INET;
395         serv_addr.sin_port = htons(port);
396
397         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
398                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
399                 ast_config_destroy(cfg);
400                 LOCAL_USER_REMOVE(u);
401                 return -1;
402         }
403         
404         /* Compute MD5 sum of string */
405         MD5Init(&md5ctx);
406         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
407         MD5Final(MD5Res,&md5ctx);
408                 MD5Hex[0] = '\0';
409         
410         /* Convert to HEX and look if there is any matching file in the cache 
411                 directory */
412         for (i=0;i<16;i++) {
413                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
414                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
415         }
416         readcache=0;
417         writecache=0;
418         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
419                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
420                 fdesc=open(cachefile,O_RDWR);
421                 if (fdesc==-1) {
422                         fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
423                         if (fdesc!=-1) {
424                                 writecache=1;
425                                 strln=strlen((char *)data);
426                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
427                                 write(fdesc,&strln,sizeof(int));
428                                 write(fdesc,data,strln);
429                                 seekpos=lseek(fdesc,0,SEEK_CUR);
430                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
431                         }
432                 } else {
433                         read(fdesc,&strln,sizeof(int));
434                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
435                         if (strlen((char *)data)==strln) {
436                                 ast_log(LOG_DEBUG,"Size OK\n");
437                                 read(fdesc,&bigstring,strln);
438                                 bigstring[strln] = 0;
439                                 if (strcmp(bigstring,data)==0) { 
440                                         readcache=1;
441                                 } else {
442                                         ast_log(LOG_WARNING,"Strings do not match\n");
443                                 }
444                         } else {
445                                 ast_log(LOG_WARNING,"Size mismatch\n");
446                         }
447                 }
448         }
449                         
450         if (readcache==1) {
451                 close(fd);
452                 fd=fdesc;
453                 ast_log(LOG_DEBUG,"Reading from cache...\n");
454         } else {
455                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
456                 fs=fdopen(dup(fd),"wb");
457                 fprintf(fs,festivalcommand,(char *)data);
458                 fflush(fs);
459                 fclose(fs);
460         }
461         
462         /* Write to cache and then pass it down */
463         if (writecache==1) {
464                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
465                 while ((strln=read(fd,buffer,16384))!=0) {
466                         write(fdesc,buffer,strln);
467                 }
468                 close(fd);
469                 close(fdesc);
470                 fd=open(cachefile,O_RDWR);
471                 lseek(fd,seekpos,SEEK_SET);
472         }
473         
474         ast_log(LOG_DEBUG,"Passing data to channel...\n");
475         
476         /* Read back info from server */
477         /* This assumes only one waveform will come back, also LP is unlikely */
478         wave = 0;
479         do {
480                int read_data;
481                 for (n=0; n < 3; )
482                {
483                        read_data = read(fd,ack+n,3-n);
484                        /* this avoids falling in infinite loop
485                         * in case that festival server goes down
486                         * */
487                        if ( read_data == -1 )
488                        {
489                                ast_log(LOG_WARNING,"Unable to read from cache/festival fd");
490                                return -1;
491                        }
492                        n += read_data;
493                }
494                 ack[3] = '\0';
495                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
496                         ast_log(LOG_DEBUG,"Festival WV command\n");
497                         waveform = socket_receive_file_to_buff(fd,&filesize);
498                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
499                         free(waveform);
500                         break;
501                 }
502                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
503                         ast_log(LOG_DEBUG,"Festival LP command\n");
504                         waveform = socket_receive_file_to_buff(fd,&filesize);
505                         waveform[filesize]='\0';
506                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
507                         free(waveform);
508                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
509                         ast_log(LOG_WARNING,"Festival returned ER\n");
510                         res=-1;
511                         break;
512                 }
513         } while (strcmp(ack,"OK\n") != 0);
514         close(fd);
515         ast_config_destroy(cfg);
516         LOCAL_USER_REMOVE(u);
517         return res;
518
519 }
520
521 static int unload_module(void *mod)
522 {
523         int res;
524
525         res = ast_unregister_application(app);
526
527         STANDARD_HANGUP_LOCALUSERS;
528
529         return res;
530 }
531
532 static int load_module(void *mod)
533 {
534         return ast_register_application(app, festival_exec, synopsis, descrip);
535 }
536
537 static const char *description(void)
538 {
539         return "Simple Festival Interface";
540
541 }
542
543 static const char *key(void)
544 {
545         return ASTERISK_GPL_KEY;
546 }
547
548 STD_MOD1;