1
2
3
4
5
6
7 package com.octo.captcha.component.sound.wordtosound;
8
9 import com.octo.captcha.CaptchaException;
10 import com.octo.captcha.component.sound.soundconfigurator.FreeTTSSoundConfigurator;
11 import com.octo.captcha.component.sound.soundconfigurator.SoundConfigurator;
12 import com.sun.speech.freetts.Voice;
13 import com.sun.speech.freetts.VoiceManager;
14 import com.sun.speech.freetts.audio.AudioPlayer;
15 import com.sun.speech.freetts.util.Utilities;
16
17 import javax.sound.sampled.AudioFormat;
18 import javax.sound.sampled.AudioInputStream;
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.SequenceInputStream;
23 import java.util.Locale;
24 import java.util.Vector;
25
26 /***
27 * WordToSound implementation with FreeTTS an openSource Text To Speech implementation.
28 *
29 * @author Benoit
30 * @version 1.0
31 */
32 public abstract class AbstractFreeTTSWordToSound implements WordToSound {
33 public static String defaultVoiceName = "kevin16";
34
35 public static String defaultVoicePackage = "com.sun.speech.freetts.en.us.cmu_time_awb.AlanVoiceDirectory,com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory";
36
37 private static String FREETTS_PROPERTIES_KEY = "freetts.voices";
38
39 private int maxAcceptedWordLength;
40
41 private int minAcceptedWordLength;
42
43 private SoundConfigurator configurator = null;
44
45 private Voice voice = null;
46
47 /***
48 * default Voice, allocated at instanciation
49 */
50 private Voice defaultVoice = null;
51
52 private Locale locale = null;
53
54 private VoiceManager voiceManager = null;
55
56 private boolean isInitiated = false;
57
58 /***
59 * Constructor
60 *
61 * @deprecated
62 */
63 public AbstractFreeTTSWordToSound() {
64 configurator = new FreeTTSSoundConfigurator(AbstractFreeTTSWordToSound.defaultVoiceName,
65 AbstractFreeTTSWordToSound.defaultVoicePackage, 1.0f, 100, 100);
66
67 minAcceptedWordLength = 4;
68 maxAcceptedWordLength = 6;
69 init();
70 }
71
72 /***
73 * Constructor for a FreeTTS implmentation of WordToSound. This constructor imply that WordToSound only use one
74 * voice define by voiceName, with its own locale
75 *
76 * @param configurator Voice configuration
77 * @param minAcceptedWordLength Length Minimal of generated words
78 * @param maxAcceptedWordLength Length Maximal of generated words
79 */
80 public AbstractFreeTTSWordToSound(SoundConfigurator configurator, int minAcceptedWordLength,
81 int maxAcceptedWordLength) {
82 this.configurator = configurator;
83 this.minAcceptedWordLength = minAcceptedWordLength;
84 this.maxAcceptedWordLength = maxAcceptedWordLength;
85 init();
86 }
87
88 /***
89 * @see com.octo.captcha.component.sound.wordtosound.WordToSound#getSound(java.lang.String)
90 */
91 public AudioInputStream getSound(String word) throws CaptchaException {
92
93 voice = defaultVoice;
94 return addEffects(stringToSound(word));
95 }
96
97 /***
98 * @see WordToSound#getSound(String, java.util.Locale)
99 */
100 public AudioInputStream getSound(String word, Locale locale) throws CaptchaException {
101
102 Voice[] voices = voiceManager.getVoices();
103 Voice selectedVoice = null;
104
105
106 if (defaultVoice.getLocale().equals(locale)) {
107 voice = defaultVoice;
108 } else {
109
110 for (int i = 0; i < voices.length; i++) {
111 if (voices[i].getLocale().equals(locale)) {
112 selectedVoice = voices[i];
113 }
114 }
115
116 if (selectedVoice != null) {
117 selectedVoice.allocate();
118 voice = selectedVoice;
119 configureVoice(voice);
120 } else {
121 throw new CaptchaException("No voice corresponding to the Locale");
122 }
123 }
124
125 return addEffects(stringToSound(word));
126 }
127
128 public int getMaxAcceptedWordLength() {
129 return maxAcceptedWordLength;
130 }
131
132 public int getMinAcceptedWordLength() {
133 return minAcceptedWordLength;
134 }
135
136 /***
137 * @return the max word lenght accepted by this word2image service
138 * @deprecated misspelled, use {@link #getMaxAcceptedWordLength()} instead
139 */
140 public int getMaxAcceptedWordLenght() {
141 return maxAcceptedWordLength;
142 }
143
144 /***
145 * @return the min word lenght accepted by this word2image service
146 * @deprecated misspelled, use {@link #getMinAcceptedWordLength()} instead
147 */
148 public int getMinAcceptedWordLenght() {
149 return minAcceptedWordLength;
150 }
151
152 protected abstract AudioInputStream addEffects(AudioInputStream sound);
153
154 /***
155 * Method to initialise FreeTTS
156 */
157 private void init() {
158 if (!isInitiated) {
159
160
161 addToSystemesPropetites(this.configurator.getLocation());
162
163
164 voiceManager = VoiceManager.getInstance();
165
166 this.defaultVoice = voiceManager.getVoice(this.configurator.getName());
167
168 configureVoice(this.defaultVoice);
169
170
171 this.defaultVoice.allocate();
172 isInitiated = true;
173 }
174 }
175
176 /***
177 * Add the package to the system properties, will be used by FreeTTS to find all data for voices.
178 */
179 private static void addToSystemesPropetites(String soundPackage) {
180
181 String packages = System.getProperty(FREETTS_PROPERTIES_KEY);
182 if (packages == null) {
183 packages = soundPackage;
184 } else if (packages.indexOf(soundPackage) == -1) {
185 packages += "," + soundPackage;
186 }
187
188 System.getProperties().put(FREETTS_PROPERTIES_KEY, packages);
189 }
190
191 /***
192 * Configue the voice with the SoundConfigurator
193 */
194 private void configureVoice(Voice voice) {
195 voice.setPitch(configurator.getPitch());
196 voice.setVolume(configurator.getVolume());
197 voice.setRate(configurator.getRate());
198 }
199
200 /***
201 * Main method for this service Return an image with the specified. Synchronisation is very important, for multi
202 * threading execution
203 *
204 * @param sentence Written sentece to transform into speech
205 *
206 * @return the generated sound
207 *
208 * @throws com.octo.captcha.CaptchaException
209 * if word is invalid or an exception occurs during the sound generation
210 */
211 public synchronized AudioInputStream stringToSound(String sentence) throws CaptchaException {
212
213
214 InputStreamAudioPlayer audioPlayer = new InputStreamAudioPlayer();
215
216 this.voice.setAudioPlayer(audioPlayer);
217
218
219 this.voice.speak(sentence);
220
221 AudioInputStream ais = audioPlayer.getAudioInputStream();
222 return ais;
223 }
224
225 /***
226 * Implementation of freeTTS AudioPlayer interface, to produce an audioInputStream, this is not a very clean way
227 * since it doesn't really play. But it is the only way to get a stream easily
228 */
229 private class InputStreamAudioPlayer implements AudioPlayer {
230 private boolean debug = false;
231
232 private AudioFormat currentFormat = null;
233
234 private byte[] outputData;
235
236 private int curIndex = 0;
237
238 private int totBytes = 0;
239
240 private Vector outputList;
241
242 private AudioInputStream audioInputStream;
243
244 /***
245 * Constructs a InputStreamAudioPlayer
246 *
247 */
248 public InputStreamAudioPlayer() {
249 debug = Utilities.getBoolean("com.sun.speech.freetts.audio.AudioPlayer.debug");
250 outputList = new Vector();
251 }
252
253 /***
254 * Sets the audio format for this player
255 *
256 * @param format the audio format
257 *
258 * @throws UnsupportedOperationException if the line cannot be opened with the given format
259 */
260 public synchronized void setAudioFormat(AudioFormat format) {
261 currentFormat = format;
262 }
263
264 /***
265 * Gets the audio format for this player
266 *
267 * @return format the audio format
268 */
269 public AudioFormat getAudioFormat() {
270 return currentFormat;
271 }
272
273 /***
274 * Pauses audio output
275 */
276 public void pause() {
277 }
278
279 /***
280 * Resumes audio output
281 */
282 public synchronized void resume() {
283 }
284
285 /***
286 * Cancels currently playing audio
287 */
288 public synchronized void cancel() {
289 }
290
291 /***
292 * Prepares for another batch of output. Larger groups of output (such as all output associated with a single
293 * FreeTTSSpeakable) should be grouped between a reset/drain pair.
294 */
295 public synchronized void reset() {
296 }
297
298 /***
299 * Starts the first sample timer
300 */
301 public void startFirstSampleTimer() {
302 }
303
304 /***
305 * Closes this audio player
306 */
307 public synchronized void close() {
308 try {
309 audioInputStream.close();
310 } catch (IOException ioe) {
311 System.err.println("Problem while closing the audioInputSteam");
312 }
313
314 }
315
316 public AudioInputStream getAudioInputStream() {
317 InputStream tInputStream = new SequenceInputStream(outputList.elements());
318 AudioInputStream tAudioInputStream = new AudioInputStream(tInputStream, currentFormat,
319 totBytes / currentFormat.getFrameSize());
320
321 return tAudioInputStream;
322 }
323
324 /***
325 * Returns the current volume.
326 *
327 * @return the current volume (between 0 and 1)
328 */
329 public float getVolume() {
330 return 1.0f;
331 }
332
333 /***
334 * Sets the current volume.
335 *
336 * @param volume the current volume (between 0 and 1)
337 */
338 public void setVolume(float volume) {
339 }
340
341 /***
342 * Starts the output of a set of data. Audio data for a single utterance should be grouped between begin/end
343 * pairs.
344 *
345 * @param size the size of data between now and the end
346 */
347 public void begin(int size) {
348 outputData = new byte[size];
349 curIndex = 0;
350 }
351
352 /***
353 * Marks the end of a set of data. Audio data for a single utterance should be groupd between begin/end pairs.
354 *
355 * @return true if the audio was output properly, false if the output was cancelled or interrupted.
356 */
357 public boolean end() {
358 outputList.add(new ByteArrayInputStream(outputData));
359 totBytes += outputData.length;
360 return true;
361 }
362
363 /***
364 * Waits for all queued audio to be played
365 *
366 * @return true if the audio played to completion, false if the audio was stopped
367 */
368 public boolean drain() {
369 return true;
370 }
371
372 /***
373 * Gets the amount of played since the last mark
374 *
375 * @return the amount of audio in milliseconds
376 */
377 public synchronized long getTime() {
378 return -1L;
379 }
380
381 /***
382 * Resets the audio clock
383 */
384 public synchronized void resetTime() {
385 }
386
387 /***
388 * Writes the given bytes to the audio stream
389 *
390 * @param audioData audio data to write to the device
391 *
392 * @return <code>true</code> of the write completed successfully, <code> false </code> if the write was
393 * cancelled.
394 */
395 public boolean write(byte[] audioData) {
396 return write(audioData, 0, audioData.length);
397 }
398
399 /***
400 * Writes the given bytes to the audio stream
401 *
402 * @param bytes audio data to write to the device
403 * @param offset the offset into the buffer
404 * @param size the size into the buffer
405 *
406 * @return <code>true</code> of the write completed successfully, <code> false </code> if the write was
407 * cancelled.
408 */
409 public boolean write(byte[] bytes, int offset, int size) {
410 System.arraycopy(bytes, offset, outputData, curIndex, size);
411 curIndex += size;
412 return true;
413 }
414
415 /***
416 * Waits for resume. If this audio player is paused waits for the player to be resumed. Returns if resumed,
417 * cancelled or shutdown.
418 *
419 * @return true if the output has been resumed, false if the output has been cancelled or shutdown.
420 */
421 private synchronized boolean waitResume() {
422 return true;
423 }
424
425 /***
426 * Returns the name of this audioplayer
427 *
428 * @return the name of the audio player
429 */
430 public String toString() {
431 return "AudioInputStreamAudioPlayer";
432 }
433
434 /***
435 * Outputs a debug message if debugging is turned on
436 *
437 * @param msg the message to output
438 */
439 private void debugPrint(String msg) {
440 if (debug) {
441 System.out.println(toString() + ": " + msg);
442 }
443 }
444
445 /***
446 * Shows metrics for this audio player
447 */
448 public void showMetrics() {
449 }
450 }
451
452 }
453