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