1 /*
2 * (c) 1998-2005 Jirka Hanika <geo@cuni.cz>
3 *
4 * This single source file src/say-epos.cc, but NOT THE REST OF THIS PACKAGE,
5 * is considered to be in Public Domain. Parts of this single source file may be
6 * freely incorporated into any commercial or other software.
7 *
8 * Most files in this package are strictly covered by the General Public
9 * License version 2, to be found in doc/COPYING. Should GPL and the paragraph
10 * above come into any sort of legal conflict, GPL takes precedence.
11 *
12 * This file implements a simple TTSCP client. See doc/english/ttscp.sgml
13 * for a preliminary technical specification.
14 *
15 * This file is almost a plain C source file. We compile it with a C++
16 * compiler just to avoid additional configure complexity.
17 */
18
19 #define THIS_IS_A_TTSCP_CLIENT
20
21 #include "common.h"
22
23 #ifdef HAVE_WINSVC_H
24 bool start_service();
25 bool stop_service();
26 #else
start_service()27 bool start_service() { return true; }
stop_service()28 bool stop_service() { return true; }
29 #endif
30
31 #ifndef HAVE_TERMINATE
32
terminate(void)33 void terminate(void)
34 {
35 abort();
36 }
37
38 #endif
39
40 const char *COMMENT_LINES = "#;\r\n";
41 const char *WHITESPACE = " \t\r";
42
43 const char *output_file = NULL;
44
45 const char *charset = "8859-2";
46
47 bool chunking = false;
48 bool show_segments = false;
49 bool debug_ttscp = false;
50 bool wavfile = false;
51 bool wavstdout = false;
52 bool traditional = true;
53 bool do_say = true;
54
55 const char *other_traffic_prefix = "Unexpected: ";
56
57 #define STDIN_BUFF_SIZE 550000
58
59 int ctrld, datad; /* file descriptors for the control and data connections */
60 char *data = NULL;
61
62 char *ch;
63 char *dh;
64
shriek(char * txt)65 void shriek(char *txt)
66 {
67 fprintf(stderr, "Client side error: %s\n", txt);
68 exit(1);
69 }
70
shriek(int,char * txt)71 void shriek(int, char *txt)
72 {
73 shriek(txt);
74 }
75
76
77 // #include "exc.h"
78 #include "client.cc"
79
send_to_epos(const char * buffer,int socket)80 int send_to_epos(const char *buffer, int socket)
81 {
82 if (debug_ttscp && socket == ctrld)
83 printf("%s", buffer);
84 return sputs(buffer, socket);
85 }
86
get_result(int sd)87 int get_result(int sd)
88 {
89 while (sgets(scratch, scfg->scratch_size, sd)) {
90 scratch[scfg->scratch_size] = 0;
91 if (debug_ttscp && sd == ctrld)
92 printf("Received: %s\n", scratch);
93 switch(*scratch) {
94 case '1': continue;
95 case '2': return 2;
96 case '3': break;
97 case '4': printf("%s\n", scratch+strspn(scratch, "0123456789x "));
98 return 4;
99 case '6': if (!strncmp(scratch, "600 ", 4)) {
100 exit(0);
101 } /* else fall through */
102 case '8': printf("%s\n", scratch+strspn(scratch, "0123456789x "));
103 exit(2);
104
105 case '5':
106 case '7':
107 case '9':
108 case '0': printf("%s\n", scratch); shriek("Unhandled response code");
109 default : ;
110 }
111 char *o = scratch+strspn(scratch, "0123456789 -");
112 if (*scratch && *o) printf("%s%s\n", other_traffic_prefix, o);
113 }
114 return 8; /* guessing */
115 }
116
117 int size;
118
get_data()119 char *get_data()
120 {
121 char *b = NULL;
122 size = 0;
123 while (sgets(scratch, scfg->scratch_size, ctrld)) {
124 scratch[scfg->scratch_size] = 0;
125 if (debug_ttscp) printf("Received: %s\n", scratch);
126 if (strchr("2468", *scratch)) { /* all done, write result */
127 if (*scratch != '2') shriek(scratch);
128 if (!size) shriek("No processed data received");
129 b[size] = 0;
130 return b;
131 }
132 if (!strncmp(scratch, "123 ", 4)) {
133 int count;
134 sgets(scratch, scfg->scratch_size, ctrld);
135 scratch[scfg->scratch_size] = 0;
136 sscanf(scratch, "%d", &count);
137 b = size ? (char *)realloc(b, size + count + 1) : (char *)malloc(count + 1);
138 int limit = size + count;
139 while (size < limit)
140 size += yread(datad, b + size, limit - size);
141 }
142 }
143 if (size) shriek("Disconnect during transmit");
144 else shriek("Disconnect before transmit");
145 return NULL;
146 }
147
say_data()148 void say_data()
149 {
150 if (!data) data = strdup("No.");
151 send_to_epos("strm $", ctrld);
152 send_to_epos(dh, ctrld);
153 if (chunking) send_to_epos(":chunk", ctrld);
154 if (traditional) send_to_epos(":raw:rules:diphs:synth:", ctrld);
155 else send_to_epos(":raw:rules:dump:syn:", ctrld);
156 if (wavfile || wavstdout) send_to_epos("$", ctrld), send_to_epos(dh, ctrld);
157 else send_to_epos("#localsound", ctrld);
158 send_to_epos("\r\n", ctrld);
159 send_to_epos("appl ", ctrld);
160 sprintf(scratch, "%d", (int)strlen(data));
161 send_to_epos(scratch, ctrld);
162 send_to_epos("\r\n", ctrld);
163 send_to_epos(data, datad);
164 if (get_result(ctrld) > 2) shriek("Could not set up a stream");
165 char *b;
166 if (wavfile || wavstdout) {
167 b = get_data();
168 FILE *f = wavstdout ? stdout : fopen(output_file, "wb");
169 if (!size || !b) shriek("Could not get waveform");
170 if (!f || !fwrite(b, size, 1, f)) shriek("Could not write waveform");
171 if (!wavstdout) fclose(f);
172 free(b);
173 return;
174 }
175 if (get_result(ctrld) > 2) shriek("Could not apply a stream");
176 }
177
trans_data()178 void trans_data()
179 {
180 if (!data) data = strdup("No.");
181 send_to_epos("strm $", ctrld);
182 send_to_epos(dh, ctrld);
183 if (chunking) send_to_epos(":chunk", ctrld);
184 if (show_segments) send_to_epos(traditional ? ":raw:rules:diphs:$"
185 : ":raw:rules:dump:$", ctrld);
186 else send_to_epos(":raw:rules:print:$", ctrld);
187 send_to_epos(dh, ctrld);
188 send_to_epos("\r\n", ctrld);
189 send_to_epos("appl ", ctrld);
190 sprintf(scratch, "%d", (int)strlen(data));
191 send_to_epos(scratch, ctrld);
192 send_to_epos("\r\n", ctrld);
193 send_to_epos(data, datad);
194 get_result(ctrld);
195
196 if (show_segments) {
197 segment *b = (segment *)get_data();
198 if (traditional)
199 for (int i=1; i<b[0].code; i++)
200 printf("%4d - %3d %3d %3d\n", b[i].code, b[i].f, b[i].e, b[i].t);
201 else printf("%s\n", (char *)b);
202 } else {
203 char *b = get_data();
204 printf("%s\n", b);
205 }
206 }
207
208 #ifdef HAVE_UNISTD_H
restart_epos()209 void restart_epos()
210 {
211 int timeout;
212 int d = just_connect_socket(0, 8778);
213 int w;
214 if (d == -1) {
215 shriek("Not running at port 8778");
216 }
217 system("killall -HUP eposd");
218 signal(SIGPIPE, SIG_IGN);
219 timeout = 30;
220 do {
221 usleep(100000);
222 w = send_to_epos(" ", d);
223 if (!timeout--) {
224 shriek("Restart not attempted");
225 }
226 } while (w != -1);
227
228 timeout = 30;
229 do usleep(100000); while(just_connect_socket(0, 8778) == -1 && timeout--);
230 if (timeout == -1) shriek("Restart attempted, but timed out");
231 }
232 #endif
233
send_option(const char * name,const char * value)234 void send_option(const char *name, const char *value)
235 {
236 if (debug_ttscp) {
237 printf("setl %s %s\n", name, value);
238 }
239 xmit_option(name, value, ctrld);
240 get_result(ctrld);
241 }
242
243 #define CMD_LINE_OPT "-"
244 #define CMD_LINE_VAL '='
245
dump_help()246 void dump_help()
247 {
248 printf("usage: say-epos [options] ['Text to be processed.']\n");
249 printf(" -b bare format (no frills)\n");
250 printf(" -c casual pronunciation\n");
251 printf(" -d show segments\n");
252 printf(" -k shutdown Epos\n");
253 printf(" -l list available languages and voices\n");
254 printf(" -m write the waveform in mu law format to ./said.vox\n");
255 printf(" -o write the waveform to the standard output\n");
256 #ifdef HAVE_UNISTD_H
257 printf(" -r reread Epos configuration\n");
258 #endif
259 printf(" -s use the SAMPA-based (MBROLA compatible) synthesizer interface\n");
260 printf(" -t use the traditional lower level synthesizer interface\n");
261 printf(" -u use utterance chunking\n");
262 printf(" -w write the waveform to ./said.wav\n");
263 printf(" -x transcribe only (do not synthesize)\n");
264 printf(" -z display the TTSCP protocol exchange in the control connection\n");
265 printf(" --some_long_option ...as documented (or listed by 'eposd -H')\n");
266 }
267
send_cmd_line(int argc,char ** argv)268 void send_cmd_line(int argc, char **argv)
269 {
270 char *ar;
271 char *j = NULL;
272 register char *par;
273
274 for(int i=1; i<argc; i++) {
275 ar=argv[i];
276 switch(strspn(ar, CMD_LINE_OPT)) {
277 case 3:
278 ar+=3;
279 if (strchr(ar, CMD_LINE_VAL))
280 shriek("Thrice dashed options have an implicit value");
281 send_option(ar, "0");
282 break;
283 case 2:
284 ar+=2;
285 par=strchr(ar, CMD_LINE_VAL);
286 if (par) { //opt=val
287 *par=0;
288 send_option(ar, par+1);
289 *par=CMD_LINE_VAL;
290 } else if (i+1==argc || strchr(CMD_LINE_OPT, *argv[i+1]))
291 send_option(ar, ""); //opt
292 else send_option(ar, argv[++i]); //opt val
293 break;
294 case 1:
295 for (j=ar+1; *j; j++) switch (*j) {
296 case 'b': send_option("out_verbose", "false"); break;
297 case 'c': send_option("colloquial", "true"); break;
298 case 'd': show_segments = true; break;
299 // case 'd': send_option("show_segments", "true"); break;
300 case 'H': send_option("long_help", "true"); /* fall through */
301 case 'h': dump_help(); exit(0);
302 case 'k': FILE *f;
303 f = fopen("/var/run/epos.pwd", "r");
304 if (!f) {
305 stop_service();
306 shriek("Cannot open /var/run/epos.pwd");
307 }
308 send_to_epos("pass ", ctrld);
309 scratch[fread(scratch, 1, 1024, f)] = 0;
310 send_to_epos(scratch, ctrld);
311 send_to_epos("down\r\n", ctrld);
312 get_result(ctrld);
313 get_result(ctrld);
314 exit(0);
315 case 'l': send_to_epos("show languages\r\nshow voices\r\n", ctrld);
316 printf("Languages available:\n");
317 other_traffic_prefix = "";
318 get_result(ctrld);
319 printf("Voices available for the current language:\n");
320 get_result(ctrld);
321 exit(0);
322 case 'm': send_option("ulaw", "true");
323 send_option("wave_header", "false");
324 wavfile = true;
325 output_file = "said.vox"; break;
326 case 'o': wavstdout = true; break;
327 case 'p': send_option("pausing", "true"); break;
328 #ifdef HAVE_UNISTD_H
329 case 'r': restart_epos();
330 exit(0);
331 #endif
332 case 's': traditional = false; break;
333 case 't': traditional = true; break;
334 case 'u': chunking = true; break;
335 case 'v': send_option("version", "true"); break;
336 case 'w': send_option("wave_header", "true");
337 wavfile = true;
338 output_file = "said.wav"; break;
339 case 'x': do_say = false; break;
340 case 'z': debug_ttscp = true; break;
341 case 'D':
342 send_option("debug", "true");
343 // if (!scfg->debug) scfg->debug=true;
344 // else if (scfg->warnings)
345 // scfg->always_dbg--;
346 break;
347 default : shriek("Unknown short option");
348 }
349 if (j==ar+1) { //dash only
350 goto join;
351 }
352 break;
353 case 0:
354 join:
355 if (data) {
356 int needed = strlen(data) + strlen(ar) + 2;
357 if (needed > scfg->scratch_size) {
358 scratch = (char *)realloc(scratch, needed + 2);
359 scfg->scratch_size = needed;
360 }
361 sprintf(scratch, "%s %s", data, ar);
362 free(data);
363 data = strdup(scratch);
364 } else data = strdup(ar);
365
366 break;
367 default:
368 shriek("Too many dashes ");
369 }
370 }
371 if (data && !strcmp("-", data)) {
372 free(data);
373 data = (char *)malloc(STDIN_BUFF_SIZE + 2);
374 data[fread(data, 1, STDIN_BUFF_SIZE, stdin)] = 0;
375 }
376 if (data) for(char *p=data; *p; p++) if (*p == '\n' || *p == '\r') *p=' ';
377 if (data && strspn(data, ",.!?:;+=-*/&^%#$_<>{}()[]|\\~`' \04\t\"") == strlen(data))
378 shriek("Input text too funny");
379 }
380
381 /*
382 * main() implements what most TTSCP clients do: it opens two TTSCP connections,
383 * converts one of them to a data connection dependent on the other one.
384 * Then, commands in a file found using the TTSCP_USER environment variable
385 * are transmitted and synthesis and transcription procedures invoked.
386 * Last, general cleanup is done (the connections are gracefully closed.)
387 *
388 * Note that the connection establishment code is less intuitive than
389 * it could be because of paralelism oriented code ordering.
390 */
391
main(int argc,char ** argv)392 int main(int argc, char **argv)
393 {
394 if (argc > 1 && !strcmp(argv[1], "--help")) {
395 dump_help();
396 exit(0);
397 }
398
399 #ifdef HAVE_WINSOCK
400 if (WSAStartup(MAKEWORD(2,0), (LPWSADATA)scratch)) shriek(464, "No winsock");
401 charset = "cp1250";
402 #endif
403 start_service(); /* Windows NT etc. only */
404
405 ctrld = connect_socket(0, 0);
406 datad = connect_socket(0, 0);
407 send_to_epos("data ", datad);
408 ch = get_handle(ctrld);
409 send_to_epos(ch, datad);
410 send_to_epos("\r\n", datad);
411 free(ch);
412 send_to_epos("setl charset ",ctrld);
413 send_to_epos(charset, ctrld);
414 send_to_epos("\r\n", ctrld);
415 get_result(ctrld);
416 send_cmd_line(argc, argv);
417 dh = get_handle(datad);
418 get_result(datad);
419
420 // #ifdef HAVE_GETENV
421 FILE *f = NULL;
422 char *ttscp_user_config = getenv("TTSCP_USER");
423 if (ttscp_user_config) f = fopen(ttscp_user_config, "rt");
424 if (f) {
425 while (!feof(f)) {
426 *scratch = 0;
427 fgets(scratch, scfg->scratch_size, f);
428 if (*scratch && !strchr(COMMENT_LINES, scratch[strspn(scratch, WHITESPACE)])) {
429 send_to_epos(scratch, ctrld);
430 get_result(ctrld);
431 }
432 }
433 fclose(f);
434 }
435 /// #endif
436
437 if (!wavstdout) trans_data();
438 if (do_say) say_data();
439 send_to_epos("delh ", ctrld);
440 send_to_epos(dh, ctrld);
441 send_to_epos("\r\ndone\r\n", ctrld);
442 get_result(ctrld);
443 get_result(ctrld);
444 close(datad);
445 close(ctrld);
446 return 0;
447 }
448
449
450