File: EmacspeakProtocolHandler.java

package info (click to toggle)
freetts 1.2.2-8
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 65,244 kB
  • sloc: java: 21,305; xml: 1,340; sh: 969; lisp: 587; ansic: 241; makefile: 25; awk: 11
file content (363 lines) | stat: -rw-r--r-- 11,373 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
/**
 * Copyright 2001 Sun Microsystems, Inc.
 * 
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL 
 * WARRANTIES.
 */
import com.sun.speech.freetts.util.Utilities;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;

import java.net.Socket;
import java.net.SocketTimeoutException;


/**
 * Implements a very simplified (and incomplete) version of the
 * Emacspeak speech server.
 *
 * See the Emacspeak protocol document at
 * http://emacspeak.sourceforge.net/info/html/TTS-Servers.html
 * for more information.
 */
public abstract class EmacspeakProtocolHandler implements Runnable {

    // network related variables
    private Socket socket;
    private BufferedReader reader;
    private OutputStream writer;

    // synthesizer related variables
    protected static final String PARENS_STAR_REGEX = "[.]*\\[\\*\\][.]*";
    private static final int NOT_HANDLED_COMMAND = 1;
    private static final int LETTER_COMMAND = 2;
    private static final int QUEUE_COMMAND = 3;
    private static final int TTS_SAY_COMMAND = 4;
    private static final int STOP_COMMAND = 5;
    private static final int EXIT_COMMAND = 6;
    private static final int RATE_COMMAND = 7;
    
    private String lastQueuedCommand;
    private String stopQuestionStart = "Active processes exist;";

    private boolean debug = false;

    /**
     * Sometimes emacspeak will embed DECTalk escape sequences in the
     * text.  These sequences are not meant to be spoken, and FreeTTS
     * currently does not interpret them.  This simple flag provides
     * a mechanism for FreeTTS to cut strings of the form "[...]"
     * out of text to be spoken (DECTalk escape sequences are of the
     * form "[...]").  Since this is a relatively heavy-handed thing
     * to do, this feature is turned off by default.  To turn it on,
     * add -DstripDECTalk=true to the command line.
     */
    private boolean stripDECTalk = false;

    /**
     * Sets the Socket to be used by this ProtocolHandler.
     *
     * @param socket the Socket to be used
     */
    public void setSocket(Socket socket) {
	this.socket = socket;
	if (socket != null) {
	    try {
		reader = new BufferedReader
		    (new InputStreamReader(socket.getInputStream()));
		writer = new DataOutputStream(socket.getOutputStream());
                socket.setKeepAlive(true);
                // socket.setSoTimeout(5000);
	    } catch (IOException ioe) {
		ioe.printStackTrace();
		throw new Error();
	    }
	}
    }


    /**
     * Returns the socket used.
     *
     * @return the socket used
     */
    public Socket getSocket() {
        return socket;
    }


    /**
     * Set to debug mode, which will print out debug messages.
     *
     * @param true if set to debug mode, false if set to non-debug mode.
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }


    /**
     * Returns true if the given input string starts with the given
     * starting and ending sequence.
     *
     * @param start the starting character sequence
     * @param end the ending character sequence
     * 
     * @return true if the input string matches the given Pattern;
     *         false otherwise
     */
    private static boolean matches(String start, String end, String input) {
	return (input.startsWith(start) && input.endsWith(end));
    }


    /**
     * Returns the type of the given command.
     *
     * @param command the command from emacspeak
     *
     * @return the command type
     */
    private static int getCommandType(String command) {
	int type = NOT_HANDLED_COMMAND;
        if (command.startsWith("l ")) {
            type = LETTER_COMMAND;
        } else if (command.startsWith("q ")) {
            type = QUEUE_COMMAND;
        } else if (command.startsWith("tts_say ")) {
            type = TTS_SAY_COMMAND;
        } else if (command.startsWith("tts_set_speech_rate ")) {
            type = RATE_COMMAND;
        } else if (command.equals("s")) {
            type = STOP_COMMAND;
        } else if (command.equals("exit")) {
            type = EXIT_COMMAND;
        }
	return type;
    }

    
    /**
     * Returns the text of the given input that is within curly
     * brackets.  If there are no curly brackets (allowed in the
     * Emacspeak protocol if the text has no spaces), then it
     * just returns the text after the first space.
     *
     * @param input the input text
     *
     * @return text within curly brackets
     */
    public static String textInCurlyBrackets(String input) {
	String result = "";
	if (input.length() > 0) {
	    int first = input.indexOf('{');
            if (first == -1) {
                first = input.indexOf(' ');
            }
	    int last = input.lastIndexOf('}');
            if (last == -1) {
                last = input.length();
            }
	    if (first != -1 && last != -1 &&
		first < last) {
		result = input.substring(first+1, last);
	    }
	}
	return result.trim();
    }


