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