acc268e3600db0af6110eb759439650df325d2d8
[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@paiko.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         res = fork();
110         if (res < 0)
111                 ast_log(LOG_WARNING, "Fork failed\n");
112         if (res)
113                 return res;
114         for (x=0;x<256;x++) {
115                 if (x != fd)
116                         close(x);
117         }
118         write(fd,waveform,length);
119         write(fd,"a",1);
120         close(fd);
121         exit(0);
122 }
123
124
125 static int send_waveform_to_channel(struct ast_channel *chan, char *waveform, int length, char *intkeys) {
126         int res=0;
127         int fds[2];
128         int ms = -1;
129         int pid = -1;
130         int needed = 0;
131         int owriteformat;
132         struct ast_frame *f;
133         struct myframe {
134                 struct ast_frame f;
135                 char offset[AST_FRIENDLY_OFFSET];
136                 char frdata[2048];
137         } myf;
138         
139         if (pipe(fds)) {
140                  ast_log(LOG_WARNING, "Unable to create pipe\n");
141                 return -1;
142         }
143                                                         
144         ast_stopstream(chan);
145
146         owriteformat = chan->writeformat;
147         res = ast_set_write_format(chan, AST_FORMAT_SLINEAR);
148         if (res < 0) {
149                 ast_log(LOG_WARNING, "Unable to set write format to signed linear\n");
150                 return -1;
151         }
152         
153         res=send_waveform_to_fd(waveform,length,fds[1]);
154         if (res >= 0) {
155                 pid = res;
156                 /* Order is important -- there's almost always going to be mp3...  we want to prioritize the
157                    user */
158                 for (;;) {
159                         ms = 1000;
160                         res = ast_waitfor(chan, ms);
161                         if (res < 1) {
162                                 res = -1;
163                                 break;
164                         }
165                         f = ast_read(chan);
166                         if (!f) {
167                                 ast_log(LOG_DEBUG, "Null frame == hangup() detected\n");
168                                 res = -1;
169                                 break;
170                         }
171                         if (f->frametype == AST_FRAME_DTMF) {
172                                 ast_log(LOG_DEBUG, "User pressed a key\n");
173                                 if (strchr(intkeys, f->subclass)) {
174                                         res = f->subclass;
175                                         ast_frfree(f);
176                                         break;
177                                 }
178                         }
179                         if (f->frametype == AST_FRAME_VOICE) {
180                                 /* Treat as a generator */
181                                 needed = f->samples * 2;
182                                 if (needed > sizeof(myf.frdata)) {
183                                         ast_log(LOG_WARNING, "Only able to deliver %d of %d requested samples\n",
184                                                 sizeof(myf.frdata) / 2, needed/2);
185                                         needed = sizeof(myf.frdata);
186                                 }
187                                 res = read(fds[0], myf.frdata, needed);
188                                 if (res > 0) {
189                                         myf.f.frametype = AST_FRAME_VOICE;
190                                         myf.f.subclass = AST_FORMAT_SLINEAR;
191                                         myf.f.datalen = res;
192                                         myf.f.samples = res / 2;
193                                         myf.f.mallocd = 0;
194                                         myf.f.offset = AST_FRIENDLY_OFFSET;
195                                         myf.f.src = __PRETTY_FUNCTION__;
196                                         myf.f.data = myf.frdata;
197                                         if (ast_write(chan, &myf.f) < 0) {
198                                                 res = -1;
199                                                 break;
200                                         }
201                                         if (res < needed) { // last frame
202                                                 ast_log(LOG_WARNING, "Last frame\n");
203                                                 res=0;
204                                                 break;
205                                         }
206                                 } else {
207                                         ast_log(LOG_WARNING, "No more waveform\n");
208                                         res = 0;
209                                 }
210                         }
211                         ast_frfree(f);
212                 }
213         }
214         close(fds[0]);
215         close(fds[1]);
216 //      if (pid > -1)
217 //              kill(pid, SIGKILL);
218         if (!res && owriteformat)
219                 ast_set_write_format(chan, owriteformat);
220         return res;
221 }
222
223 #define MAXLEN 180
224 #define MAXFESTLEN 2048
225
226
227
228
229 static int festival_exec(struct ast_channel *chan, void *vdata)
230 {
231         int usecache;
232         int res=0;
233         struct localuser *u;
234         struct sockaddr_in serv_addr;
235         struct hostent *serverhost;
236         int fd;
237         FILE *fs;
238         char *host;
239         char *cachedir;
240         char *temp;
241         char *festivalcommand;
242         int port=1314;
243         int n;
244         char ack[4];
245         char *waveform;
246         int filesize;
247         int wave;
248         char bigstring[MAXFESTLEN];
249         int i;
250         struct MD5Context md5ctx;
251         unsigned char MD5Res[16];
252         char MD5Hex[32];
253         char koko[4];
254         char cachefile[MAXFESTLEN];
255         int readcache=0;
256         int writecache=0;
257         int strln;
258         int fdesc = -1;
259         char buffer[16384];
260         int seekpos = 0;        
261         char data[256] = "";
262         char *intstr;
263         
264         struct ast_config *cfg;
265         cfg = ast_load(FESTIVAL_CONFIG);
266         if (!cfg) {
267                 ast_log(LOG_WARNING, "No such configuration file %s\n", FESTIVAL_CONFIG);
268                 return -1;
269         }
270         if (!(host = ast_variable_retrieve(cfg, "general", "host"))) {
271                 host = "localhost";
272         }
273         if (!(temp = ast_variable_retrieve(cfg, "general", "port"))) {
274                 port = 1314;
275         } else {
276                 port = atoi(temp);
277         }
278         if (!(temp = ast_variable_retrieve(cfg, "general", "usecache"))) {
279                 usecache=0;
280         } else {
281                 usecache = ast_true(temp);
282         }
283         if (!(cachedir = ast_variable_retrieve(cfg, "general", "cachedir"))) {
284                 cachedir = "/tmp/";
285         }
286         if (!(festivalcommand = ast_variable_retrieve(cfg, "general", "festivalcommand"))) {
287                 festivalcommand = "(tts_textasterisk \"%s\" 'file)(quit)\n";
288         }
289         
290                 
291
292         if (!vdata || !strlen(vdata)) {
293                 ast_log(LOG_WARNING, "festival requires an argument (text)\n");
294                 return -1;
295         }
296         strncpy(data, vdata, sizeof(data) - 1);
297         if ((intstr = strchr(data, '|'))) {
298                 *intstr = '\0';
299                 intstr++;
300                 if (!strcasecmp(intstr, "any"))
301                         intstr = AST_DIGIT_ANY;
302         }
303         LOCAL_USER_ADD(u);
304         ast_log(LOG_WARNING, "Text passed to festival server : %s\n",(char *)data);
305         /* Connect to local festival server */
306         
307         fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
308
309         if (fd < 0) {
310                 ast_log(LOG_WARNING,"festival_client: can't get socket\n");
311                 return -1;
312         }
313         memset(&serv_addr, 0, sizeof(serv_addr));
314         if ((serv_addr.sin_addr.s_addr = inet_addr(host)) == -1) {
315                 /* its a name rather than an ipnum */
316                 serverhost = gethostbyname(host);
317                 if (serverhost == (struct hostent *)0) {
318                         ast_log(LOG_WARNING,"festival_client: gethostbyname failed\n");
319                         return -1;
320                 }
321                 memmove(&serv_addr.sin_addr,serverhost->h_addr, serverhost->h_length);
322         }
323         serv_addr.sin_family = AF_INET;
324         serv_addr.sin_port = htons(port);
325
326         if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) != 0) {
327                 ast_log(LOG_WARNING,"festival_client: connect to server failed\n");
328                 return -1;
329         }
330         
331         /* Compute MD5 sum of string */
332         MD5Init(&md5ctx);
333         MD5Update(&md5ctx,(unsigned char const *)data,strlen(data));
334         MD5Final(MD5Res,&md5ctx);
335         strcpy(MD5Hex,"");
336         
337         /* Convert to HEX and look if there is any matching file in the cache 
338                 directory */
339         for (i=0;i<16;i++) {
340                 sprintf(koko,"%X",MD5Res[i]);
341                 strcat(MD5Hex,koko);
342         }
343         readcache=0;
344         writecache=0;
345         if (strlen(cachedir)+strlen(MD5Hex)+1<=MAXFESTLEN && (usecache==1)) {
346                 sprintf(cachefile,"%s/%s",cachedir,MD5Hex);
347                 fdesc=open(cachefile,O_RDWR);
348                 if (fdesc==-1) {
349                         fdesc=open(cachefile,O_CREAT|O_RDWR,0);
350                         if (fdesc!=-1) {
351                                 writecache=1;
352                                 strln=strlen((char *)data);
353                                 ast_log(LOG_WARNING,"line length : %d\n",strln);
354                                 write(fdesc,&strln,sizeof(int));
355                                 write(fdesc,data,strln);
356                                 seekpos=lseek(fdesc,0,SEEK_CUR);
357                                 ast_log(LOG_WARNING,"Seek position : %d\n",seekpos);
358                         }
359                 } else {
360                         read(fdesc,&strln,sizeof(int));
361                         ast_log(LOG_WARNING,"Cache file exists, strln=%d, strlen=%d\n",strln,strlen((char *)data));
362                         if (strlen((char *)data)==strln) {
363                                 ast_log(LOG_WARNING,"Size OK\n");
364                                 read(fdesc,&bigstring,strln);
365                                 if (strcmp(bigstring,data)==0) { 
366                                         readcache=1;
367                                 } else {
368                                         ast_log(LOG_WARNING,"Strings do not match\n");
369                                 }
370                         } else {
371                                 ast_log(LOG_WARNING,"Size mismatch\n");
372                         }
373                 }
374         }
375                         
376         if (readcache==1) {
377                 close(fd);
378                 fd=fdesc;
379                 ast_log(LOG_WARNING,"Reading from cache...\n");
380         } else {
381                 ast_log(LOG_WARNING,"Passing text to festival...\n");
382                 fs=fdopen(dup(fd),"wb");
383                 fprintf(fs,festivalcommand,(char *)data);
384                 fflush(fs);
385                 fclose(fs);
386         }
387         
388         /* Write to cache and then pass it down */
389         if (writecache==1) {
390                 ast_log(LOG_WARNING,"Writing result to cache...\n");
391                 while ((strln=read(fd,buffer,16384))!=0) {
392                         write(fdesc,buffer,strln);
393                 }
394                 close(fd);
395                 close(fdesc);
396                 fd=open(cachefile,O_RDWR);
397                 lseek(fd,seekpos,SEEK_SET);
398         }
399         
400         ast_log(LOG_WARNING,"Passing data to channel...\n");
401         
402         /* Read back info from server */
403         /* This assumes only one waveform will come back, also LP is unlikely */
404         wave = 0;
405         do {
406                 for (n=0; n < 3; )
407                         n += read(fd,ack+n,3-n);
408                 ack[3] = '\0';
409                 if (strcmp(ack,"WV\n") == 0) {         /* receive a waveform */
410                         ast_log(LOG_WARNING,"Festival WV command");
411                         waveform = socket_receive_file_to_buff(fd,&filesize);
412                         send_waveform_to_channel(chan,waveform,filesize, intstr);
413                         free(waveform);
414                         res=0;
415                         break;
416                 }
417                 else if (strcmp(ack,"LP\n") == 0) {   /* receive an s-expr */
418                         ast_log(LOG_WARNING,"Festival LP command");
419                         waveform = socket_receive_file_to_buff(fd,&filesize);
420                         waveform[filesize]='\0';
421                         ast_log(LOG_WARNING,"Festival returned LP : %s\n",waveform);
422                         free(waveform);
423                 } else if (strcmp(ack,"ER\n") == 0) {    /* server got an error */
424                         ast_log(LOG_WARNING,"Festival returned ER\n");
425                         res=-1;
426                         break;
427                 }
428         } while (strcmp(ack,"OK\n") != 0);
429         close(fd);
430         LOCAL_USER_REMOVE(u);                                                                                
431         return res;
432
433 }
434
435 int unload_module(void)
436 {
437         STANDARD_HANGUP_LOCALUSERS;
438         return ast_unregister_application(app);
439 }
440
441 int load_module(void)
442 {
443         
444         return ast_register_application(app, festival_exec, synopsis, descrip);
445 }
446
447 char *description(void)
448 {
449         return tdesc;
450 }
451
452 int usecount(void)
453 {
454         int res;
455         STANDARD_USECOUNT(res);
456         return res;
457 }
458
459 char *key()
460 {
461         return ASTERISK_GPL_KEY;
462 }