diff --git a/.gitignore b/.gitignore
index b6d585c..812f83f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@
.project
.settings
/target
+/.idea
+java-speech-api.iml
\ No newline at end of file
diff --git a/README.markdown b/README.markdown
index e6bfcf7..2f0246a 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,4 +1,4 @@
-#J.A.R.V.I.S. (Java-Speech-API)
+# J.A.R.V.I.S. (Java-Speech-API)
J.A.R.V.I.S. Java Speech API: Just A Reliable Vocal Interpreter & Synthesizer.
This is a project for the Java Speech API. The program interprets vocal inputs into text and synthesizes voices from text input.
@@ -7,7 +7,7 @@ The program supports dozens of languages and even has the ability to auto-detect
## Description
The J.A.R.V.I.S. Speech API is designed to be simple and efficient, using the speech engines created by Google to provide functionality for parts of the API. Essentially, it is an API written in Java, including a recognizer, synthesizer, and a microphone capture utility. The project uses Google services for the synthesizer and recognizer. While this requires an Internet connection, it provides a complete, modern, and fully functional speech API in Java.
-##Features
+## Features
The API currently provides the following functionality,
* Microphone Capture API (Wrapped around the current Java API for simplicity)
@@ -19,7 +19,7 @@ The API currently provides the following functionality,
* Wave to FLAC API (Wrapped around the used API in the project, javaFlacEncoder, see CREDITS)
* A translator using Google Translate (courtesy of Skylion's Google Toolkit)
-##Notes
+## Notes
To get access to the Google API, you need an API key. To get this, you need to follow the instructions here:
* https://stackoverflow.com/questions/26485531/google-speech-api-v2
@@ -27,8 +27,8 @@ To get access to the Google API, you need an API key. To get this, you need to f
A sample application using this library can be found here:
* See API-Example repository branch.
-##Changelog
+## Changelog
See CHANGELOG.markdown for Version History/Changelog
-##Credits
+## Credits
See CREDITS.markdown for Credits
diff --git a/pom.xml b/pom.xml
index 0c80b1c..9219c31 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,6 +9,8 @@
UTF-8
+ 1.6
+ 1.6
java-speech-api
@@ -82,6 +84,11 @@
json
20150729
+
+ javax.xml.ws
+ jaxws-api
+ 2.3.0
+
diff --git a/src/main/java/com/darkprograms/speech/microphone/Microphone.java b/src/main/java/com/darkprograms/speech/microphone/Microphone.java
index 66fe962..7859050 100644
--- a/src/main/java/com/darkprograms/speech/microphone/Microphone.java
+++ b/src/main/java/com/darkprograms/speech/microphone/Microphone.java
@@ -11,17 +11,18 @@
* @author Luke Kuza, Aaron Gokaslan
***************************************************************************/
public class Microphone implements Closeable{
-
+
/**
* TargetDataLine variable to receive data from microphone
*/
private TargetDataLine targetDataLine;
+
/**
* Enum for current Microphone state
*/
public enum CaptureState {
- PROCESSING_AUDIO, STARTING_CAPTURE, CLOSED
+ PROCESSING_AUDIO, STARTING_CAPTURE, CLOSED;
}
/**
@@ -39,6 +40,18 @@ public enum CaptureState {
*/
private File audioFile;
+ /**
+ * Constructor
+ *
+ * @param fileType File type to save the audio in
+ * Example, to save as WAVE use AudioFileFormat.Type.WAVE
+ */
+ public Microphone(AudioFileFormat.Type fileType) {
+ setState(CaptureState.CLOSED);
+ setFileType(fileType);
+ initTargetDataLine();
+ }
+
/**
* Gets the current state of Microphone
*
@@ -79,22 +92,10 @@ public TargetDataLine getTargetDataLine() {
return targetDataLine;
}
+
public void setTargetDataLine(TargetDataLine targetDataLine) {
this.targetDataLine = targetDataLine;
}
-
-
- /**
- * Constructor
- *
- * @param fileType File type to save the audio in
- * Example, to save as WAVE use AudioFileFormat.Type.WAVE
- */
- public Microphone(AudioFileFormat.Type fileType) {
- setState(CaptureState.CLOSED);
- setFileType(fileType);
- initTargetDataLine();
- }
/**
* Initializes the target data line.
diff --git a/src/main/java/com/darkprograms/speech/microphone/MicrophoneAnalyzer.java b/src/main/java/com/darkprograms/speech/microphone/MicrophoneAnalyzer.java
index 34d6ad2..52ef1df 100644
--- a/src/main/java/com/darkprograms/speech/microphone/MicrophoneAnalyzer.java
+++ b/src/main/java/com/darkprograms/speech/microphone/MicrophoneAnalyzer.java
@@ -121,7 +121,7 @@ public int getFrequency(){
/**
* Calculates the frequency based off of the number of bytes.
- * CAVEAT: THE NUMBER OF BYTES MUST BE A MULTIPLE OF 2!!!
+ * CAVEAT: The number of Bytes must be a multiple of 2!
* @param numOfBytes The number of bytes which must be a multiple of 2!!!
* @return The calculated frequency in Hertz.
*/
@@ -173,7 +173,7 @@ private double[] applyHanningWindow(double[] data){
private double[] applyHanningWindow(double[] signal_in, int pos, int size){
for (int i = pos; i < pos + size; i++){
int j = i - pos; // j = index into Hann window function
- signal_in[i] = (double)(signal_in[i] * 0.5 * (1.0 - Math.cos(2.0 * Math.PI * j / size)));
+ signal_in[i] = (signal_in[i] * 0.5 * (1.0 - Math.cos(2.0 * Math.PI * j / size)));
}
return signal_in;
}
diff --git a/src/main/java/com/darkprograms/speech/recognizer/GSpeechDuplex.java b/src/main/java/com/darkprograms/speech/recognizer/GSpeechDuplex.java
index a9dc61c..a66e844 100644
--- a/src/main/java/com/darkprograms/speech/recognizer/GSpeechDuplex.java
+++ b/src/main/java/com/darkprograms/speech/recognizer/GSpeechDuplex.java
@@ -151,7 +151,7 @@ public void recognize(byte[] data, int sampleRate){
* @throws IOException
* @throws LineUnavailableException
*/
- public void recognize(TargetDataLine tl, AudioFormat af) throws IOException, LineUnavailableException{
+ public void recognize(TargetDataLine tl, AudioFormat af) throws IOException, LineUnavailableException, InterruptedException {
//Generates a unique ID for the response.
final long PAIR = MIN + (long)(Math.random() * ((MAX - MIN) + 1L));
@@ -164,10 +164,21 @@ public void recognize(TargetDataLine tl, AudioFormat af) throws IOException, Lin
"&key=" + API_KEY + "&continuous=true&interim=true"; //Tells Google to constantly monitor the stream;
//Opens downChannel
- this.downChannel(API_DOWN_URL);
+ Thread downChannel = this.downChannel(API_DOWN_URL);
//Opens upChannel
- this.upChannel(API_UP_URL, tl, af);
+ Thread upChannel = this.upChannel(API_UP_URL, tl, af);
+ try {
+ downChannel.join();
+ upChannel.interrupt();
+ upChannel.join();
+ } catch (InterruptedException e) {
+ downChannel.interrupt();
+ downChannel.join();
+ upChannel.interrupt();
+ upChannel.join();
+ }
+
}
/**
@@ -175,9 +186,9 @@ public void recognize(TargetDataLine tl, AudioFormat af) throws IOException, Lin
* the best way to handle this is through the use of listeners.
* @param The URL you want to connect to.
*/
- private void downChannel(String urlStr) {
+ private Thread downChannel(String urlStr) {
final String url = urlStr;
- new Thread ("Downstream Thread") {
+ Thread downChannelThread = new Thread ("Downstream Thread") {
public void run() {
// handler for DOWN channel http response stream - httpsUrlConn
// response handler should manage the connection.... ??
@@ -206,7 +217,9 @@ public void run() {
inStream.close();
System.out.println("Finished write on down stream...");
}
- }.start();
+ };
+ downChannelThread.start();
+ return downChannelThread;
}
@@ -235,7 +248,7 @@ public void run() {
* @param af The AudioFormat to stream with.
* @throws LineUnavailableException If cannot open or stream the TargetDataLine.
*/
- private void upChannel(String urlStr, TargetDataLine tl, AudioFormat af) throws IOException, LineUnavailableException{
+ private Thread upChannel(String urlStr, TargetDataLine tl, AudioFormat af) throws IOException, LineUnavailableException{
final String murl = urlStr;
final TargetDataLine mtl = tl;
final AudioFormat maf = af;
@@ -243,11 +256,13 @@ private void upChannel(String urlStr, TargetDataLine tl, AudioFormat af) throws
mtl.open(maf);
mtl.start();
}
- new Thread ("Upstream Thread") {
+ Thread upChannelThread = new Thread ("Upstream Thread") {
public void run() {
openHttpsPostConnection(murl, mtl, (int)maf.getSampleRate());
}
- }.start();
+ };
+ upChannelThread.start();
+ return upChannelThread;
}
@@ -289,28 +304,12 @@ private Scanner openHttpsConnection(String urlStr) {
* Opens a HTTPSPostConnection that posts data from a TargetDataLine input
* @param murl The URL you want to post to.
* @param mtl The TargetDataLine you want to post data from. Note should be open
- * @param maf The AudioFormat of the data you want to post
*/
private void openHttpsPostConnection(String murl, TargetDataLine mtl, int sampleRate) {
URL url;
try {
url = new URL(murl);
- URLConnection urlConn = url.openConnection();
- if (!(urlConn instanceof HttpsURLConnection)) {
- throw new IOException ("URL is not an Https URL");
- }
-
- HttpsURLConnection httpConn = (HttpsURLConnection)urlConn;
- httpConn.setAllowUserInteraction(false);
- httpConn.setInstanceFollowRedirects(true);
- httpConn.setRequestMethod("POST");
- httpConn.setDoOutput(true);
- httpConn.setChunkedStreamingMode(0);
- httpConn.setRequestProperty("Transfer-Encoding", "chunked");
- httpConn.setRequestProperty("Content-Type", "audio/x-flac; rate=" + sampleRate);
- // also worked with ("Content-Type", "audio/amr; rate=8000");
- httpConn.connect();
-
+ HttpsURLConnection httpConn = getHttpsURLConnection(sampleRate, url);
// this opens a connection, then sends POST & headers.
final OutputStream out = httpConn.getOutputStream();
//Note : if the audio is more than 15 seconds
@@ -357,20 +356,7 @@ private Scanner openHttpsPostConnection(String urlStr, byte[][] data, int sample
// int http_status;
try {
URL url = new URL(urlStr);
- URLConnection urlConn = url.openConnection();
- if (!(urlConn instanceof HttpsURLConnection)) {
- throw new IOException ("URL is not an Https URL");
- }
- HttpsURLConnection httpConn = (HttpsURLConnection)urlConn;
- httpConn.setAllowUserInteraction(false);
- httpConn.setInstanceFollowRedirects(true);
- httpConn.setRequestMethod("POST");
- httpConn.setDoOutput(true);
- httpConn.setChunkedStreamingMode(0);
- httpConn.setRequestProperty("Transfer-Encoding", "chunked");
- httpConn.setRequestProperty("Content-Type", "audio/x-flac; rate=" + sampleRate);
- // also worked with ("Content-Type", "audio/amr; rate=8000");
- httpConn.connect();
+ HttpsURLConnection httpConn = getHttpsURLConnection(sampleRate, url);
// this opens a connection, then sends POST & headers.
out = httpConn.getOutputStream();
//Note : if the audio is more than 15 seconds
@@ -409,9 +395,33 @@ private Scanner openHttpsPostConnection(String urlStr, byte[][] data, int sample
return null;
}
+ /**
+ * @param sampleRate
+ * @param url
+ * @return
+ * @throws IOException
+ */
+ private HttpsURLConnection getHttpsURLConnection(int sampleRate, URL url) throws IOException {
+ URLConnection urlConn = url.openConnection();
+ if (!(urlConn instanceof HttpsURLConnection)) {
+ throw new IOException ("URL is not an Https URL");
+ }
+ HttpsURLConnection httpConn = (HttpsURLConnection)urlConn;
+ httpConn.setAllowUserInteraction(false);
+ httpConn.setInstanceFollowRedirects(true);
+ httpConn.setRequestMethod("POST");
+ httpConn.setDoOutput(true);
+ httpConn.setChunkedStreamingMode(0);
+ httpConn.setRequestProperty("Transfer-Encoding", "chunked");
+ httpConn.setRequestProperty("Content-Type", "audio/x-flac; rate=" + sampleRate);
+ // also worked with ("Content-Type", "audio/amr; rate=8000");
+ httpConn.connect();
+ return httpConn;
+ }
+
/**
* Converts the file into a byte[]. Also Android compatible. :)
- * @param The File you want to get the byte[] from.
+ * @param infile The File you want to get the byte[] from.
* @return The byte[]
* @throws IOException if something goes wrong in reading the file.
*/
@@ -436,7 +446,11 @@ private void parseResponse(String rawResponse, GoogleResponse gr){
gr.setConfidence(String.valueOf(1));
}
String response = StringUtil.substringBetween(rawResponse, "[{\"transcript\":\"", "\"}],");
+ if (response == null) {
+ response = StringUtil.substringBetween(rawResponse, "[{\"transcript\":\"", "\",\"");
+ }
gr.setResponse(response);
+ gr.setFinalResponse(rawResponse.contains("\"final\":true"));
String[] currentHypos = rawResponse.split("\\[\\{\"transcript\":\"");
for(int i = 2; i otherPossibleResponses = new ArrayList(20);
+
+ private boolean finalResponse = true;
/**
* Constructor
*/
@@ -86,4 +88,11 @@ public List getAllPossibleResponses() {
return tmp;
}
+ public boolean isFinalResponse() {
+ return finalResponse;
+ }
+
+ public void setFinalResponse(boolean finalResponse) {
+ this.finalResponse = finalResponse;
+ }
}
diff --git a/src/main/java/com/darkprograms/speech/recognizer/RecognizerChunked.java b/src/main/java/com/darkprograms/speech/recognizer/RecognizerChunked.java
index 1ca9357..160b395 100644
--- a/src/main/java/com/darkprograms/speech/recognizer/RecognizerChunked.java
+++ b/src/main/java/com/darkprograms/speech/recognizer/RecognizerChunked.java
@@ -188,7 +188,11 @@ public void run() {
} catch (IOException e) {
e.printStackTrace();
}
- finally {httpConn.disconnect();}
+ finally {
+ if(httpConn != null) {
+ httpConn.disconnect();
+ }
+ }
}
}.start();
}
diff --git a/src/main/java/com/darkprograms/speech/synthesiser/BaseSynthsiser.java b/src/main/java/com/darkprograms/speech/synthesiser/BaseSynthsiser.java
new file mode 100644
index 0000000..6d5b0ab
--- /dev/null
+++ b/src/main/java/com/darkprograms/speech/synthesiser/BaseSynthsiser.java
@@ -0,0 +1,163 @@
+package com.darkprograms.speech.synthesiser;
+
+import com.darkprograms.speech.translator.GoogleTranslate;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+/*******************************************************************************
+ * Synthesiser class that connects to Google's unoffical API to retrieve data
+ *
+ * @author Luke Kuza, Aaron Gokaslan (Skylion)
+ *******************************************************************************/
+public abstract class BaseSynthsiser {
+
+ /**
+ * Gets an input stream to MP3 data for the returned information from a request
+ *
+ * @param synthText Text you want to be synthesized into MP3 data
+ * @return Returns an input stream of the MP3 data that is returned from Google
+ * @throws IOException Throws exception if it can not complete the request
+ */
+ public abstract InputStream getMP3Data(String synthText) throws IOException;
+
+ /**
+ * Gets an InputStream to MP3Data for the returned information from a request
+ * @param synthText List of Strings you want to be synthesized into MP3 data
+ * @return Returns an input stream of all the MP3 data that is returned from Google
+ * @throws IOException Throws exception if it cannot complete the request
+ */
+ public InputStream getMP3Data(List synthText) throws IOException {
+ //Uses an executor service pool for concurrency. Limit to 1000 threads max.
+ ExecutorService pool = Executors.newFixedThreadPool(1000);
+ //Stores the Future (Data that will be returned in the future)
+ Set> set = new LinkedHashSet>(synthText.size());
+ for(String part: synthText){ //Iterates through the list
+ Callable callable = new MP3DataFetcher(part);//Creates Callable
+ Future future = pool.submit(callable);//Begins to run Callable
+ set.add(future);//Adds the response that will be returned to a set.
+ }
+ List inputStreams = new ArrayList(set.size());
+ for(Future future: set){
+ try {
+ inputStreams.add(future.get());//Gets the returned data from the future.
+ } catch (ExecutionException e) {//Thrown if the MP3DataFetcher encountered an error.
+ Throwable ex = e.getCause();
+ if(ex instanceof IOException){
+ throw (IOException)ex;//Downcasts and rethrows it.
+ }
+ } catch (InterruptedException e){//Will probably never be called, but just in case...
+ Thread.currentThread().interrupt();//Interrupts the thread since something went wrong.
+ }
+ }
+ return new SequenceInputStream(Collections.enumeration(inputStreams));//Sequences the stream.
+ }
+
+ /**
+ * Separates a string into smaller parts so that Google will not reject the request.
+ * @param input The string you want to separate
+ * @return A List of the String fragments from your input..
+ */
+ protected List parseString(String input){
+ return parseString (input, new ArrayList());
+ }
+
+ /**
+ * Separates a string into smaller parts so that Google will not reject the request.
+ * @param input The string you want to break up into smaller parts
+ * @param fragments List that you want to add stuff too.
+ * If you don't have a List already constructed "new ArrayList()" works well.
+ * @return A list of the fragments of the original String
+ */
+ private List parseString(String input, List fragments){
+ if(input.length()<=100){//Base Case
+ fragments.add(input);
+ return fragments;
+ }
+ else{
+ int lastWord = findLastWord(input);//Checks if a space exists
+ if(lastWord<=0){
+ fragments.add(input.substring(0,100));//In case you sent gibberish to Google.
+ return parseString(input.substring(100), fragments);
+ }else{
+ fragments.add(input.substring(0,lastWord));
+ //Otherwise, adds the last word to the list for recursion.
+ return parseString(input.substring(lastWord), fragments);
+ }
+ }
+ }
+
+ /**
+ * Finds the last word in your String (before the index of 99) by searching for spaces and ending punctuation.
+ * Will preferably parse on punctuation to alleviate mid-sentence pausing
+ * @param input The String you want to search through.
+ * @return The index of where the last word of the string ends before the index of 99.
+ */
+ private int findLastWord(String input){
+ if(input.length()<100)
+ return input.length();
+ int space = -1;
+ for(int i = 99; i>0; i--){
+ char tmp = input.charAt(i);
+ if(isEndingPunctuation(tmp)){
+ return i+1;
+ }
+ if(space==-1 && tmp == ' '){
+ space = i;
+ }
+ }
+ if(space>0){
+ return space;
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if char is an ending character
+ * Ending punctuation for all languages according to Wikipedia (Except for Sanskrit non-unicode)
+ * @param input The char you want check
+ * @return True if it is, false if not.
+ */
+ private boolean isEndingPunctuation(char input){
+ return input == '.' || input == '!' || input == '?' || input == ';' || input == ':' || input == '|';
+ }
+
+ /**
+ * Automatically determines the language of the original text
+ * @param text represents the text you want to check the language of
+ * @return the languageCode in ISO-639
+ * @throws IOException if it cannot complete the request
+ */
+ public String detectLanguage(String text) throws IOException{
+ return GoogleTranslate.detectLanguage(text);
+ }
+
+ /**
+ * This class is a callable.
+ * A callable is like a runnable except that it can return data and throw exceptions.
+ * Useful when using futures. Dramatically improves the speed of execution.
+ * @author Aaron Gokaslan (Skylion)
+ */
+ private class MP3DataFetcher implements Callable{
+ private String synthText;
+
+ public MP3DataFetcher(String synthText){
+ this.synthText = synthText;
+ }
+
+ public InputStream call() throws IOException{
+ return getMP3Data(synthText);
+ }
+ }
+}
diff --git a/src/main/java/com/darkprograms/speech/synthesiser/Synthesiser.java b/src/main/java/com/darkprograms/speech/synthesiser/Synthesiser.java
index 4b4af82..840a3aa 100644
--- a/src/main/java/com/darkprograms/speech/synthesiser/Synthesiser.java
+++ b/src/main/java/com/darkprograms/speech/synthesiser/Synthesiser.java
@@ -26,7 +26,7 @@
*
* @author Luke Kuza, Aaron Gokaslan (Skylion)
*******************************************************************************/
-public class Synthesiser {
+public class Synthesiser extends BaseSynthsiser {
/**
* URL to query for Google synthesiser
@@ -83,13 +83,7 @@ public void setLanguage(String languageCode){
this.languageCode = languageCode;
}
- /**
- * Gets an input stream to MP3 data for the returned information from a request
- *
- * @param synthText Text you want to be synthesized into MP3 data
- * @return Returns an input stream of the MP3 data that is returned from Google
- * @throws IOException Throws exception if it can not complete the request
- */
+ @Override
public InputStream getMP3Data(String synthText) throws IOException{
String languageCode = this.languageCode;//Ensures retention of language settings if set to auto
@@ -105,7 +99,7 @@ public InputStream getMP3Data(String synthText) throws IOException{
//Throw an error message here eventually
}
}
-
+
if(synthText.length()>100){
List fragments = parseString(synthText);//parses String if too long
String tmp = getLanguage();
@@ -119,7 +113,7 @@ public InputStream getMP3Data(String synthText) throws IOException{
StringBuilder sb = new StringBuilder();
sb.append(GOOGLE_SYNTHESISER_URL); //The base URL prefixed by the query parameter.
- sb.append("?tl=");
+ sb.append("?tl=");
sb.append(languageCode); //The query parameter to specify the language code.
sb.append("&q=");
sb.append(encoded); //We encode the String using URL Encoder
@@ -128,145 +122,16 @@ public InputStream getMP3Data(String synthText) throws IOException{
sb.append(synthText.length()); //We need some String length now.
sb.append("&client=tw-ob"); //Once again, a weird parameter.
//Client=t no longer works as it requires a token, but client=tw-ob seems to work just fine.
-
+
URL url = new URL(sb.toString());
// Open New URL connection channel.
URLConnection urlConn = url.openConnection(); //Open connection
-
+
//Adding header for user agent is required
urlConn.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) "
+ "Gecko/20100101 Firefox/4.0");
return urlConn.getInputStream();
}
-
- /**
- * Gets an InputStream to MP3Data for the returned information from a request
- * @param synthText List of Strings you want to be synthesized into MP3 data
- * @return Returns an input stream of all the MP3 data that is returned from Google
- * @throws IOException Throws exception if it cannot complete the request
- */
- public InputStream getMP3Data(List synthText) throws IOException{
- //Uses an executor service pool for concurrency. Limit to 1000 threads max.
- ExecutorService pool = Executors.newFixedThreadPool(1000);
- //Stores the Future (Data that will be returned in the future)
- Set> set = new LinkedHashSet>(synthText.size());
- for(String part: synthText){ //Iterates through the list
- Callable callable = new MP3DataFetcher(part);//Creates Callable
- Future future = pool.submit(callable);//Begins to run Callable
- set.add(future);//Adds the response that will be returned to a set.
- }
- List inputStreams = new ArrayList(set.size());
- for(Future future: set){
- try {
- inputStreams.add(future.get());//Gets the returned data from the future.
- } catch (ExecutionException e) {//Thrown if the MP3DataFetcher encountered an error.
- Throwable ex = e.getCause();
- if(ex instanceof IOException){
- throw (IOException)ex;//Downcasts and rethrows it.
- }
- } catch (InterruptedException e){//Will probably never be called, but just in case...
- Thread.currentThread().interrupt();//Interrupts the thread since something went wrong.
- }
- }
- return new SequenceInputStream(Collections.enumeration(inputStreams));//Sequences the stream.
- }
-
- /**
- * Separates a string into smaller parts so that Google will not reject the request.
- * @param input The string you want to separate
- * @return A List of the String fragments from your input..
- */
- private List parseString(String input){
- return parseString (input, new ArrayList());
- }
-
- /**
- * Separates a string into smaller parts so that Google will not reject the request.
- * @param input The string you want to break up into smaller parts
- * @param fragments List that you want to add stuff too.
- * If you don't have a List already constructed "new ArrayList()" works well.
- * @return A list of the fragments of the original String
- */
- private List parseString(String input, List fragments){
- if(input.length()<=100){//Base Case
- fragments.add(input);
- return fragments;
- }
- else{
- int lastWord = findLastWord(input);//Checks if a space exists
- if(lastWord<=0){
- fragments.add(input.substring(0,100));//In case you sent gibberish to Google.
- return parseString(input.substring(100), fragments);
- }else{
- fragments.add(input.substring(0,lastWord));
- //Otherwise, adds the last word to the list for recursion.
- return parseString(input.substring(lastWord), fragments);
- }
- }
- }
-
- /**
- * Finds the last word in your String (before the index of 99) by searching for spaces and ending punctuation.
- * Will preferably parse on punctuation to alleviate mid-sentence pausing
- * @param input The String you want to search through.
- * @return The index of where the last word of the string ends before the index of 99.
- */
- private int findLastWord(String input){
- if(input.length()<100)
- return input.length();
- int space = -1;
- for(int i = 99; i>0; i--){
- char tmp = input.charAt(i);
- if(isEndingPunctuation(tmp)){
- return i+1;
- }
- if(space==-1 && tmp == ' '){
- space = i;
- }
- }
- if(space>0){
- return space;
- }
- return -1;
- }
-
- /**
- * Checks if char is an ending character
- * Ending punctuation for all languages according to Wikipedia (Except for Sanskrit non-unicode)
- * @param The char you want check
- * @return True if it is, false if not.
- */
- private boolean isEndingPunctuation(char input){
- return input == '.' || input == '!' || input == '?' || input == ';' || input == ':' || input == '|';
- }
-
- /**
- * Automatically determines the language of the original text
- * @param text represents the text you want to check the language of
- * @return the languageCode in ISO-639
- * @throws IOException if it cannot complete the request
- */
- public String detectLanguage(String text) throws IOException{
- return GoogleTranslate.detectLanguage(text);
- }
-
- /**
- * This class is a callable.
- * A callable is like a runnable except that it can return data and throw exceptions.
- * Useful when using futures. Dramatically improves the speed of execution.
- * @author Aaron Gokaslan (Skylion)
- */
- private class MP3DataFetcher implements Callable{
- private String synthText;
-
- public MP3DataFetcher(String synthText){
- this.synthText = synthText;
- }
-
- public InputStream call() throws IOException{
- return getMP3Data(synthText);
- }
- }
}
diff --git a/src/main/java/com/darkprograms/speech/synthesiser/SynthesiserV2.java b/src/main/java/com/darkprograms/speech/synthesiser/SynthesiserV2.java
index 23344d7..5ca08aa 100644
--- a/src/main/java/com/darkprograms/speech/synthesiser/SynthesiserV2.java
+++ b/src/main/java/com/darkprograms/speech/synthesiser/SynthesiserV2.java
@@ -26,7 +26,7 @@
* See the constructor for instructions regarding the API_Key.
* @author Skylion (Aaron Gokaslan)
*/
-public class SynthesiserV2 {
+public class SynthesiserV2 extends BaseSynthsiser {
private static final String GOOGLE_SYNTHESISER_URL = "https://www.google.com/speech-api/v2/synthesize?enc=mpeg" +
"&client=chromium";
@@ -113,13 +113,7 @@ public void setSpeed(double speed) {
this.speed = speed;
}
- /**
- * Gets an input stream to MP3 data for the returned information from a request
- *
- * @param synthText Text you want to be synthesized into MP3 data
- * @return Returns an input stream of the MP3 data that is returned from Google
- * @throws IOException Throws exception if it can not complete the request
- */
+ @Override
public InputStream getMP3Data(String synthText) throws IOException{
String languageCode = this.languageCode;//Ensures retention of language settings if set to auto
@@ -171,133 +165,4 @@ public InputStream getMP3Data(String synthText) throws IOException{
return urlConn.getInputStream();
}
-
- /**
- * Gets an InputStream to MP3Data for the returned information from a request
- * @param synthText List of Strings you want to be synthesized into MP3 data
- * @return Returns an input stream of all the MP3 data that is returned from Google
- * @throws IOException Throws exception if it cannot complete the request
- */
- public InputStream getMP3Data(List synthText) throws IOException{
- //Uses an executor service pool for concurrency. Limit to 1000 threads max.
- ExecutorService pool = Executors.newFixedThreadPool(1000);
- //Stores the Future (Data that will be returned in the future)
- Set> set = new LinkedHashSet>(synthText.size());
- for(String part: synthText){ //Iterates through the list
- Callable callable = new MP3DataFetcher(part);//Creates Callable
- Future future = pool.submit(callable);//Begins to run Callable
- set.add(future);//Adds the response that will be returned to a set.
- }
- List inputStreams = new ArrayList(set.size());
- for(Future future: set){
- try {
- inputStreams.add(future.get());//Gets the returned data from the future.
- } catch (ExecutionException e) {//Thrown if the MP3DataFetcher encountered an error.
- Throwable ex = e.getCause();
- if(ex instanceof IOException){
- throw (IOException)ex;//Downcasts and rethrows it.
- }
- } catch (InterruptedException e){//Will probably never be called, but just in case...
- Thread.currentThread().interrupt();//Interrupts the thread since something went wrong.
- }
- }
- return new SequenceInputStream(Collections.enumeration(inputStreams));//Sequences the stream.
- }
-
- /**
- * Separates a string into smaller parts so that Google will not reject the request.
- * @param input The string you want to separate
- * @return A List of the String fragments from your input..
- */
- private List parseString(String input){
- return parseString (input, new ArrayList());
- }
-
- /**
- * Separates a string into smaller parts so that Google will not reject the request.
- * @param input The string you want to break up into smaller parts
- * @param fragments List that you want to add stuff too.
- * If you don't have a List already constructed "new ArrayList()" works well.
- * @return A list of the fragments of the original String
- */
- private List parseString(String input, List fragments){
- if(input.length()<=100){//Base Case
- fragments.add(input);
- return fragments;
- }
- else{
- int lastWord = findLastWord(input);//Checks if a space exists
- if(lastWord<=0){
- fragments.add(input.substring(0,100));//In case you sent gibberish to Google.
- return parseString(input.substring(100), fragments);
- }else{
- fragments.add(input.substring(0,lastWord));//Otherwise, adds the last word to the list for recursion.
- return parseString(input.substring(lastWord), fragments);
- }
- }
- }
-
- /**
- * Finds the last word in your String (before the index of 99) by searching for spaces and ending punctuation.
- * Will preferably parse on punctuation to alleviate mid-sentence pausing
- * @param input The String you want to search through.
- * @return The index of where the last word of the string ends before the index of 99.
- */
- private int findLastWord(String input){
- if(input.length()<100)
- return input.length();
- int space = -1;
- for(int i = 99; i>0; i--){
- char tmp = input.charAt(i);
- if(isEndingPunctuation(tmp)){
- return i+1;
- }
- if(space==-1 && tmp == ' '){
- space = i;
- }
- }
- if(space>0){
- return space;
- }
- return -1;
- }
-
- /**
- * Checks if char is an ending character
- * Ending punctuation for all languages according to Wikipedia (Except for Sanskrit non-unicode)
- * @param The char you want check
- * @return True if it is, false if not.
- */
- private boolean isEndingPunctuation(char input){
- return input == '.' || input == '!' || input == '?' || input == ';' || input == ':' || input == '|';
- }
-
- /**
- * Automatically determines the language of the original text
- * @param text represents the text you want to check the language of
- * @return the languageCode in ISO-639
- * @throws IOException if it cannot complete the request
- */
- public String detectLanguage(String text) throws IOException{
- return GoogleTranslate.detectLanguage(text);
- }
-
- /**
- * This class is a callable.
- * A callable is like a runnable except that it can return data and throw exceptions.
- * Useful when using futures. Dramatically improves the speed of execution.
- * @author Aaron Gokaslan (Skylion)
- */
- private class MP3DataFetcher implements Callable{
- private String synthText;
-
- public MP3DataFetcher(String synthText){
- this.synthText = synthText;
- }
-
- public InputStream call() throws IOException{
- return getMP3Data(synthText);
- }
- }
-
}
diff --git a/src/main/java/com/darkprograms/speech/translator/GoogleTranslate.java b/src/main/java/com/darkprograms/speech/translator/GoogleTranslate.java
index 9755265..c2514c9 100644
--- a/src/main/java/com/darkprograms/speech/translator/GoogleTranslate.java
+++ b/src/main/java/com/darkprograms/speech/translator/GoogleTranslate.java
@@ -54,13 +54,13 @@ private static String generateURL(String sourceLanguage, String targetLanguage,
String encoded = URLEncoder.encode(text, "UTF-8"); //Encode
StringBuilder sb = new StringBuilder();
sb.append(GOOGLE_TRANSLATE_URL);
- sb.append("?client=t"); //The client parameter
+ sb.append("?client=webapp"); //The client parameter
sb.append("&hl=en"); //The language of the UI?
sb.append("&sl="); //Source language
sb.append(sourceLanguage);
sb.append("&tl="); //Target language
sb.append(targetLanguage);
- sb.append("&text=");
+ sb.append("&q=");
sb.append(encoded);
sb.append("&multires=1");//Necessary but unknown parameters
sb.append("&otf=0");
@@ -68,7 +68,7 @@ private static String generateURL(String sourceLanguage, String targetLanguage,
sb.append("&trs=1");
sb.append("&ssel=0");
sb.append("&tsel=0");
- sb.append("&sc=1");
+ sb.append("&kc=1");
sb.append("&dt=t");//This parameters requests the translated text back.
//Other dt parameters request additional information such as pronunciation, and so on.
//TODO Modify API so that the user may request this additional information.
@@ -212,16 +212,11 @@ private static boolean containsLettersOnly(String text){
//TODO Possibly refactor code as utility class
/**
- * This function generates the b parameter for translation acting as the seed for the hashing algorithm.
+ * This function generates the int array for translation acting as the seed for the hashing algorithm.
*/
- private static int generateB() {
- Date start = new Date(0L); //Unix Epoch
- Date now = new Date();
-
- long diff = now.getTime() - start.getTime();
- long hours = diff / (60 * 60 * 1000) % 24;
- long days = diff / (24 * 60 * 60 * 1000);
- return (int) (hours + days * 24);
+ private static int[] TKK() {
+ int[] tkk = { 0x6337E, 0x217A58DC + 0x5AF91132};
+ return tkk;
}
/**
@@ -255,7 +250,8 @@ private static int RL(int a, String b) {//I am not entirely sure what this magic
* @return The generated token as a string.
*/
private static String generateToken(String text) {
- int b = generateB();
+ int tkk[] = TKK();
+ int b = tkk[0];
int e = 0;
int f = 0;
List d = new ArrayList();
@@ -287,6 +283,7 @@ private static String generateToken(String text) {
a_i = RL(a_i, "+-a^+6");
}
a_i = RL(a_i, "+-3^+b+-f");
+ a_i ^= tkk[1];
long a_l;
if (0 > a_i) {
a_l = 0x80000000l + (a_i & 0x7FFFFFFF);
diff --git a/src/main/java/com/darkprograms/speech/util/ChunkedOutputStream.java b/src/main/java/com/darkprograms/speech/util/ChunkedOutputStream.java
index 89af9ef..d5bbf3a 100644
--- a/src/main/java/com/darkprograms/speech/util/ChunkedOutputStream.java
+++ b/src/main/java/com/darkprograms/speech/util/ChunkedOutputStream.java
@@ -62,6 +62,12 @@
public class ChunkedOutputStream extends BufferedOutputStream
{
+ private static final byte[] crlf = { 13, 10 };
+ private byte[] lenBytes = new byte[20]; // big enough for any number in hex
+ private List footerNames = new ArrayList();
+ private List footerValues = new ArrayList();
+
+
/// Make a ChunkedOutputStream with a default buffer size.
// @param out the underlying output stream
public ChunkedOutputStream( OutputStream out )
@@ -69,6 +75,7 @@ public ChunkedOutputStream( OutputStream out )
super( out );
}
+
/// Make a ChunkedOutputStream with a specified buffer size.
// @param out the underlying output stream
// @param size the buffer size
@@ -77,7 +84,6 @@ public ChunkedOutputStream( OutputStream out, int size )
super( out, size );
}
-
/// Flush the stream. This will write any buffered output
// bytes as a chunk.
// @exception IOException if an I/O error occurred
@@ -91,15 +97,12 @@ public synchronized void flush() throws IOException
}
- private Vector footerNames = new Vector();
- private Vector footerValues = new Vector();
-
/// Set a footer. Footers are much like HTTP headers, except that
// they come at the end of the data instead of at the beginning.
public void setFooter( String name, String value )
{
- footerNames.addElement( name );
- footerValues.addElement( value );
+ footerNames.add( name );
+ footerValues.add( value );
}
@@ -116,8 +119,8 @@ public void done() throws IOException
// Send footers.
for ( int i = 0; i < footerNames.size(); ++i )
{
- String name = (String) footerNames.elementAt( i );
- String value = (String) footerValues.elementAt( i );
+ String name = footerNames.get( i );
+ String value = footerValues.get( i );
pout.println( name + ": " + value );
}
}
@@ -162,10 +165,6 @@ public synchronized void write( byte b[], int off, int len ) throws IOException
writeBuf( b, off, len );
}
-
- private static final byte[] crlf = { 13, 10 };
- private byte[] lenBytes = new byte[20]; // big enough for any number in hex
-
/// The only routine that actually writes to the output stream.
// This is where chunking semantics are implemented.
// @exception IOException if an I/O error occurred
diff --git a/src/main/java/com/darkprograms/speech/util/Complex.java b/src/main/java/com/darkprograms/speech/util/Complex.java
index 5177eaf..7bf3931 100644
--- a/src/main/java/com/darkprograms/speech/util/Complex.java
+++ b/src/main/java/com/darkprograms/speech/util/Complex.java
@@ -114,7 +114,10 @@ public double getMagnitude(){
}
public boolean equals(Complex other){
- return (re==other.re) && (im==other.im);
+ if(other != null) {
+ return (re == other.re) && (im == other.im);
+ }
+ return false;
}
}
diff --git a/src/main/java/com/darkprograms/speech/util/FFT.java b/src/main/java/com/darkprograms/speech/util/FFT.java
index 5ceb479..e983818 100644
--- a/src/main/java/com/darkprograms/speech/util/FFT.java
+++ b/src/main/java/com/darkprograms/speech/util/FFT.java
@@ -28,6 +28,8 @@
public class FFT {
+ private FFT() {}
+
// compute the FFT of x[], assuming its length is a power of 2
public static Complex[] fft(Complex[] x) {
int N = x.length;