    /**
     * Strips DECTalk commands from the input text.  The DECTalk
     * commands are anything inside "[" and "]".
     */
    public String stripDECTalkCommands(String content) {
        int startPos = content.indexOf('[');
        while (startPos != -1) {
            int endPos = content.indexOf(']');
            if (endPos != -1) {
                if (startPos == 0) {
                    if (endPos == (content.length() - 1)) {
                        content = "";
                    } else {
                        content = content.substring(endPos + 1);
                    }
                } else {
                    if (endPos == (content.length() - 1)) {
                        content = content.substring(0, startPos);
                    } else {
                        String firstPart = content.substring(0, startPos);
                        String secondPart = content.substring(endPos + 1);
                        content = firstPart + " " + secondPart;
                    }
                }
                startPos = content.indexOf('[');
            } else {
                break;
            }
        }
        return content.trim();
    }

    
    /**
     * Speaks the given input text.
     *
     * @param input the input text to speak.
     */
    public abstract void speak(String input);


    /**
     * Removes all the queued text.
     */
    public abstract void cancelAll();


    /**
     * Sets the speaking rate.
     *
     * @param wpm the new speaking rate (words per minute)
     */
    public abstract void setRate(float wpm);


    /**
     * Implements the run() method of Runnable
     */
    public synchronized void run() {
        try {
            String command = "";
            stripDECTalk = Utilities.getBoolean("stripDECTalk");
            while (isSocketLive()) {
                command = reader.readLine();
                if (command != null) {
                    command = command.trim();
                    debugPrintln("IN   : " + command);

                    int commandType = getCommandType(command);

                    if (commandType == EXIT_COMMAND) {
                        socket.close();
                        notifyAll();
                    } else if (commandType == STOP_COMMAND) {
                        cancelAll();
                    } else if (commandType == RATE_COMMAND) {
                        try {
                            setRate(Float.parseFloat(
                                        textInCurlyBrackets(command)));
                        } catch (NumberFormatException e) {
                            // ignore and do nothing
                        }
                    } else if (commandType != NOT_HANDLED_COMMAND) {
                        String content = textInCurlyBrackets(command);
                        if (stripDECTalk) {
                            content = stripDECTalkCommands(content);
                        }
                        if (content.length() > 0) {
                            speak(content);
                        }
                        // detect if emacspeak is trying to quit
                        detectQuitting(commandType, content);
                    } else {
                        debugPrintln("SPEAK:");
                    }
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } finally {
            debugPrintln("EmacspeakProtocolHandler: thread terminated");
        }
    }


    /**
     * Returns true if the Socket is still alive.
     *
     * @return true if the Socket is still alive
     */
    private boolean isSocketLive() {
        return (socket.isBound() &&
                !socket.isClosed() && socket.isConnected() &&
                !socket.isInputShutdown() && !socket.isOutputShutdown());
    }


    /**
     * Read a line of text. A line is considered to be terminated 
     * by any one of a line feed ('\n'), a carriage return ('\r'),
     * or a carriage return followed immediately by a linefeed. 
     *
     * @return A String containing the contents of the line, 
     * not including any line-termination
     * characters, or null if the end of the stream has been reached
     *
     * @throws IOException if an I/O error occurs
     */
    private String readLine() throws IOException {
        String command = null;
        boolean repeat = false;
        do {
            try {
                command = reader.readLine();
                repeat = false;
            } catch (SocketTimeoutException ste) {
                System.out.println("timed out");
                /*
                writer.write(-1);
                writer.flush();
                */
                repeat = isSocketLive();
            }
        } while (repeat);
     
        return command;
    }

    
    /**
     * Detects and handles a possible emacspeak quitting sequence 
     * of commands, by looking at the given command type and content.
     * If a quitting sequence is detected, it will close the socket.
     * Note that this is not the best way to trap a quitting sequence,
     * but I can't find another way to trap it. This method will
     * do a socket.notifyAll() to tell objects waiting on the socket
     * that it has been closed. 
     *
     * @param commandType the command type
     * @param content the contents of the command
     */
    private synchronized void detectQuitting(int commandType, String content)
        throws IOException {
        if (commandType == QUEUE_COMMAND) {
            lastQueuedCommand = content;
        } else if (commandType == TTS_SAY_COMMAND) {
            if (content.equals("no")) {
                lastQueuedCommand = "";
            } else if (content.equals("yes") &&
                       lastQueuedCommand.startsWith(stopQuestionStart)) {
                socket.close();
                notifyAll();
            }
        }
    }


    /**
     * Prints the given message if the <code>debug</code> System property
     * is set to <code>true</code>.
     *
     * @param message the message to print
     */ 
    public void debugPrintln(String message) {
	if (debug) {
	    System.out.println(message);
	}
    }
}