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