Merge Olle's comment patch (bug #3097)
[asterisk/asterisk.git] / apps / app_festival.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Connect to festival
5  * 
6  * Copyright (C) 2002, Christos Ricudis
7  *
8  * Christos Ricudis <ricudis@itc.auth.gr>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <sys/types.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/module.h>
20 #include <asterisk/md5.h>
21 #include <asterisk/config.h>
22 #include <asterisk/utils.h>
23 #include <asterisk/lock.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <netdb.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
33 #include <stdio.h>
34 #include <signal.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <ctype.h>
39
40
41 #define FESTIVAL_CONFIG "festival.conf"
42
43 static char *tdesc = "Simple Festival Interface";
44
45 static char *app = "Festival";
46
47 static char *synopsis = "Say text to the user";
48
49 static char *descrip = 
50 "  Festival(text[|intkeys]):  Connect to Festival, send the argument, get back the waveform,"
51 "play it to the user, allowing any given interrupt keys to immediately terminate and return\n"
52 "the value, or 'any' to allow any number back (useful in dialplan)\n";
53
54 STANDARD_LOCAL_USER;
55
56 LOCAL_USER_DECL;
57
58 static char *socket_receive_file_to_buff(int fd,int *size)
59 {
60     /* Receive file (probably a waveform file) from socket using   */
61     /* Festival key stuff technique, but long winded I know, sorry */
62     /* but will receive any file without closeing the stream or    */
63     /* using OOB data                                              */
64     static char *file_stuff_key = "ft_StUfF_key"; /* must == Festival's key */
65     char *buff;
66     int bufflen;
67     int n,k,i;
68     char c;
69
70     bufflen = 1024;
71     buff = (char *)malloc(bufflen);
72     *size=0;
73
74     for (k=0; file_stuff_key[k] != '\0';)
75     {
76         n = read(fd,&c,1);
77         if (n==0) break;  /* hit stream eof before end of file */
78         if ((*size)+k+1 >= bufflen)
79         {   /* +1 so you can add a NULL if you want */
80             bufflen += bufflen/4;
81             buff = (char *)realloc(buff,bufflen);
82         }
83         if (file_stuff_key[k] == c)
84             k++;
85         else if ((c == 'X') && (file_stuff_key[k+1] == '\0'))
86         {   /* It looked like the key but wasn't */
87             for (i=0; i < k; i++,(*size)++)
88                 buff[*size] = file_stuff_key[i];
89             k=0;
90             /* omit the stuffed 'X' */
91         }
92         else
93         {
94             for (i=0; i < k; i++,(*size)++)
95                 buff[*size] = file_stuff_key[i];
96             k=0;
97             buff[*size] = c;
98             (*size)++;
99         }
100
101     }
102
103     return buff;
104 }
105
106 static int send_waveform_to_fd(char *waveform, int length, int fd) {
107
108         int res;
109         int x;
110 #ifdef __PPC__ 
111         char c;
112 #endif
113
114         res = fork();
115         if (res < 0)
116                 ast_log(LOG_WARNING, "Fork failed\n");
117         if (res)
118                 return res;
119         for (x=0;x<256;x++) {
120                 if (x != fd)
121                         close(x);
122         }
123 /*IAS */
124 #ifdef __PPC__  
125         for( x=0; x<length; x+=2)
126         {
127                 c = *(waveform+x+1);
128                 *(waveform+x+1)=*(waveform+x);
129                 *(waveform+x)=c;
130         }
131 #endif
132         
133         write(fd,waveform,length);
134         write(fd,"a",1);
135         close(fd);
136         exit(0);
137 }
138
139
140 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
141         int res=0;
142         int fds[2];
143         int ms = -1;
144         int pid = -1;
145         int needed = 0;
146         int owriteformat;
147         struct ast_frame *f;
148         struct myframe {
149                 struct ast_frame f;
150                 char offset[AST_FRIENDLY_OFFSET];
151                 char frdata[2048];
152         } myf;
153         
154         if (pipe(fds)) {
155                  ast_log(LOG_WARNING, "Unable to create pipe\n");
156                 return -1;
157         }
158                                                         
159         /* Answer if it's not already going */
160         if (chan->_state != AST_STATE_UP)
161                 ast_answer(chan);
162         ast_stopstream(chan);
163
164         owriteformat = chan->writeformat;
165         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
166         if (res < 0) {
167                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
168                 return -1;
169         }
170         
171         res=send_waveform_to_fd(waveform,length,fds[1]);
172         if (res >= 0) {
173                 pid = res;
174                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
175                    user */
176                 for (;;) {
177                         ms = 1000;
178                         res = ast_waitfor(chan, ms);
179                         if (res < 1) {
180                                 res = -1;
181                                 break;
182                         }
183                         f = ast_read(chan);
184                         if (!f) {
185                                 ast_log(LOG_WARNING, "Null frame == hangup() detected\n");
186                                 res = -1;
187                                 break;
188                         }
189                         if (f->frametype == AST_FRAME_DTMF) {
190                                 ast_log(LOG_DEBUG, "User pressed a key\n");
191                                 if (intkeys && strchr(intkeys, f->subclass)) {
192                                         res = f->subclass;
193                                         ast_frfree(f);
194                                         break;
195                                 }
196                         }
197                         if (f->frametype == AST_FRAME_VOICE) {
198                                 /* Treat as a generator */
199                                 needed = f->samples * 2;
200                                 if (needed > sizeof(myf.frdata)) {
201                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
202                                                 (int)sizeof(myf.frdata) / 2, needed/2);
203                                         needed = sizeof(myf.frdata);
204                                 }
205                                 res = read(fds[0], myf.frdata, needed);
206                                 if (res > 0) {
207                                         myf.f.frametype = AST_FRAME_VOICE;
208                                         myf.f.subclass = AST_FORMAT_SLINEAR;
209                                         myf.f.datalen = res;
210                                         myf.f.samples = res / 2;
211                                         myf.f.mallocd = 0;
212                                         myf.f.offset = AST_FRIENDLY_OFFSET;
213                                         myf.f.src = __PRETTY_FUNCTION__;
214                                         myf.f.data = myf.frdata;
215                                         if (ast_write(chan, &myf.f) < 0) {
216                                                 res = -1;
217                                                 break;
218                                         }
219                                         if (res < needed) { /* last frame */
220                                                 ast_log(LOG_DEBUG, "Last frame\n");
221                                                 res=0;
222                                                 break;
223                                         }
224                                 } else {
225                                         ast_log(LOG_DEBUG, "No more waveform\n");
226                                         res = 0;
227                                 }
228                         }
229                         ast_frfree(f);
230                 }
231         }
232         close(fds[0]);
233         close(fds[1]);
234
235 /*      if (pid > -1) */
236 /*              kill(pid, SIGKILL); */
237         if (!res && owriteformat)
238                 ast_set_write_format(chan, owriteformat);
239         return res;
240 }
241
242 #define MAXLEN 180
243 #define MAXFESTLEN 2048
244
245
246
247
248 static int festival_exec(struct ast_channel *chan, void *vdata)
249 {
250         int usecache;
251         int res=0;
252         struct localuser *u;
253         struct sockaddr_in serv_addr;
254         struct hostent *serverhost;
255         struct ast_hostent ahp;
256         int fd;
257         FILE *fs;
258         char *host;
259         char *cachedir;
260         char *temp;
261         char *festivalcommand;
262         int port=1314;
263         int n;
264         char ack[4];
265         char *waveform;
266         int filesize;
267         int wave;
268         char bigstring[MAXFESTLEN];
269         int i;
270         struct MD5Context md5ctx;
271         unsigned char MD5Res[16];
272         char MD5Hex[33] = "";
273         char koko[4] = "";
274         char cachefile[MAXFESTLEN]="";
275         int readcache=0;
276         int writecache=0;
277         int strln;
278         int fdesc = -1;
279         char buffer[16384];
280         int seekpos = 0;        
281         char data[256] = "";
282         char *intstr;
283         
284         struct ast_config *cfg;
285         cfg = ast_load(FESTIVAL_CONFIG);
286         if (!cfg) {
287                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
288                 return -1;
289         }
290         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
291                 host = "localhost";
292         }
293         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
294                 port = 1314;
295         } else {
296                 port = atoi(temp);
297         }
298         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
299                 usecache=0;
300         } else {
301                 usecache = ast_true(temp);
302         }
303         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
304                 cachedir = "/tmp/";
305         }
306         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
307                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
308         }
309         if (!vdata || ast_strlen_zero(vdata)) {
310                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
311                 ast_destroy(cfg);
312                 return -1;
313         }
314         strncpy(data, vdata, sizeof(data) - 1);
315         if ((intstr = strchr(data, '|'))) {
316                 *intstr = '\0';
317                 intstr++;
318                 if (!strcasecmp(intstr, "any"))
319                         intstr = AST_DIGIT_ANY;
320         }
321         LOCAL_USER_ADD(u);
322         ast_log(LOG_DEBUG, "Text passed to festival server : %s\n",(char *)data);
323         /* Connect to local festival server */
324         
325         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
326
327         if (fd < 0) {
328                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
329                 ast_destroy(cfg);
330                 return -1;
331         }
332         memset(&serv_addr, 0, sizeof(serv_addr));
333         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
334                 /* its a name rather than an ipnum */
335                 serverhost = ast_gethostbyname(host, &ahp);
336                 if (serverhost == (struct hostent *)0) {
337                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
338                         ast_destroy(cfg);
339                         return -1;
340                 }
341                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
342         }
343         serv_addr.sin_family = AF_INET;
344         serv_addr.sin_port = htons(port);
345
346         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
347                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
348                 ast_destroy(cfg);
349                 return -1;
350         }
351         
352         /* Compute MD5 sum of string */
353         MD5Init(&md5ctx);
354         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
355         MD5Final(MD5Res,&md5ctx);
356                 MD5Hex[0] = '\0';
357         
358         /* Convert to HEX and look if there is any matching file in the cache 
359                 directory */
360         for (i=0;i<16;i++) {
361                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
362                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
363         }
364         readcache=0;
365         writecache=0;
366         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
367                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
368                 fdesc=open(cachefile,O_RDWR);
369                 if (fdesc==-1) {
370                         fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
371                         if (fdesc!=-1) {
372                                 writecache=1;
373                                 strln=strlen((char *)data);
374                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
375                                 write(fdesc,&strln,sizeof(int));
376                                 write(fdesc,data,strln);
377                                 seekpos=lseek(fdesc,0,SEEK_CUR);
378                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
379                         }
380                 } else {
381                         read(fdesc,&strln,sizeof(int));
382                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
383                         if (strlen((char *)data)==strln) {
384                                 ast_log(LOG_DEBUG,"Size OK\n");
385                                 read(fdesc,&bigstring,strln);
386                                 bigstring[strln] = 0;
387                                 if (strcmp(bigstring,data)==0) { 
388                                         readcache=1;
389                                 } else {
390                                         ast_log(LOG_WARNING,"Strings do not match\n");
391                                 }
392                         } else {
393                                 ast_log(LOG_WARNING,"Size mismatch\n");
394                         }
395                 }
396         }
397                         
398         if (readcache==1) {
399                 close(fd);
400                 fd=fdesc;
401                 ast_log(LOG_DEBUG,"Reading from cache...\n");
402         } else {
403                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
404                 fs=fdopen(dup(fd),"wb");
405                 fprintf(fs,festivalcommand,(char *)data);
406                 fflush(fs);
407                 fclose(fs);
408         }
409         
410         /* Write to cache and then pass it down */
411         if (writecache==1) {
412                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
413                 while ((strln=read(fd,buffer,16384))!=0) {
414                         write(fdesc,buffer,strln);
415                 }
416                 close(fd);
417                 close(fdesc);
418                 fd=open(cachefile,O_RDWR);
419                 lseek(fd,seekpos,SEEK_SET);
420         }
421         
422         ast_log(LOG_DEBUG,"Passing data to channel...\n");
423         
424         /* Read back info from server */
425         /* This assumes only one waveform will come back, also LP is unlikely */
426         wave = 0;
427         do {
428                 for (n=0; n < 3; )
429                         n += read(fd,ack+n,3-n);
430                 ack[3] = '\0';
431                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
432                         ast_log(LOG_DEBUG,"Festival WV command\n");
433                         waveform = socket_receive_file_to_buff(fd,&filesize);
434                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
435                         free(waveform);
436                         break;
437                 }
438                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
439                         ast_log(LOG_DEBUG,"Festival LP command\n");
440                         waveform = socket_receive_file_to_buff(fd,&filesize);
441                         waveform[filesize]='\0';
442                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
443                         free(waveform);
444                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
445                         ast_log(LOG_WARNING,"Festival returned ER\n");
446                         res=-1;
447                         break;
448                 }
449         } while (strcmp(ack,"OK\n") != 0);
450         close(fd);
451         ast_destroy(cfg);
452         LOCAL_USER_REMOVE(u);                                                                                
453         return res;
454
455 }
456
457 int unload_module(void)
458 {
459         STANDARD_HANGUP_LOCALUSERS;
460         return ast_unregister_application(app);
461 }
462
463 int load_module(void)
464 {
465         
466         return ast_register_application(app, festival_exec, synopsis, descrip);
467 }
468
469 char *description(void)
470 {
471         return tdesc;
472 }
473
474 int usecount(void)
475 {
476         int res;
477         STANDARD_USECOUNT(res);
478         return res;
479 }
480
481 char *key()
482 {
483         return ASTERISK_GPL_KEY;
484 }