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