Oops it didn't make it in
[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 //      if (pid > -1)
235 //              kill(pid, SIGKILL);
236         if (!res && owriteformat)
237                 ast_set_write_format(chan, owriteformat);
238         return res;
239 }
240
241 #define MAXLEN 180
242 #define MAXFESTLEN 2048
243
244
245
246
247 static int festival_exec(struct ast_channel *chan, void *vdata)
248 {
249         int usecache;
250         int res=0;
251         struct localuser *u;
252         struct sockaddr_in serv_addr;
253         struct hostent *serverhost;
254         struct ast_hostent ahp;
255         int fd;
256         FILE *fs;
257         char *host;
258         char *cachedir;
259         char *temp;
260         char *festivalcommand;
261         int port=1314;
262         int n;
263         char ack[4];
264         char *waveform;
265         int filesize;
266         int wave;
267         char bigstring[MAXFESTLEN];
268         int i;
269         struct MD5Context md5ctx;
270         unsigned char MD5Res[16];
271         char MD5Hex[33] = "";
272         char koko[4] = "";
273         char cachefile[MAXFESTLEN]="";
274         int readcache=0;
275         int writecache=0;
276         int strln;
277         int fdesc = -1;
278         char buffer[16384];
279         int seekpos = 0;        
280         char data[256] = "";
281         char *intstr;
282         
283         struct ast_config *cfg;
284         cfg = ast_load(FESTIVAL_CONFIG);
285         if (!cfg) {
286                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
287                 return -1;
288         }
289         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
290                 host = "localhost";
291         }
292         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
293                 port = 1314;
294         } else {
295                 port = atoi(temp);
296         }
297         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
298                 usecache=0;
299         } else {
300                 usecache = ast_true(temp);
301         }
302         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
303                 cachedir = "/tmp/";
304         }
305         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
306                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
307         }
308         if (!vdata || ast_strlen_zero(vdata)) {
309                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
310                 ast_destroy(cfg);
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                 ast_destroy(cfg);
329                 return -1;
330         }
331         memset(&serv_addr, 0, sizeof(serv_addr));
332         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
333                 /* its a name rather than an ipnum */
334                 serverhost = ast_gethostbyname(host, &ahp);
335                 if (serverhost == (struct hostent *)0) {
336                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
337                         ast_destroy(cfg);
338                         return -1;
339                 }
340                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
341         }
342         serv_addr.sin_family = AF_INET;
343         serv_addr.sin_port = htons(port);
344
345         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
346                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
347                 ast_destroy(cfg);
348                 return -1;
349         }
350         
351         /* Compute MD5 sum of string */
352         MD5Init(&md5ctx);
353         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
354         MD5Final(MD5Res,&md5ctx);
355                 MD5Hex[0] = '\0';
356         
357         /* Convert to HEX and look if there is any matching file in the cache 
358                 directory */
359         for (i=0;i<16;i++) {
360                 snprintf(koko, sizeof(koko), "%X",MD5Res[i]);
361                 strncat(MD5Hex, koko, sizeof(MD5Hex) - strlen(MD5Hex) - 1);
362         }
363         readcache=0;
364         writecache=0;
365         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==-1)) {
366                 snprintf(cachefile, sizeof(cachefile), "%s/%s", cachedir, MD5Hex);
367                 fdesc=open(cachefile,O_RDWR);
368                 if (fdesc==-1) {
369                         fdesc=open(cachefile,O_CREAT|O_RDWR,0777);
370                         if (fdesc!=-1) {
371                                 writecache=1;
372                                 strln=strlen((char *)data);
373                                 ast_log(LOG_DEBUG,"line length : %d\n",strln);
374                                 write(fdesc,&strln,sizeof(int));
375                                 write(fdesc,data,strln);
376                                 seekpos=lseek(fdesc,0,SEEK_CUR);
377                                 ast_log(LOG_DEBUG,"Seek position : %d\n",seekpos);
378                         }
379                 } else {
380                         read(fdesc,&strln,sizeof(int));
381                         ast_log(LOG_DEBUG,"Cache file exists, strln=%d, strlen=%d\n",strln,(int)strlen((char *)data));
382                         if (strlen((char *)data)==strln) {
383                                 ast_log(LOG_DEBUG,"Size OK\n");
384                                 read(fdesc,&bigstring,strln);
385                                 bigstring[strln] = 0;
386                                 if (strcmp(bigstring,data)==0) { 
387                                         readcache=1;
388                                 } else {
389                                         ast_log(LOG_WARNING,"Strings do not match\n");
390                                 }
391                         } else {
392                                 ast_log(LOG_WARNING,"Size mismatch\n");
393                         }
394                 }
395         }
396                         
397         if (readcache==1) {
398                 close(fd);
399                 fd=fdesc;
400                 ast_log(LOG_DEBUG,"Reading from cache...\n");
401         } else {
402                 ast_log(LOG_DEBUG,"Passing text to festival...\n");
403                 fs=fdopen(dup(fd),"wb");
404                 fprintf(fs,festivalcommand,(char *)data);
405                 fflush(fs);
406                 fclose(fs);
407         }
408         
409         /* Write to cache and then pass it down */
410         if (writecache==1) {
411                 ast_log(LOG_DEBUG,"Writing result to cache...\n");
412                 while ((strln=read(fd,buffer,16384))!=0) {
413                         write(fdesc,buffer,strln);
414                 }
415                 close(fd);
416                 close(fdesc);
417                 fd=open(cachefile,O_RDWR);
418                 lseek(fd,seekpos,SEEK_SET);
419         }
420         
421         ast_log(LOG_DEBUG,"Passing data to channel...\n");
422         
423         /* Read back info from server */
424         /* This assumes only one waveform will come back, also LP is unlikely */
425         wave = 0;
426         do {
427                 for (n=0; n < 3; )
428                         n += read(fd,ack+n,3-n);
429                 ack[3] = '\0';
430                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
431                         ast_log(LOG_DEBUG,"Festival WV command\n");
432                         waveform = socket_receive_file_to_buff(fd,&filesize);
433                         res = send_waveform_to_channel(chan,waveform,filesize, intstr);
434                         free(waveform);
435                         break;
436                 }
437                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
438                         ast_log(LOG_DEBUG,"Festival LP command\n");
439                         waveform = socket_receive_file_to_buff(fd,&filesize);
440                         waveform[filesize]='\0';
441                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
442                         free(waveform);
443                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
444                         ast_log(LOG_WARNING,"Festival returned ER\n");
445                         res=-1;
446                         break;
447                 }
448         } while (strcmp(ack,"OK\n") != 0);
449         close(fd);
450         ast_destroy(cfg);
451         LOCAL_USER_REMOVE(u);                                                                                
452         return res;
453
454 }
455
456 int unload_module(void)
457 {
458         STANDARD_HANGUP_LOCALUSERS;
459         return ast_unregister_application(app);
460 }
461
462 int load_module(void)
463 {
464         
465         return ast_register_application(app, festival_exec, synopsis, descrip);
466 }
467
468 char *description(void)
469 {
470         return tdesc;
471 }
472
473 int usecount(void)
474 {
475         int res;
476         STANDARD_USECOUNT(res);
477         return res;
478 }
479
480 char *key()
481 {
482         return ASTERISK_GPL_KEY;
483 }