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