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