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