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