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