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