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