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