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