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