From 0d929b3949afb4c6979cc9f2e0038375f6172650 Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Tue, 12 Jan 2016 16:59:32 -0500 Subject: [PATCH 01/14] [text-to-speech] Add a WaveUtils class to fix the .wav header #81 --- .../text_to_speech/v1/util/WaveUtils.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java diff --git a/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java b/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java new file mode 100644 index 00000000000..116ae813a0a --- /dev/null +++ b/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java @@ -0,0 +1,68 @@ +package com.ibm.watson.developer_cloud.text_to_speech.v1.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import com.ibm.watson.developer_cloud.text_to_speech.v1.TextToSpeech; + + +/** + * Utility class to write the data size header in wave(.wav) files synthesized with the + * {@link TextToSpeech} service + */ +public class WaveUtils { + + /** The WAVE header byte size. (value is 44) */ + private static final int WAVE_HEADER_BYTE_SIZE = 44; + + /** + * Adds the data size to the header(bytes 4-8) of the WAVE(.wav) input stream.
+ * It needs to be read in order to calculate the size. + * + * @param is the input stream + * @return A new input stream that includes the data header in the header + * @throws IOException Signals that an I/O exception has occurred. + */ + public static InputStream addDataSizeToInputStream(InputStream is) throws IOException { + byte[] audioBytes = toByteArray(is); + int filesize = audioBytes.length - 8; + audioBytes[4] = (byte) (filesize); + audioBytes[5] = (byte) (filesize >>> 8); + audioBytes[6] = (byte) (filesize >>> 16); + audioBytes[7] = (byte) (filesize >>> 24); + + int datasize = filesize + 8 - WAVE_HEADER_BYTE_SIZE; + audioBytes[40] = (byte) (datasize); + audioBytes[41] = (byte) (datasize >>> 8); + audioBytes[42] = (byte) (datasize >>> 16); + audioBytes[43] = (byte) (datasize >>> 24); + + + return new ByteArrayInputStream(audioBytes); + } + + /** + * Converts an {@link InputStream} to byte array + * + * @param is the input stream + * @return the byte array + * @throws IOException If the first byte cannot be read for any reason other than end of file, or + * if the input stream has been closed, or if some other I/O error occurs. + */ + public static byte[] toByteArray(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int nRead; + byte[] data = new byte[16384]; // 4 kb + + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + + buffer.flush(); + return buffer.toByteArray(); + } + +} From 520a7794ee4448a70a7429056101e076071d329b Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Tue, 12 Jan 2016 23:06:43 -0500 Subject: [PATCH 02/14] [text-to-speech] write data size in input streams #81 --- .../text_to_speech/v1/util/WaveUtils.java | 40 +++++++++----- .../text_to_speech/v1/TextToSpeechIT.java | 55 ++++++++++++------- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java b/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java index 116ae813a0a..626bcc65069 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java +++ b/src/main/java/com/ibm/watson/developer_cloud/text_to_speech/v1/util/WaveUtils.java @@ -13,36 +13,46 @@ * {@link TextToSpeech} service */ public class WaveUtils { + /** The WAVE meta-data header size. (value is 8) */ + private static final int WAVE_HEADER_SIZE = 8; - /** The WAVE header byte size. (value is 44) */ - private static final int WAVE_HEADER_BYTE_SIZE = 44; + /** The WAVE meta-data size position. (value is 4) */ + private static final int WAVE_SIZE_POS = 4; + + /** The WAVE meta-data position in bytes. (value is 74) */ + private static final int WAVE_METADATA_POS = 74; /** - * Adds the data size to the header(bytes 4-8) of the WAVE(.wav) input stream.
+ * Re-writes the data size in the header(bytes 4-8) of the WAVE(.wav) input stream.
* It needs to be read in order to calculate the size. * * @param is the input stream * @return A new input stream that includes the data header in the header * @throws IOException Signals that an I/O exception has occurred. */ - public static InputStream addDataSizeToInputStream(InputStream is) throws IOException { + public static InputStream reWriteWaveHeader(InputStream is) throws IOException { byte[] audioBytes = toByteArray(is); - int filesize = audioBytes.length - 8; - audioBytes[4] = (byte) (filesize); - audioBytes[5] = (byte) (filesize >>> 8); - audioBytes[6] = (byte) (filesize >>> 16); - audioBytes[7] = (byte) (filesize >>> 24); - - int datasize = filesize + 8 - WAVE_HEADER_BYTE_SIZE; - audioBytes[40] = (byte) (datasize); - audioBytes[41] = (byte) (datasize >>> 8); - audioBytes[42] = (byte) (datasize >>> 16); - audioBytes[43] = (byte) (datasize >>> 24); + int filesize = audioBytes.length - WAVE_HEADER_SIZE; + writeInt(filesize, audioBytes, WAVE_SIZE_POS); + writeInt(filesize - WAVE_HEADER_SIZE, audioBytes, WAVE_METADATA_POS); return new ByteArrayInputStream(audioBytes); } + /** + * Writes an number into an array using 4 bytes + * + * @param value the number to write + * @param array the byte array + * @param offset the offset + */ + private static void writeInt(int value, byte[] array, int offset) { + for (int i = 0; i < 4; i++) { + array[offset + i] = (byte) (value >>> (8 * i)); + } + } + /** * Converts an {@link InputStream} to byte array * diff --git a/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java b/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java index 2e3a6be187b..e916bb787f8 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java +++ b/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java @@ -36,7 +36,9 @@ import java.io.OutputStream; import java.util.List; -import org.apache.commons.io.IOUtils; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -44,6 +46,7 @@ import com.ibm.watson.developer_cloud.WatsonServiceTest; import com.ibm.watson.developer_cloud.http.HttpMediaType; import com.ibm.watson.developer_cloud.text_to_speech.v1.model.Voice; +import com.ibm.watson.developer_cloud.text_to_speech.v1.util.WaveUtils; /** * The Class TextToSpeechIntegrationTest. @@ -67,29 +70,25 @@ public void setUp() throws Exception { service.setEndPoint(prop.getProperty("text_to_speech.url")); } - /** - * Synthesize. - * - * @param text the text - * @param audio the audio - */ - private void synthesize(String text, File audio) { - final InputStream is = service.synthesize(text, Voice.EN_LISA, HttpMediaType.AUDIO_WAV); - Assert.assertNotNull(is); + private void writeInputStreamToFile(InputStream inputStream, File audio) { OutputStream outStream = null; try { outStream = new FileOutputStream(audio); - final byte[] buffer = new byte[8 * 1024]; + byte[] buffer = new byte[8 * 1024]; int bytesRead; - while ((bytesRead = is.read(buffer)) != -1) { + while ((bytesRead = inputStream.read(buffer)) != -1) { outStream.write(buffer, 0, bytesRead); } - } catch (final Exception e) { + } catch (Exception e) { fail(); } finally { - IOUtils.closeQuietly(is); - IOUtils.closeQuietly(outStream); + try { + inputStream.close(); + outStream.close(); + } catch (Exception e) { + fail(); + } } } @@ -98,21 +97,37 @@ private void synthesize(String text, File audio) { */ @Test public void testGetVoices() { - final List voices = service.getVoices(); + List voices = service.getVoices(); Assert.assertNotNull(voices); Assert.assertTrue(!voices.isEmpty()); } /** - * Test synthesize. + * Synthesize text and write it to a temporary file * * @throws IOException Signals that an I/O exception has occurred. */ @Test public void testSynthesize() throws IOException { - final String text = "This is an integration test"; - final File audio = File.createTempFile("tts-audio", "wav"); + String text = "This is an integration test"; + InputStream result = service.synthesize(text, Voice.EN_LISA, HttpMediaType.AUDIO_WAV); + writeInputStreamToFile(result, File.createTempFile("tts-audio", "wav")); + } - synthesize(text, audio); + /** + * Test the fix wave header not having the size due to be streamed. + * + * @throws IOException Signals that an I/O exception has occurred. + * @throws UnsupportedAudioFileException the unsupported audio file exception + */ + @Test + public void testSynthesizeAndFixHeader() throws IOException, UnsupportedAudioFileException { + String text = "one two three four five"; + InputStream result = service.synthesize(text, Voice.EN_LISA, HttpMediaType.AUDIO_WAV); + Assert.assertNotNull(result); + result = WaveUtils.reWriteWaveHeader(result); + File tempFile = File.createTempFile("output", ".wav"); + writeInputStreamToFile(result, tempFile); + Assert.assertNotNull(AudioSystem.getAudioFileFormat(tempFile)); } } From 41de597ba25bc959e561254fba0e9b6bde755dcb Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Wed, 13 Jan 2016 09:51:49 -0500 Subject: [PATCH 03/14] [text-to-speech] Added test for the fix to write the wave header #81 --- .../developer_cloud/WatsonServiceTest.java | 33 ++++++++++++++++++ .../text_to_speech/v1/TextToSpeechIT.java | 24 ------------- .../text_to_speech/v1/TextToSpeechTest.java | 24 +++++++++++++ src/test/resources/text_to_speech/numbers.wav | Bin 0 -> 158758 bytes 4 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 src/test/resources/text_to_speech/numbers.wav diff --git a/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java b/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java index eb3a8771f0c..6321d553849 100755 --- a/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java +++ b/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java @@ -13,12 +13,17 @@ */ package com.ibm.watson.developer_cloud; +import static org.junit.Assert.fail; + import java.io.BufferedReader; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -182,6 +187,34 @@ private void setupLogging() { root.setLevel(ch.qos.logback.classic.Level.OFF); } + /** + * Write input stream to file. + * + * @param inputStream the input stream + * @param audio the audio + */ + public static void writeInputStreamToFile(InputStream inputStream, File audio) { + OutputStream outStream = null; + try { + outStream = new FileOutputStream(audio); + + byte[] buffer = new byte[8 * 1024]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outStream.write(buffer, 0, bytesRead); + } + } catch (Exception e) { + fail(); + } finally { + try { + inputStream.close(); + outStream.close(); + } catch (Exception e) { + fail(); + } + } + } + /** * Loads fixture. * diff --git a/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java b/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java index e916bb787f8..7b2861ecbb2 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java +++ b/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechIT.java @@ -27,13 +27,9 @@ * the License. */ -import static org.junit.Assert.fail; - import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.util.List; import javax.sound.sampled.AudioSystem; @@ -70,27 +66,7 @@ public void setUp() throws Exception { service.setEndPoint(prop.getProperty("text_to_speech.url")); } - private void writeInputStreamToFile(InputStream inputStream, File audio) { - OutputStream outStream = null; - try { - outStream = new FileOutputStream(audio); - byte[] buffer = new byte[8 * 1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outStream.write(buffer, 0, bytesRead); - } - } catch (Exception e) { - fail(); - } finally { - try { - inputStream.close(); - outStream.close(); - } catch (Exception e) { - fail(); - } - } - } /** * Test get voices. diff --git a/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechTest.java b/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechTest.java index cd036046609..00fd7b933b0 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechTest.java +++ b/src/test/java/com/ibm/watson/developer_cloud/text_to_speech/v1/TextToSpeechTest.java @@ -18,6 +18,7 @@ import io.netty.handler.codec.http.HttpHeaders; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -28,6 +29,9 @@ import java.util.List; import java.util.Map; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + import org.junit.Assert; import org.junit.Before; import org.junit.FixMethodOrder; @@ -40,6 +44,7 @@ import com.ibm.watson.developer_cloud.WatsonServiceUnitTest; import com.ibm.watson.developer_cloud.http.HttpMediaType; import com.ibm.watson.developer_cloud.text_to_speech.v1.model.Voice; +import com.ibm.watson.developer_cloud.text_to_speech.v1.util.WaveUtils; import com.ibm.watson.developer_cloud.util.GsonSingleton; /** @@ -213,4 +218,23 @@ public void testWithVoiceAsWav() { } } + + + /** + * Test the fix wave header not having the size due to be streamed. + * + * @throws IOException Signals that an I/O exception has occurred. + * @throws UnsupportedAudioFileException the unsupported audio file exception + */ + @Test + public void testSynthesizeAndFixHeader() throws IOException, UnsupportedAudioFileException { + File audio = new File("src/test/resources/text_to_speech/numbers.wav"); + InputStream stream = new FileInputStream(audio); + Assert.assertNotNull(stream); + stream = WaveUtils.reWriteWaveHeader(stream); + File tempFile = File.createTempFile("output", ".wav"); + writeInputStreamToFile(stream, tempFile); + Assert.assertNotNull(AudioSystem.getAudioFileFormat(tempFile)); + } + } diff --git a/src/test/resources/text_to_speech/numbers.wav b/src/test/resources/text_to_speech/numbers.wav new file mode 100644 index 0000000000000000000000000000000000000000..d03169a650cfd2494eb3c4ed3408d541538ddde4 GIT binary patch literal 158758 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 ?w6Iol-r|m>%&NCVrm|YuNBLZuE9xU$ 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
  • 7SHFEF`xF~+h#@uGz7 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 Date: Thu, 14 Jan 2016 23:19:36 -0500 Subject: [PATCH 04/14] [document-conversion] fix example --- ...DocumentConversionCustomConfigExample.java | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/examples/java/com/ibm/watson/developer_cloud/document_conversion/v1/DocumentConversionCustomConfigExample.java b/examples/java/com/ibm/watson/developer_cloud/document_conversion/v1/DocumentConversionCustomConfigExample.java index aa3e7bb4052..1f9ec66f382 100644 --- a/examples/java/com/ibm/watson/developer_cloud/document_conversion/v1/DocumentConversionCustomConfigExample.java +++ b/examples/java/com/ibm/watson/developer_cloud/document_conversion/v1/DocumentConversionCustomConfigExample.java @@ -1,11 +1,11 @@ /** * Copyright 2015 IBM Corp. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -13,72 +13,75 @@ */ package com.ibm.watson.developer_cloud.document_conversion.v1; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.ibm.watson.developer_cloud.document_conversion.v1.model.Answers; import com.ibm.watson.developer_cloud.http.HttpMediaType; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; - public class DocumentConversionCustomConfigExample { - public static void main(String[] args) { - final String versionDate = "2015-12-14"; - DocumentConversion service = new DocumentConversion(versionDate); - service.setUsernameAndPassword("", ""); - - final File html = new File("src/test/resources/document_conversion/html-with-extra-content-input.htm"); + public static void main(String[] args) { + final String versionDate = "2015-12-14"; + DocumentConversion service = new DocumentConversion(versionDate); + service.setUsernameAndPassword("", ""); - // Run a conversion with no configuration specified. The Document Conversion service will use - // its default configuration when no configuration is specified. For this example, the - // Document Conversion service will section a HTML document by h1, h2, h3, h4, h5, and h6 tags. - // Those sections will be returned as Answers - System.out.println("Convert html document to Answer Units using default configuration"); - final Answers htmlToAnswersWithDefaultConfig = - service.convertDocumentToAnswer(html, HttpMediaType.TEXT_HTML); - System.out.println(htmlToAnswersWithDefaultConfig); + final File html = + new File("src/test/resources/document_conversion/html-with-extra-content-input.htm"); - System.out.println("=================================================="); + // Run a conversion with no configuration specified. The Document Conversion service will use + // its default configuration when no configuration is specified. For this example, the + // Document Conversion service will section a HTML document by h1, h2, h3, h4, h5, and h6 tags. + // Those sections will be returned as Answers + System.out.println("Convert html document to Answer Units using default configuration"); + final Answers htmlToAnswersWithDefaultConfig = + service.convertDocumentToAnswer(html, HttpMediaType.TEXT_HTML); + System.out.println(htmlToAnswersWithDefaultConfig); - // Run a conversion with a custom configuration. The next example shows how to convert this same - // document with a custom configuration. Instead of sectioning by the default settings (h1, h2, - // h3, h4, h5, and h6), the following example shows how to section a HTML document by only the - // h1 tag. This will result in Answers that are sectioned by h1 tags. - String configAsString = "{\n" + - " \"answer_units\": {\n" + - " \"selector_tags\": [\"h1\"]\n" + - " }\n" + - "}"; - JsonParser jsonParser = new JsonParser(); - JsonObject customConfig = jsonParser.parse(configAsString).getAsJsonObject(); + System.out.println("=================================================="); - System.out.println("Convert html document to Answer Units using custom configuration"); - final Answers htmlToAnswersWithCustomConfig = - service.convertDocumentToAnswer(html, HttpMediaType.TEXT_HTML, customConfig); - System.out.println(htmlToAnswersWithCustomConfig); + // Run a conversion with a custom configuration. The next example shows how to convert this same + // document with a custom configuration. Instead of sectioning by the default settings (h1, h2, + // h3, h4, h5, and h6), the following example shows how to section a HTML document by only the + // h1 tag. This will result in Answers that are sectioned by h1 tags. + String configAsString = + "{\n" + " \"answer_units\": {\n" + " \"selector_tags\": [\"h1\"]\n" + " }\n" + + "}"; + JsonParser jsonParser = new JsonParser(); + JsonObject customConfig = jsonParser.parse(configAsString).getAsJsonObject(); - System.out.println("=================================================="); + System.out.println("Convert html document to Answer Units using custom configuration"); + final Answers htmlToAnswersWithCustomConfig = + service.convertDocumentToAnswer(html, HttpMediaType.TEXT_HTML, customConfig); + System.out.println(htmlToAnswersWithCustomConfig); - // Run a conversion with a custom configuration that is loaded from a file. This example is similar - // to the previous one above. The custom configuration from the file will section a HTML document - // by only the h2 tag. This will result in Answers that are sectioned by h2 tags. - System.out.println("Convert html document to Answer Units using custom configuration loaded from a file"); - String customConfigFilePath = "src/test/resources/document_conversion/answer_unit_config_selector_h2.json"; - JsonObject customConfigFromFile = null; - try { - customConfigFromFile = service.loadCustomConfig(new FileInputStream(customConfigFilePath)); - } catch(FileNotFoundException e ) { - e.printStackTrace(); - } + System.out.println("=================================================="); - if(customConfigFilePath == null) { - System.err.println("ERROR - Unable to load custom config from file " + customConfigFilePath); - return; - } + // Run a conversion with a custom configuration that is loaded from a file. This example is + // similar + // to the previous one above. The custom configuration from the file will section a HTML + // document + // by only the h2 tag. This will result in Answers that are sectioned by h2 tags. + System.out + .println("Convert html document to Answer Units using custom configuration loaded from a file"); + String customConfigFilePath = + "src/test/resources/document_conversion/answer_unit_config_selector_h2.json"; + JsonObject customConfigFromFile = null; + try { + customConfigFromFile = service.loadCustomConfig(new FileInputStream(customConfigFilePath)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } - final Answers htmlToAnswersWithCustomConfigFromFile = - service.convertDocumentToAnswer(html, HttpMediaType.TEXT_HTML, customConfigFromFile); - System.out.println(htmlToAnswersWithCustomConfigFromFile); + if (customConfigFromFile == null) { + System.err.println("ERROR - Unable to load custom config from file " + customConfigFilePath); + return; } + + final Answers htmlToAnswersWithCustomConfigFromFile = + service.convertDocumentToAnswer(html, HttpMediaType.TEXT_HTML, customConfigFromFile); + System.out.println(htmlToAnswersWithCustomConfigFromFile); + } } From 34d305e8a13a4c247f01f70d9da2dd7103d07f1a Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Thu, 14 Jan 2016 23:22:57 -0500 Subject: [PATCH 05/14] [speech-to-text] WebSockets support for speech to text --- pom.xml | 12 +- .../developer_cloud/http/HttpHeaders.java | 2 + .../speech_to_text/v1/RecognizeDelegate.java | 49 ++++ .../speech_to_text/v1/RecognizeOptions.java | 157 +++++++++++-- .../speech_to_text/v1/SpeechToText.java | 13 ++ .../speech_to_text/v1/WebSocketClient.java | 214 ++++++++++++++++++ .../developer_cloud/WatsonServiceTest.java | 12 +- .../speech_to_text/v1/SpeechToTextIT.java | 86 +++++++ 8 files changed, 526 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java create mode 100644 src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java diff --git a/pom.xml b/pom.xml index cb47b7bb97a..98ec9ba2a0c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 com.ibm.watson.developer_cloud 2.5.1-SNAPSHOT @@ -20,7 +21,7 @@ com.squareup.okhttp okhttp - 2.7.0 + 2.7.2 com.google.code.gson @@ -35,9 +36,14 @@ junit junit - 4.11 + 4.12 test + + com.neovisionaries + nv-websocket-client + 1.19 + org.mock-server diff --git a/src/main/java/com/ibm/watson/developer_cloud/http/HttpHeaders.java b/src/main/java/com/ibm/watson/developer_cloud/http/HttpHeaders.java index a3d819a0004..63db918e4e2 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/http/HttpHeaders.java +++ b/src/main/java/com/ibm/watson/developer_cloud/http/HttpHeaders.java @@ -179,4 +179,6 @@ public interface HttpHeaders { */ public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + /** The Authorization token header. */ + public static final String X_WATSON_AUTHORIZATION_TOKEN = "X-Watson-Authorization-Token"; } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java new file mode 100644 index 00000000000..6d243715ba3 --- /dev/null +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java @@ -0,0 +1,49 @@ +/** + * Copyright 2015 IBM Corp. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.ibm.watson.developer_cloud.speech_to_text.v1; + +import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; + + + +/** + * The Interface RecognizeDelegate. + */ +public interface RecognizeDelegate { + + /** + * On message. + * + * @param speechResults the speech results + * @param fin if results are final + */ + public void onMessage(SpeechResults speechResults, boolean fin); + + /** + * On connected. + */ + public void onConnected(); + + /** + * On error. + * + * @param e the e + */ + public void onError(Exception e); + + /** + * On disconnected. + */ + public void onDisconnected(); +} diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java index 75c78ff963f..4e1102b4869 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java @@ -13,6 +13,9 @@ */ package com.ibm.watson.developer_cloud.speech_to_text.v1; +import java.util.List; + +import com.google.gson.annotations.SerializedName; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechSession; @@ -22,15 +25,27 @@ */ public class RecognizeOptions { + @SerializedName("content-type") + private String contentType; private Boolean continuous; private Integer inactivityTimeout; + + @SerializedName("interim_results") + private Boolean interimResults; + private List keywords; + + @SerializedName("keywords_threshold") + private Double keywordsThreshold; private Integer maxAlternatives; private String model; private String sessionId; - private Boolean timestamps; - private Boolean wordConfidence; + @SerializedName("word_alternatives_threshold") + private Double wordAlternativesThreshold; + + @SerializedName("word_confidence") + private Boolean wordConfidence; /** * If true, multiple final results that represent multiple consecutive phrases separated by pauses @@ -44,6 +59,15 @@ public RecognizeOptions continuous(Boolean continuous) { return this; } + /** + * Gets the content type. + * + * @return the contentType + */ + public String getContentType() { + return contentType; + } + /** * Gets the continuous. * @@ -62,6 +86,33 @@ public Integer getInactivityTimeout() { return inactivityTimeout; } + /** + * Gets the interim results. + * + * @return the interimResults + */ + public Boolean getInterimResults() { + return interimResults; + } + + /** + * Gets the keywords. + * + * @return the keywords + */ + public List getKeywords() { + return keywords; + } + + /** + * Gets the keywords threshold. + * + * @return the keywordsThreshold + */ + public Double getKeywordsThreshold() { + return keywordsThreshold; + } + /** * Gets the max alternatives. * @@ -98,7 +149,14 @@ public Boolean getTimestamps() { return timestamps; } - + /** + * Gets the word alternatives threshold. + * + * @return the wordAlternativesThreshold + */ + public Double getWordAlternativesThreshold() { + return wordAlternativesThreshold; + } /** * Gets the word confidence. @@ -121,7 +179,50 @@ public RecognizeOptions inactivityTimeout(Integer inactivityTimeout) { } /** - * Maximum number of alternative transcripts returned + * If true, the service sends interim results for the transcription. Otherwise, the recognition + * ends after first "end of speech" is detected. The default is false.. + * + * @param interimResults the interim results + * @return the recognize options + */ + public RecognizeOptions interimResults(Boolean interimResults) { + this.interimResults = interimResults; + return this; + } + + /** + * Specifies an array of keyword strings to be matched in the input audio. By default, the service + * does no keyword spotting. + * + * + * @param keywords the keywords + * @return the recognize options + */ + public RecognizeOptions keywords(List keywords) { + this.keywords = keywords; + return this; + } + + + + /** + * Specifies a minimum level of confidence that the service must have to report a matching keyword + * in the input audio. Specify a probability value between 0 and 1 inclusive. A match must have at + * least the specified confidence to be returned. Omit the parameter or specify a value of null + * (the default) to spot no keywords. If you specify a valid threshold, you must also specify at + * least one keyword. + * + * + * @param keywordsThreshold the keywords threshold + * @return the recognize options + */ + public RecognizeOptions keywordsThreshold(Double keywordsThreshold) { + this.keywordsThreshold = keywordsThreshold; + return this; + } + + /** + * Maximum number of alternative transcripts returned. * * @param maxAlternatives the max alternatives * @return the recognize options @@ -132,7 +233,7 @@ public RecognizeOptions maxAlternatives(Integer maxAlternatives) { } /** - * Sets the model name used for the recognition + * Sets the model name used for the recognition. * * @param model the model * @return the recognize options @@ -142,6 +243,17 @@ public RecognizeOptions model(String model) { return this; } + /** + * Sets the session id. + * + * @param session the {@link SpeechSession} + * @return the recognize options + */ + public RecognizeOptions session(SpeechSession session) { + this.sessionId = session.getSessionId(); + return this; + } + /** * Sets session id. * @@ -154,29 +266,46 @@ public RecognizeOptions sessionId(String sessionId) { } /** - * Sets the session id. + * If true, time alignment for each word is returned. * - * @param session the {@link SpeechSession} + * @param timestamps the timestamps * @return the recognize options */ - public RecognizeOptions session(SpeechSession session) { - this.sessionId = session.getSessionId(); + public RecognizeOptions timestamps(Boolean timestamps) { + this.timestamps = timestamps; return this; } /** - * If true, time alignment for each word is returned + * Specifies a minimum level of confidence that the service must have to report a hypothesis for a + * word from the input audio. Specify a probability value between 0 and 1 inclusive. A hypothesis + * must have at least the specified confidence to be returned as a word alternative. Omit the + * parameter or specify a value of null (the default) to return no word alternatives. * - * @param timestamps the timestamps + * + * + * @param wordAlternativesThreshold the wordAalternatives threshold * @return the recognize options */ - public RecognizeOptions timestamps(Boolean timestamps) { - this.timestamps = timestamps; + public RecognizeOptions wordAlternativesThreshold(Double wordAlternativesThreshold) { + this.wordAlternativesThreshold = wordAlternativesThreshold; return this; } /** - * If true, confidence measure per word is returned if available + * Content type. + * + * @param contentType the content type + * @return the recognize options + */ + public RecognizeOptions contentType(String contentType) { + this.contentType = contentType; + return this; + } + + + /** + * If true, confidence measure per word is returned if available. * * @param wordConfidence the word confidence * @return the recognize options diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java index daabfda4a51..88802a002c0 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java @@ -14,6 +14,7 @@ package com.ibm.watson.developer_cloud.speech_to_text.v1; import java.io.File; +import java.io.InputStream; import java.util.List; import com.google.gson.JsonObject; @@ -262,4 +263,16 @@ public SpeechResults recognize(File audio, String contentType, RecognizeOptions requestBuilder.withBody(RequestBody.create(MediaType.parse(contentType), audio)); return executeRequest(requestBuilder.build(), SpeechResults.class); } + + /** + * Recognizes using WebSockets. + * + * @param audio the audio + * @param options the options + * @param delegate the delegate + */ + public void recognizeWS(InputStream audio, RecognizeOptions options, RecognizeDelegate delegate) { + WebSocketClient webSocket = new WebSocketClient(); + webSocket.recognize(audio, options, delegate); + } } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java new file mode 100644 index 00000000000..897d86e1686 --- /dev/null +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java @@ -0,0 +1,214 @@ +package com.ibm.watson.developer_cloud.speech_to_text.v1; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.java_websocket.util.Base64; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.ibm.watson.developer_cloud.http.HttpHeaders; +import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; +import com.ibm.watson.developer_cloud.util.GsonSingleton; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketAdapter; +import com.neovisionaries.ws.client.WebSocketException; +import com.neovisionaries.ws.client.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketFrame; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + + +public class WebSocketClient { + private static final String START = "start"; + private static final String STOP = "stop"; + private static final String ACTION = "action"; + private static final String SERVER = + "wss://stream.watsonplatform.net/speech-to-text/api/v1/recognize"; + private static final String RESULT_INDEX = "result_index"; + private static final String ERROR = "error"; + private static final int TEN_SECONDS = 10000; + + /** + * The WebSocket listener + */ + public class WebSocketListener extends WebSocketAdapter { + + private RecognizeDelegate delegate; + + /** + * Instantiates a new web socket client. + * + * @param delegate the delegate + */ + public WebSocketListener(RecognizeDelegate delegate) { + super(); + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onTextMessage(com.neovisionaries.ws.client. + * WebSocket, java.lang.String) + */ + // A text message arrived from the server. + public void onTextMessage(WebSocket websocket, String message) { + JsonObject json = new JsonParser().parse(message).getAsJsonObject(); + if (json.has(ERROR)) { + delegate.onError(new RuntimeException("Error parsing the message: " + message)); + } else if (json.has(RESULT_INDEX)) { + SpeechResults transcript = GsonSingleton.getGson().fromJson(message, SpeechResults.class); + boolean fin = + transcript.getResults() != null && !transcript.getResults().isEmpty() + && transcript.getResults().get(transcript.getResultIndex()).isFinal(); + + delegate.onMessage(transcript, fin); + + // if final is true + if (fin) + websocket.disconnect(); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onConnected(com.neovisionaries.ws.client.WebSocket + * , java.util.Map) + */ + @Override + public void onConnected(WebSocket websocket, Map> headers) + throws Exception { + delegate.onConnected(); + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onDisconnected(com.neovisionaries.ws.client + * .WebSocket, com.neovisionaries.ws.client.WebSocketFrame, + * com.neovisionaries.ws.client.WebSocketFrame, boolean) + */ + @Override + public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, + WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { + delegate.onDisconnected(); + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onError(com.neovisionaries.ws.client.WebSocket, + * com.neovisionaries.ws.client.WebSocketException) + */ + @Override + public void onError(WebSocket websocket, WebSocketException cause) throws Exception { + delegate.onError(cause); + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#handleCallbackError(com.neovisionaries.ws.client + * .WebSocket, java.lang.Throwable) + */ + @Override + public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception { + cause.printStackTrace(); + } + + } + + private static final String AUTHORIZATION_ENDPOINT = + "https://stream.watsonplatform.net/authorization/api"; + private static final String HTTP_ENDPOINT = + "https://stream.watsonplatform.net/speech-to-text/api"; + private static final String USERNAME = "USERNAME"; + private static final String PASSWORD = "PASSWORD"; + private static final int FOUR_KB = 4096; + + /** + * Gets the token using the Authorization Service + * + * @return the token + * @throws RuntimeException if the token can't be created + */ + private static String getToken() { + String url = AUTHORIZATION_ENDPOINT + "/v1/token?url=" + HTTP_ENDPOINT; + String auth = USERNAME + ":" + PASSWORD; + String apiKey = new String(Base64.encodeBytes(auth.getBytes())); + + Request request = + new Request.Builder().header(HttpHeaders.AUTHORIZATION, "Basic " + apiKey).url(url).build(); + + OkHttpClient client = new OkHttpClient(); + Response response; + try { + response = client.newCall(request).execute(); + + if (!response.isSuccessful()) + throw new RuntimeException("Error getting the token: " + response.code() + " " + + response.message()); + + return response.body().string(); + } catch (IOException e) { + throw new RuntimeException("Error getting the token", e); + } + } + + public void recognize(InputStream stream, RecognizeOptions options, RecognizeDelegate delegate) { + WebSocketListener listener = new WebSocketListener(delegate); + + try { + + // 1. Connect to the web socket + WebSocket ws = new WebSocketFactory().setConnectionTimeout(TEN_SECONDS).createSocket(SERVER); + ws.addHeader(HttpHeaders.X_WATSON_AUTHORIZATION_TOKEN, getToken()); + ws.addListener(listener).connect(); + + // 2. Send start message + JsonObject startMessage = + new JsonParser().parse(GsonSingleton.getGson().toJson(options)).getAsJsonObject(); + startMessage.addProperty(ACTION, START); + System.out.println("sending:" + startMessage); + ws.sendText(startMessage.toString()); + + // 3. Send binary data + byte[] buffer = new byte[FOUR_KB]; + int read; + while ((read = stream.read(buffer)) > 0) { + if (read == FOUR_KB) + ws.sendBinary(buffer); + else + ws.sendBinary(Arrays.copyOfRange(buffer, 0, read)); + + Thread.sleep(5); + } + + // 4. Send stop message + JsonObject stopMessage = new JsonObject(); + stopMessage.addProperty(ACTION, STOP); + ws.sendText(stopMessage.toString()); + + } catch (WebSocketException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java b/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java index 6321d553849..99d04f05122 100755 --- a/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java +++ b/src/test/java/com/ibm/watson/developer_cloud/WatsonServiceTest.java @@ -40,6 +40,9 @@ public abstract class WatsonServiceTest { private static final Logger log = Logger.getLogger(WatsonServiceTest.class.getName()); + /** + * Instantiates a new watson service test. + */ public WatsonServiceTest() { if (prop == null) loadProperties(); @@ -133,8 +136,8 @@ public String getExistingProperty(String property) { } /** - * Gets the existing property if exists, otherwise it returns the defaultValue - * + * Gets the existing property if exists, otherwise it returns the defaultValue. + * * @param property the property * @param defaultValue the default value * @return the existing property @@ -230,6 +233,11 @@ public static T loadFixture(String filename, Class returnType) return GsonSingleton.getGson().fromJson(jsonString, returnType); } + /** + * Sets the up. + * + * @throws Exception the exception + */ public void setUp() throws Exception {} } diff --git a/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java b/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java index e74d55d5d7b..3a697098cf7 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java +++ b/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java @@ -17,8 +17,13 @@ import static org.junit.Assert.assertTrue; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -29,11 +34,19 @@ import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechSession; +/** + * The Class SpeechToTextIT. + */ public class SpeechToTextIT extends WatsonServiceTest { private static final String EN_BROADBAND16K = "en-US_BroadbandModel"; private SpeechToText service; + /* + * (non-Javadoc) + * + * @see com.ibm.watson.developer_cloud.WatsonServiceTest#setUp() + */ @Override @Before public void setUp() throws Exception { @@ -44,6 +57,9 @@ public void setUp() throws Exception { service.setEndPoint(getValidProperty("speech_to_text.url")); } + /** + * Test create session. + */ @Test public void testCreateSession() { final SpeechSession session = service.createSession(); @@ -55,6 +71,9 @@ public void testCreateSession() { } } + /** + * Test create session speech model. + */ @Test public void testCreateSessionSpeechModel() { final SpeechSession session = service.createSession(SpeechModel.EN_BROADBAND16K); @@ -66,6 +85,9 @@ public void testCreateSessionSpeechModel() { } } + /** + * Test create session string. + */ @Test public void testCreateSessionString() { final SpeechSession session = service.createSession(EN_BROADBAND16K); @@ -77,6 +99,9 @@ public void testCreateSessionString() { } } + /** + * Test get model. + */ @Test public void testGetModel() { final SpeechModel model = service.getModel(EN_BROADBAND16K); @@ -85,6 +110,9 @@ public void testGetModel() { assertNotNull(model.getRate()); } + /** + * Test get models. + */ @Test public void testGetModels() { final List models = service.getModels(); @@ -92,6 +120,9 @@ public void testGetModels() { assertTrue(!models.isEmpty()); } + /** + * Test get recognize status. + */ @Test public void testGetRecognizeStatus() { final SpeechSession session = service.createSession(SpeechModel.EN_BROADBAND16K); @@ -105,6 +136,9 @@ public void testGetRecognizeStatus() { } } + /** + * Test recognize file string. + */ @Test public void testRecognizeFileString() { final File audio = new File("src/test/resources/speech_to_text/sample1.wav"); @@ -112,6 +146,58 @@ public void testRecognizeFileString() { assertNotNull(results.getResults().get(0).getAlternatives().get(0).getTranscript()); } + private SpeechResults asyncResults; + private CountDownLatch lock = new CountDownLatch(1); + + /** + * Test recognize webSocket + * + * @throws FileNotFoundException the file not found exception + * @throws InterruptedException + */ + @Test + public void testRecognizeWebSocket() throws FileNotFoundException, InterruptedException { + File audio = new File("src/test/resources/speech_to_text/sample1.wav"); + + RecognizeOptions options = + new RecognizeOptions().continuous(true).interimResults(true) + .contentType(HttpMediaType.AUDIO_WAV); + + service.recognizeWS(new FileInputStream(audio), options, new RecognizeDelegate() { + + @Override + public void onMessage(SpeechResults speechResults, boolean fin) { + System.out.println(speechResults); + if (fin) { + asyncResults = speechResults; + lock.countDown(); + } + } + + @Override + public void onError(Exception e) { + Assert.assertTrue(false); + + } + + @Override + public void onDisconnected() { + Assert.assertTrue(false); + + } + + @Override + public void onConnected() {} + + }); + + lock.await(10000, TimeUnit.MILLISECONDS); + assertNotNull(asyncResults); + } + + /** + * Test recognize file string recognize options. + */ @Test public void testRecognizeFileStringRecognizeOptions() { final File audio = new File("src/test/resources/speech_to_text/sample1.wav"); From 61a9909e894258d036024369b35e833867265d52 Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 22:40:25 -0500 Subject: [PATCH 06/14] [speech-to-text] Added web socket example and code review for web sockets --- .../v1/SpeechToTextExample.java | 9 +- .../service/ConflictException.java | 39 +++ .../service/WatsonService.java | 22 +- .../speech_to_text/v1/RecognizeOptions.java | 25 +- .../speech_to_text/v1/SpeechToText.java | 137 +++++++-- .../speech_to_text/v1/WebSocketClient.java | 214 -------------- .../v1/model/SessionStatus.java | 2 +- .../v1/model/SpeechAlternative.java | 29 +- .../speech_to_text/v1/model/SpeechModel.java | 5 +- .../v1/model/SpeechModelSet.java | 2 +- .../v1/model/SpeechResults.java | 9 + .../v1/model/SpeechTimestamp.java | 20 +- .../v1/model/SpeechWordConfidence.java | 16 +- .../v1/websocket/BaseRecognizeDelegate.java | 56 ++++ .../v1/{ => websocket}/RecognizeDelegate.java | 20 +- .../WebSocketSpeechToTextClient.java | 268 ++++++++++++++++++ .../speech_to_text/v1/SpeechToTextIT.java | 102 +++---- .../speech_to_text/v1/SpeechToTextTest.java | 4 +- 18 files changed, 626 insertions(+), 353 deletions(-) create mode 100644 src/main/java/com/ibm/watson/developer_cloud/service/ConflictException.java delete mode 100644 src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java create mode 100644 src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/BaseRecognizeDelegate.java rename src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/{ => websocket}/RecognizeDelegate.java (59%) create mode 100644 src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java diff --git a/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextExample.java b/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextExample.java index b6d523ab94f..687524dd241 100644 --- a/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextExample.java +++ b/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextExample.java @@ -1,11 +1,11 @@ /** * Copyright 2015 IBM Corp. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -15,7 +15,6 @@ import java.io.File; -import com.ibm.watson.developer_cloud.http.HttpMediaType; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; @@ -30,7 +29,7 @@ public static void main(String[] args) { service.setUsernameAndPassword("", ""); File audio = new File("src/test/resources/speech_to_text/sample1.wav"); - SpeechResults transcript = service.recognize(audio, HttpMediaType.AUDIO_WAV); + SpeechResults transcript = service.recognize(audio); System.out.println(transcript); } diff --git a/src/main/java/com/ibm/watson/developer_cloud/service/ConflictException.java b/src/main/java/com/ibm/watson/developer_cloud/service/ConflictException.java new file mode 100644 index 00000000000..6c489308fd6 --- /dev/null +++ b/src/main/java/com/ibm/watson/developer_cloud/service/ConflictException.java @@ -0,0 +1,39 @@ +/** + * Copyright 2015 IBM Corp. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.ibm.watson.developer_cloud.service; + +import com.ibm.watson.developer_cloud.http.HttpStatus; +import com.squareup.okhttp.Response; + +/** + * 409 Conflict (HTTP/1.1 - RFC 2616) + */ +public class ConflictException extends ServiceResponseException { + + /** + * The Constant serialVersionUID. + */ + private static final long serialVersionUID = 1L; + + /** + * Instantiates a new Forbidden Exception. + * + * @param message the error message + * @param response the HTTP response + */ + public ConflictException(String message, Response response) { + super(HttpStatus.CONFLICT, message, response); + } + +} diff --git a/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java b/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java index 2c45680b560..ab7517b823f 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java +++ b/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java @@ -26,12 +26,14 @@ import com.google.gson.JsonSyntaxException; import com.ibm.watson.developer_cloud.http.HttpHeaders; import com.ibm.watson.developer_cloud.http.HttpStatus; +import com.ibm.watson.developer_cloud.http.RequestBuilder; import com.ibm.watson.developer_cloud.service.model.GenericModel; import com.ibm.watson.developer_cloud.util.BluemixUtils; import com.ibm.watson.developer_cloud.util.RequestUtil; import com.ibm.watson.developer_cloud.util.ResponseUtil; import com.squareup.okhttp.Credentials; import com.squareup.okhttp.Headers; +import com.squareup.okhttp.HttpUrl; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Request.Builder; @@ -69,9 +71,9 @@ public WatsonService(String name) { /** - * Configure HTTP client. + * Configures the HTTP client. * - * @return the okhttp client + * @return the HTTP client */ protected OkHttpClient configureHttpClient() { final OkHttpClient client = new OkHttpClient(); @@ -147,6 +149,8 @@ protected Response execute(Request request) { case HttpStatus.NOT_ACCEPTABLE: // HTTP 406 throw new ForbiddenException(error != null ? error : "Forbidden: Service refuse the request", response); + case HttpStatus.CONFLICT: // HTTP 409 + throw new ConflictException(error != null ? error : "", response); case HttpStatus.REQUEST_TOO_LONG: // HTTP 413 throw new RequestTooLargeException(error != null ? error : "Request too large: The request entity is larger than the server is able to process", @@ -216,6 +220,20 @@ public String getEndPoint() { return endPoint; } + /** + * Gets an authorization token that can be use to authorize API calls. + * + * + * @return the token + */ + public String getToken() { + HttpUrl url = + HttpUrl.parse(getEndPoint()).newBuilder().setPathSegment(0, "authorization").build(); + Request request = RequestBuilder.get(url + "/v1/token").withQuery("url", getEndPoint()).build(); + Response response = execute(request); + return ResponseUtil.getJsonObject(response).get("token").getAsString(); + } + /** * Gets the error message from a JSON response * diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java index 4e1102b4869..b656911b15c 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeOptions.java @@ -13,18 +13,19 @@ */ package com.ibm.watson.developer_cloud.speech_to_text.v1; -import java.util.List; +import org.apache.commons.lang3.Validate; import com.google.gson.annotations.SerializedName; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechSession; +import com.squareup.okhttp.MediaType; /** - * Recognize Options when using the - * {@link SpeechToText#recognize(java.io.File, String, RecognizeOptions)} method. + * Parameters to be use during a recognize call in the {@link SpeechToText} service. */ public class RecognizeOptions { + @SerializedName("content-type") private String contentType; private Boolean continuous; @@ -32,7 +33,7 @@ public class RecognizeOptions { @SerializedName("interim_results") private Boolean interimResults; - private List keywords; + private String[] keywords; @SerializedName("keywords_threshold") private Double keywordsThreshold; @@ -100,7 +101,7 @@ public Boolean getInterimResults() { * * @return the keywords */ - public List getKeywords() { + public String[] getKeywords() { return keywords; } @@ -180,7 +181,7 @@ public RecognizeOptions inactivityTimeout(Integer inactivityTimeout) { /** * If true, the service sends interim results for the transcription. Otherwise, the recognition - * ends after first "end of speech" is detected. The default is false.. + * ends after first "end of speech" is detected. The default is false. * * @param interimResults the interim results * @return the recognize options @@ -198,7 +199,7 @@ public RecognizeOptions interimResults(Boolean interimResults) { * @param keywords the keywords * @return the recognize options */ - public RecognizeOptions keywords(List keywords) { + public RecognizeOptions keywords(String[] keywords) { this.keywords = keywords; return this; } @@ -293,12 +294,20 @@ public RecognizeOptions wordAlternativesThreshold(Double wordAlternativesThresho } /** - * Content type. + * The format of the audio data specified as one of the following values:
    + *
      + *
    • audio/flac for Free Lossless Audio Codec (FLAC)
    • + *
    • audio/l16 for Linear 16-bit Pulse-Code Modulation (PCM)
    • + *
    • audio/wav for Waveform Audio File Format (WAV)
    • + *
    • audio/ogg;codecs=opus for Ogg format files that use the opus codec
    • + *
    * * @param contentType the content type * @return the recognize options */ public RecognizeOptions contentType(String contentType) { + Validate.isTrue(MediaType.parse(contentType) != null, + "contentType is not a valid mime audio format. Valid formats start with 'audio/'"); this.contentType = contentType; return this; } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java index 88802a002c0..3a8ee7ff511 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java @@ -27,6 +27,8 @@ import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechSession; import com.ibm.watson.developer_cloud.speech_to_text.v1.util.MediaTypeUtils; +import com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.RecognizeDelegate; +import com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.WebSocketSpeechToTextClient; import com.ibm.watson.developer_cloud.util.GsonSingleton; import com.ibm.watson.developer_cloud.util.ResponseUtil; import com.ibm.watson.developer_cloud.util.Validate; @@ -61,6 +63,9 @@ public class SpeechToText extends WatsonService { private final static String URL = "https://stream.watsonplatform.net/speech-to-text/api"; private static final String WORD_CONFIDENCE = "word_confidence"; private static final String SESSION = "session"; + private static final String KEYWORDS_THRESHOLD = "keywords_threshold"; + private static final String WORD_ALTERNATIVES_THRESHOLD = "word_alternatives_threshold"; + private static final String KEYWORDS = "keywords"; /** * Instantiates a new speech to text. @@ -97,6 +102,15 @@ private void buildRecognizeRequest(RequestBuilder requestBuilder, RecognizeOptio if (options.getModel() != null) requestBuilder.withQuery(MODEL, options.getModel()); + + if (options.getKeywordsThreshold() != null) + requestBuilder.withQuery(KEYWORDS_THRESHOLD, options.getKeywordsThreshold()); + + if (options.getKeywords() != null && options.getKeywords().length > 0) + requestBuilder.withQuery(KEYWORDS, GsonSingleton.getGson().toJson(options.getKeywords())); + + if (options.getWordAlternativesThreshold() != null) + requestBuilder.withQuery(WORD_ALTERNATIVES_THRESHOLD, options.getWordAlternativesThreshold()); } /** @@ -210,24 +224,76 @@ public SessionStatus getRecognizeStatus(final SpeechSession session) { /** * Recognizes an audio file and returns {@link SpeechResults}. It will try to recognize the audio - * format based on the file extension. + * format based on the file extension.
    + * Here is an example of how to recognize an audio file: + * + *
    +   * SpeechToText service = new SpeechToText();
    +   * service.setUsernameAndPassword("USERNAME", "PASSWORD");
    +   * service.setEndPoint("SERVICE_URL");
    +   * 
    +   * SpeechResults results = service.recognize(new File("sample1.wav"));
    +   * System.out.println(results);
    +   * 
    * * @param audio the audio file * @return the {@link SpeechResults} + * @throws IllegalArgumentException if the file extension doesn't match a valid audio type */ public SpeechResults recognize(File audio) { + return recognize(audio, (RecognizeOptions) null); + } + + /** + * Recognizes an audio file and returns {@link SpeechResults}.
    + *
    + * Here is an example of how to recognize an audio file: + * + *
    +   * SpeechToText service = new SpeechToText();
    +   * service.setUsernameAndPassword("USERNAME", "PASSWORD");
    +   * service.setEndPoint("SERVICE_URL");
    +   * 
    +   * RecognizeOptions options = new RecognizeOptions().maxAlternatives(3).continuous(true);
    +   * 
    +   * SpeechResults results = service.recognize(new File("sample1.wav"), options);
    +   * System.out.println(results);
    +   * 
    + * + * @param audio the audio + * @param options the options + * @return the speech results + */ + public SpeechResults recognize(File audio, RecognizeOptions options) { + Validate.isTrue(audio != null && audio.exists(), "audio file is null or does not exist"); + + final double fileSize = audio.length() / Math.pow(1024, 2); + Validate.isTrue(fileSize < 100.0, "The audio file is greater than 100MB."); + String contentType = MediaTypeUtils.getMediaTypeFromFile(audio); - Validate.notNull(contentType, "Audio format cannot be recognized"); - return recognize(audio, contentType, null); + if (options != null && options.getContentType() != null) + contentType = options.getContentType(); + Validate.notNull(contentType, "The audio format cannot be recognized"); + + String path = PATH_RECOGNIZE; + if (options != null && (options.getSessionId() != null && !options.getSessionId().isEmpty())) + path = String.format(PATH_SESSION_RECOGNIZE, options.getSessionId()); + + final RequestBuilder requestBuilder = RequestBuilder.post(path); + buildRecognizeRequest(requestBuilder, options); + requestBuilder.withBody(RequestBody.create(MediaType.parse(contentType), audio)); + return executeRequest(requestBuilder.build(), SpeechResults.class); } /** * Recognizes an audio file and returns {@link SpeechResults}. * * @param audio the audio file - * @param contentType the media type of the audio. If you use the audio/l16 MIME type, specify the - * rate and channels. + * @param contentType the media type of the audio. * @return the {@link SpeechResults} + * @deprecated Deprecated in 2.5.0
    + * Use {@link SpeechToText#recognize(File, RecognizeOptions)} + * */ public SpeechResults recognize(File audio, String contentType) { return recognize(audio, contentType, null); @@ -239,40 +305,55 @@ public SpeechResults recognize(File audio, String contentType) { * @param audio the audio file * @param contentType the media type of the audio. If you use the audio/l16 MIME type, specify the * rate and channels. + * * @param options the {@link RecognizeOptions} * @return the {@link SpeechResults} + * @deprecated Deprecated in 2.5.0
    + * Use {@link SpeechToText#recognize(File, RecognizeOptions)} */ public SpeechResults recognize(File audio, String contentType, RecognizeOptions options) { - Validate.isTrue(audio != null && audio.exists(), "audio file is null or does not exist"); - Validate.isTrue(audio != null && audio.exists(), "audio file is null or does not exist"); - - Validate.isTrue((audio.length() / (1024 * 1024)) < 100.0, - "The audio file is greater than 100MB."); - - Validate.isTrue(MediaType.parse(contentType) != null, - "contentType is not a valid mime audio format. Valid formats start with 'audio/'"); - - String path = PATH_RECOGNIZE; - if (options != null && (options.getSessionId() != null && !options.getSessionId().isEmpty())) - path = String.format(PATH_SESSION_RECOGNIZE, options.getSessionId()); - - final RequestBuilder requestBuilder = RequestBuilder.post(path); + RecognizeOptions opt = options; + if (opt == null) + opt = new RecognizeOptions().contentType(contentType); - buildRecognizeRequest(requestBuilder, options); - - requestBuilder.withBody(RequestBody.create(MediaType.parse(contentType), audio)); - return executeRequest(requestBuilder.build(), SpeechResults.class); + return recognize(audio, opt); } /** - * Recognizes using WebSockets. + * Recognizes an audio {@link InputStream} using WebSockets. The {@link RecognizeDelegate} + * instance will be called every time the service sends {@link SpeechResults}.
    + *
    * - * @param audio the audio - * @param options the options + * Here is an example of how to recognize an audio file using WebSockets and get interim results: + * + *
    +   * SpeechToText service = new SpeechToText();
    +   * service.setUsernameAndPassword("USERNAME", "PASSWORD");
    +   * service.setEndPoint("SERVICE_URL");
    +   * 
    +   * RecognizeOptions options = new RecognizeOptions().continuous(true).interimResults(true);
    +   * 
    +   * service.recognizeWS(new FileInputStream("sample1.wav"), options, new BaseRecognizeDelegate() {
    +   *   @Override
    +   *   public void onMessage(SpeechResults speechResults) {
    +   *     System.out.println(speechResults);
    +   *   }
    +   * });
    +   * 
    + * + * @param audio the audio input stream + * @param options the recognize options * @param delegate the delegate */ - public void recognizeWS(InputStream audio, RecognizeOptions options, RecognizeDelegate delegate) { - WebSocketClient webSocket = new WebSocketClient(); + public void recognizeUsingWebSockets(InputStream audio, RecognizeOptions options, + RecognizeDelegate delegate) { + Validate.notNull(audio, "audio cannot be null"); + Validate.notNull(options, "options cannot be null"); + Validate.notNull(options.getContentType(), "options.contentType cannot be null"); + Validate.notNull(delegate, "delegate cannot be null"); + + String url = getEndPoint().replaceFirst("(https|http)", "wss"); + WebSocketSpeechToTextClient webSocket = new WebSocketSpeechToTextClient(url + PATH_RECOGNIZE, getToken()); webSocket.recognize(audio, options, delegate); } } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java deleted file mode 100644 index 897d86e1686..00000000000 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/WebSocketClient.java +++ /dev/null @@ -1,214 +0,0 @@ -package com.ibm.watson.developer_cloud.speech_to_text.v1; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.java_websocket.util.Base64; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.ibm.watson.developer_cloud.http.HttpHeaders; -import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; -import com.ibm.watson.developer_cloud.util.GsonSingleton; -import com.neovisionaries.ws.client.WebSocket; -import com.neovisionaries.ws.client.WebSocketAdapter; -import com.neovisionaries.ws.client.WebSocketException; -import com.neovisionaries.ws.client.WebSocketFactory; -import com.neovisionaries.ws.client.WebSocketFrame; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; - - -public class WebSocketClient { - private static final String START = "start"; - private static final String STOP = "stop"; - private static final String ACTION = "action"; - private static final String SERVER = - "wss://stream.watsonplatform.net/speech-to-text/api/v1/recognize"; - private static final String RESULT_INDEX = "result_index"; - private static final String ERROR = "error"; - private static final int TEN_SECONDS = 10000; - - /** - * The WebSocket listener - */ - public class WebSocketListener extends WebSocketAdapter { - - private RecognizeDelegate delegate; - - /** - * Instantiates a new web socket client. - * - * @param delegate the delegate - */ - public WebSocketListener(RecognizeDelegate delegate) { - super(); - this.delegate = delegate; - } - - /* - * (non-Javadoc) - * - * @see - * com.neovisionaries.ws.client.WebSocketAdapter#onTextMessage(com.neovisionaries.ws.client. - * WebSocket, java.lang.String) - */ - // A text message arrived from the server. - public void onTextMessage(WebSocket websocket, String message) { - JsonObject json = new JsonParser().parse(message).getAsJsonObject(); - if (json.has(ERROR)) { - delegate.onError(new RuntimeException("Error parsing the message: " + message)); - } else if (json.has(RESULT_INDEX)) { - SpeechResults transcript = GsonSingleton.getGson().fromJson(message, SpeechResults.class); - boolean fin = - transcript.getResults() != null && !transcript.getResults().isEmpty() - && transcript.getResults().get(transcript.getResultIndex()).isFinal(); - - delegate.onMessage(transcript, fin); - - // if final is true - if (fin) - websocket.disconnect(); - } - } - - /* - * (non-Javadoc) - * - * @see - * com.neovisionaries.ws.client.WebSocketAdapter#onConnected(com.neovisionaries.ws.client.WebSocket - * , java.util.Map) - */ - @Override - public void onConnected(WebSocket websocket, Map> headers) - throws Exception { - delegate.onConnected(); - } - - /* - * (non-Javadoc) - * - * @see - * com.neovisionaries.ws.client.WebSocketAdapter#onDisconnected(com.neovisionaries.ws.client - * .WebSocket, com.neovisionaries.ws.client.WebSocketFrame, - * com.neovisionaries.ws.client.WebSocketFrame, boolean) - */ - @Override - public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, - WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { - delegate.onDisconnected(); - } - - /* - * (non-Javadoc) - * - * @see - * com.neovisionaries.ws.client.WebSocketAdapter#onError(com.neovisionaries.ws.client.WebSocket, - * com.neovisionaries.ws.client.WebSocketException) - */ - @Override - public void onError(WebSocket websocket, WebSocketException cause) throws Exception { - delegate.onError(cause); - } - - /* - * (non-Javadoc) - * - * @see - * com.neovisionaries.ws.client.WebSocketAdapter#handleCallbackError(com.neovisionaries.ws.client - * .WebSocket, java.lang.Throwable) - */ - @Override - public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception { - cause.printStackTrace(); - } - - } - - private static final String AUTHORIZATION_ENDPOINT = - "https://stream.watsonplatform.net/authorization/api"; - private static final String HTTP_ENDPOINT = - "https://stream.watsonplatform.net/speech-to-text/api"; - private static final String USERNAME = "USERNAME"; - private static final String PASSWORD = "PASSWORD"; - private static final int FOUR_KB = 4096; - - /** - * Gets the token using the Authorization Service - * - * @return the token - * @throws RuntimeException if the token can't be created - */ - private static String getToken() { - String url = AUTHORIZATION_ENDPOINT + "/v1/token?url=" + HTTP_ENDPOINT; - String auth = USERNAME + ":" + PASSWORD; - String apiKey = new String(Base64.encodeBytes(auth.getBytes())); - - Request request = - new Request.Builder().header(HttpHeaders.AUTHORIZATION, "Basic " + apiKey).url(url).build(); - - OkHttpClient client = new OkHttpClient(); - Response response; - try { - response = client.newCall(request).execute(); - - if (!response.isSuccessful()) - throw new RuntimeException("Error getting the token: " + response.code() + " " - + response.message()); - - return response.body().string(); - } catch (IOException e) { - throw new RuntimeException("Error getting the token", e); - } - } - - public void recognize(InputStream stream, RecognizeOptions options, RecognizeDelegate delegate) { - WebSocketListener listener = new WebSocketListener(delegate); - - try { - - // 1. Connect to the web socket - WebSocket ws = new WebSocketFactory().setConnectionTimeout(TEN_SECONDS).createSocket(SERVER); - ws.addHeader(HttpHeaders.X_WATSON_AUTHORIZATION_TOKEN, getToken()); - ws.addListener(listener).connect(); - - // 2. Send start message - JsonObject startMessage = - new JsonParser().parse(GsonSingleton.getGson().toJson(options)).getAsJsonObject(); - startMessage.addProperty(ACTION, START); - System.out.println("sending:" + startMessage); - ws.sendText(startMessage.toString()); - - // 3. Send binary data - byte[] buffer = new byte[FOUR_KB]; - int read; - while ((read = stream.read(buffer)) > 0) { - if (read == FOUR_KB) - ws.sendBinary(buffer); - else - ws.sendBinary(Arrays.copyOfRange(buffer, 0, read)); - - Thread.sleep(5); - } - - // 4. Send stop message - JsonObject stopMessage = new JsonObject(); - stopMessage.addProperty(ACTION, STOP); - ws.sendText(stopMessage.toString()); - - } catch (WebSocketException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } -} diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SessionStatus.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SessionStatus.java index f2fd3b0e49e..f49d25e6975 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SessionStatus.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SessionStatus.java @@ -18,7 +18,7 @@ import com.ibm.watson.developer_cloud.speech_to_text.v1.SpeechToText; /** - * SessionStatus Status used by {@link SpeechToText}. + * SessionStatus used by {@link SpeechToText}. */ public class SessionStatus extends GenericModel { diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechAlternative.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechAlternative.java index 1191dc762ec..13d7e60f10c 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechAlternative.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechAlternative.java @@ -1,11 +1,11 @@ /** * Copyright 2015 IBM Corp. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -20,7 +20,8 @@ import com.ibm.watson.developer_cloud.service.model.GenericModel; /** - * The Class SpeechAlternative. + * SpeechAlternative contains the transcript of the utterance along with confidence, timestamp, + * etc... */ public class SpeechAlternative extends GenericModel { @@ -39,7 +40,7 @@ public class SpeechAlternative extends GenericModel { /** * Gets the transcript. - * + * * @return The transcript */ public String getTranscript() { @@ -48,7 +49,7 @@ public String getTranscript() { /** * Sets the transcript. - * + * * @param transcript The transcript */ public void setTranscript(final String transcript) { @@ -57,7 +58,7 @@ public void setTranscript(final String transcript) { /** * Gets the confidence. - * + * * @return The confidence */ public Double getConfidence() { @@ -66,7 +67,7 @@ public Double getConfidence() { /** * Sets the confidence. - * + * * @param confidence The confidence */ public void setConfidence(final Double confidence) { @@ -75,7 +76,7 @@ public void setConfidence(final Double confidence) { /** * Gets the timestamps. - * + * * @return The timestamps */ public List getTimestamps() { @@ -84,7 +85,7 @@ public List getTimestamps() { /** * Sets the timestamps. - * + * * @param timestamps The timestamps */ public void setTimestamps(final List timestamps) { @@ -93,7 +94,7 @@ public void setTimestamps(final List timestamps) { /** * With timestamps. - * + * * @param timestamps the timestamps * @return the speech */ @@ -105,7 +106,7 @@ public SpeechAlternative withTimestamps(final List timestamps) /** * Gets the word confidences. - * + * * @return The wordConfidences */ public List getWordConfidences() { @@ -114,7 +115,7 @@ public List getWordConfidences() { /** * Sets the word confidences. - * + * * @param wordConfidences The wordConfidences */ public void setWordConfidences(final List wordConfidences) { @@ -123,7 +124,7 @@ public void setWordConfidences(final List wordConfidences) /** * With word confidences. - * + * * @param wordConfidences the wordConfidences * @return the speech */ diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModel.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModel.java index dce6a3db848..1aa887cd9a8 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModel.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModel.java @@ -17,13 +17,16 @@ import com.ibm.watson.developer_cloud.service.model.GenericModel; /** - * The Class SpeechModel. + * Speech model */ public class SpeechModel extends GenericModel { /** US English broadband model (16KHz). */ public static final SpeechModel EN_BROADBAND16K = new SpeechModel("en-US_BroadbandModel"); + /** US English narrowband model (8KHz). */ + public static final SpeechModel EN_NARROWBAND8K = new SpeechModel("en-US_NarrowbandModel"); + /** Spanish broadband model (16KHz). */ public static final SpeechModel ES_BROADBAND16K = new SpeechModel("es-ES_BroadbandModel"); diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModelSet.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModelSet.java index b15e5b4294c..1adc822d4d0 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModelSet.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechModelSet.java @@ -20,7 +20,7 @@ import com.ibm.watson.developer_cloud.service.model.GenericModel; /** - * The Class SpeechModelSet. + * Speech model set */ public class SpeechModelSet extends GenericModel { diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechResults.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechResults.java index 700446253b7..0514f902c24 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechResults.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechResults.java @@ -67,4 +67,13 @@ public void setResults(final List results) { this.results = results; } + /** + * Returns true if the results are final + * + * @return true, if the results are final + */ + public boolean isFinal() { + return (results != null && results.get(resultIndex) != null && results.get(resultIndex) + .isFinal()); + } } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechTimestamp.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechTimestamp.java index 081cc96a174..32d1ddb66f8 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechTimestamp.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechTimestamp.java @@ -1,11 +1,11 @@ /** * Copyright 2015 IBM Corp. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -19,7 +19,7 @@ import com.ibm.watson.developer_cloud.speech_to_text.v1.util.SpeechTimestampTypeAdapter; /** - * The Class SpeechTimestamp. + * Transcription timestamp */ @JsonAdapter(SpeechTimestampTypeAdapter.class) public class SpeechTimestamp extends GenericModel { @@ -35,7 +35,7 @@ public class SpeechTimestamp extends GenericModel { /** * Gets the word. - * + * * @return The word */ public String getWord() { @@ -44,7 +44,7 @@ public String getWord() { /** * Sets the word. - * + * * @param word The word */ public void setWord(final String word) { @@ -53,7 +53,7 @@ public void setWord(final String word) { /** * Gets the start time. - * + * * @return The start time */ public Double getStartTime() { @@ -62,7 +62,7 @@ public Double getStartTime() { /** * Sets the start time. - * + * * @param startTime The start time */ public void setStartTime(final Double startTime) { @@ -71,7 +71,7 @@ public void setStartTime(final Double startTime) { /** * Gets the end time. - * + * * @return The end time */ public Double getEndTime() { @@ -80,7 +80,7 @@ public Double getEndTime() { /** * Sets the end time. - * + * * @param endTime The end time */ public void setEndTime(final Double endTime) { diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechWordConfidence.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechWordConfidence.java index 66e6123bcc1..5c469c69e5c 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechWordConfidence.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/model/SpeechWordConfidence.java @@ -1,11 +1,11 @@ /** * Copyright 2015 IBM Corp. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -19,7 +19,7 @@ import com.ibm.watson.developer_cloud.speech_to_text.v1.util.SpeechWordConfidenceTypeAdapter; /** - * The Class SpeechWordConfidence. + * Transcription word confidence */ @JsonAdapter(SpeechWordConfidenceTypeAdapter.class) public class SpeechWordConfidence extends GenericModel { @@ -32,7 +32,7 @@ public class SpeechWordConfidence extends GenericModel { /** * Gets the word. - * + * * @return The word */ public String getWord() { @@ -41,7 +41,7 @@ public String getWord() { /** * Sets the word. - * + * * @param word The word */ public void setWord(final String word) { @@ -50,7 +50,7 @@ public void setWord(final String word) { /** * Gets the confidence. - * + * * @return The confidence */ public Double getConfidence() { @@ -59,7 +59,7 @@ public Double getConfidence() { /** * Sets the confidence. - * + * * @param confidence The confidence */ public void setConfidence(final Double confidence) { diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/BaseRecognizeDelegate.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/BaseRecognizeDelegate.java new file mode 100644 index 00000000000..e7ccb2fea40 --- /dev/null +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/BaseRecognizeDelegate.java @@ -0,0 +1,56 @@ +/** + * Copyright 2015 IBM Corp. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.ibm.watson.developer_cloud.speech_to_text.v1.websocket; + +import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; + +/** + * An empty implementation of {@link RecognizeDelegate} interface. + */ +public class BaseRecognizeDelegate implements RecognizeDelegate { + + /* + * (non-Javadoc) + * + * @see + * com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.RecognizeDelegate#onMessage(com. + * ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults) + */ + public void onMessage(SpeechResults speechResults) {}; + + /* + * (non-Javadoc) + * + * @see com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.RecognizeDelegate#onConnected() + */ + public void onConnected() {}; + + /* + * (non-Javadoc) + * + * @see + * com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.RecognizeDelegate#onError(java.lang + * .Exception) + */ + public void onError(Exception e) {}; + + /* + * (non-Javadoc) + * + * @see + * com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.RecognizeDelegate#onDisconnected() + */ + public void onDisconnected() {}; + +} diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/RecognizeDelegate.java similarity index 59% rename from src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java rename to src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/RecognizeDelegate.java index 6d243715ba3..081d800e5fe 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeDelegate.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/RecognizeDelegate.java @@ -11,39 +11,39 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package com.ibm.watson.developer_cloud.speech_to_text.v1; +package com.ibm.watson.developer_cloud.speech_to_text.v1.websocket; +import com.ibm.watson.developer_cloud.speech_to_text.v1.SpeechToText; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; - /** - * The Interface RecognizeDelegate. + * The recognize delegate used in the + * {@link SpeechToText#recognizeUsingWebSockets(java.io.InputStream, com.ibm.watson.developer_cloud.speech_to_text.v1.RecognizeOptions, RecognizeDelegate)} */ public interface RecognizeDelegate { /** - * On message. + * Called when a {@link SpeechResults} was received. * * @param speechResults the speech results - * @param fin if results are final */ - public void onMessage(SpeechResults speechResults, boolean fin); + public void onMessage(SpeechResults speechResults); /** - * On connected. + * Called when a WebSocket connection was made */ public void onConnected(); /** - * On error. + * Called when there is an error in the Web Socket connection * - * @param e the e + * @param e the exception */ public void onError(Exception e); /** - * On disconnected. + * Called when a WebSocket connection was closed */ public void onDisconnected(); } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java new file mode 100644 index 00000000000..2ba32df87b0 --- /dev/null +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java @@ -0,0 +1,268 @@ +package com.ibm.watson.developer_cloud.speech_to_text.v1.websocket; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.ibm.watson.developer_cloud.http.HttpHeaders; +import com.ibm.watson.developer_cloud.speech_to_text.v1.RecognizeOptions; +import com.ibm.watson.developer_cloud.speech_to_text.v1.SpeechToText; +import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; +import com.ibm.watson.developer_cloud.util.GsonSingleton; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketAdapter; +import com.neovisionaries.ws.client.WebSocketException; +import com.neovisionaries.ws.client.WebSocketExtension; +import com.neovisionaries.ws.client.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketFrame; + + +/** + * WebSocket client used by the {@link SpeechToText} to recognize audio + */ +public class WebSocketSpeechToTextClient { + private static final String MODEL = "model"; + private static final String START = "start"; + private static final String STOP = "stop"; + private static final String ACTION = "action"; + private static final String RESULTS = "results"; + private static final String ERROR = "error"; + + private static final int TEN_SECONDS = 10000; // milliseconds + + /** + * Listener that call the {@link RecognizeDelegate} when a message from the WebSocket connection + * arrives + */ + public class WebSocketListener extends WebSocketAdapter { + private RecognizeDelegate delegate; + + /** + * Instantiates a new WebSocket listener. + * + * @param delegate the delegate to notify events + */ + public WebSocketListener(RecognizeDelegate delegate) { + super(); + this.delegate = delegate; + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onTextMessage(com.neovisionaries.ws.client. + * WebSocket, java.lang.String) + */ + public void onTextMessage(WebSocket websocket, String message) { + try { + JsonObject json = new JsonParser().parse(message).getAsJsonObject(); + + if (json.has(ERROR)) { + delegate.onError(new RuntimeException(json.get(ERROR).getAsString())); + } else if (json.has(RESULTS)) { + SpeechResults transcript = GsonSingleton.getGson().fromJson(message, SpeechResults.class); + delegate.onMessage(transcript); + + // if final is true + if (transcript.isFinal()) + websocket.disconnect(); + } + } catch (JsonParseException e) { + new RuntimeException("Error parsing the incoming message: " + message); + } + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onConnected(com.neovisionaries.ws.client.WebSocket + * , java.util.Map) + */ + @Override + public void onConnected(WebSocket websocket, Map> headers) + throws Exception { + delegate.onConnected(); + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onDisconnected(com.neovisionaries.ws.client + * .WebSocket, com.neovisionaries.ws.client.WebSocketFrame, + * com.neovisionaries.ws.client.WebSocketFrame, boolean) + */ + @Override + public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, + WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception { + delegate.onDisconnected(); + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#onError(com.neovisionaries.ws.client.WebSocket, + * com.neovisionaries.ws.client.WebSocketException) + */ + @Override + public void onError(WebSocket websocket, WebSocketException cause) throws Exception { + delegate.onError(cause); + } + + /* + * (non-Javadoc) + * + * @see + * com.neovisionaries.ws.client.WebSocketAdapter#handleCallbackError(com.neovisionaries.ws.client + * .WebSocket, java.lang.Throwable) + */ + @Override + public void handleCallbackError(WebSocket websocket, Throwable cause) throws Exception { + cause.printStackTrace(); + } + + } + + private static final int FOUR_KB = 4096; + private String token; + private String webSocketUrl; + + /** + * Instantiates a new web socket speech to text client. + * + * @param webSocketUrl the web socket url. + * + *
    +   * wss://stream.watsonplatform.net/speech-to-text/api/v1/api/recognize
    +   * 
    + * @param token the authorization token + */ + public WebSocketSpeechToTextClient(String webSocketUrl, String token) { + this.token = token; + this.webSocketUrl = webSocketUrl; + } + + /** + * Creates a WebSocket connection to the Speech To Text service and sends the audio bytes from the + * input stream for recognition + * + * @param stream the stream + * @param options the options + * @param delegate the delegate + */ + public void recognize(InputStream stream, RecognizeOptions options, RecognizeDelegate delegate) { + WebSocketListener listener = new WebSocketListener(delegate); + + try { + // 1. Connect to the WebSocket + WebSocket ws = connect(options); + + // 2. Add a listener to messages coming from the WebSocket + ws.addListener(listener); + + // 3. Send start message + ws.sendText(buildStartMessage(options)); + + // 4. Send the input stream as binary data + sendInputStream(ws, stream); + + // 5. Send stop message + ws.sendText(buildStopMessage()); + + } catch (WebSocketException e) { + delegate.onError(e); + } catch (IOException e) { + delegate.onError(e); + } catch (InterruptedException e) { + delegate.onError(e); + } + } + + /** + * Builds the stop message.
    + *
    + * + * { "action": "stop" } + * + * + * @return the string + */ + private String buildStopMessage() { + JsonObject stopMessage = new JsonObject(); + stopMessage.addProperty(ACTION, STOP); + return stopMessage.toString(); + } + + /** + * Sends 4k byte arrays to the WebSocket as binary data + * + * @param ws the WebSocket + * @param stream the stream + * @throws IOException Signals that an I/O exception has occurred. + * @throws InterruptedException if any thread has interrupted the current thread. The interrupted + * status of the current thread is cleared when this exception is thrown. + */ + private void sendInputStream(WebSocket ws, InputStream stream) throws IOException, + InterruptedException { + byte[] buffer = new byte[FOUR_KB]; + int read; + while ((read = stream.read(buffer)) > 0) { + if (read == FOUR_KB) + ws.sendBinary(buffer); + else + ws.sendBinary(Arrays.copyOfRange(buffer, 0, read)); + + Thread.sleep(5); + } + + stream.close(); + } + + /** + * Builds the start message using the {@link RecognizeOptions}.
    + *
    + * + * { + * "action": "start", + * "content-type": "audio/wav" + * } + * + * + * @param options the recognize options + * @return the string + */ + private String buildStartMessage(RecognizeOptions options) { + JsonObject startMessage = new JsonParser().parse(new Gson().toJson(options)).getAsJsonObject(); + startMessage.remove(MODEL); + startMessage.addProperty(ACTION, START); + return startMessage.toString(); + } + + /** + * Creates a connects to the Speech to Text service + * + * @param options + * + * @return the WebSocket + * @throws IOException Signals that an I/O exception has occurred. + * @throws WebSocketException the WebSocket exception + */ + private WebSocket connect(RecognizeOptions options) throws IOException, WebSocketException { + String speechModel = options.getModel() != null ? "?model=" + options.getModel() : ""; + + WebSocketFactory factory = new WebSocketFactory().setConnectionTimeout(TEN_SECONDS); + WebSocket ws = factory.createSocket(webSocketUrl + speechModel); + ws.addHeader(HttpHeaders.X_WATSON_AUTHORIZATION_TOKEN, token); + ws.addExtension(WebSocketExtension.PERMESSAGE_DEFLATE).connect(); + return ws; + } +} diff --git a/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java b/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java index 3a697098cf7..151d1191240 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java +++ b/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextIT.java @@ -23,7 +23,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -33,13 +32,18 @@ import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechModel; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechSession; +import com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.BaseRecognizeDelegate; /** * The Class SpeechToTextIT. */ public class SpeechToTextIT extends WatsonServiceTest { - private static final String EN_BROADBAND16K = "en-US_BroadbandModel"; + private static String EN_BROADBAND16K = "en-US_BroadbandModel"; + private SpeechResults asyncResults; + + private CountDownLatch lock = new CountDownLatch(1); + private SpeechToText service; /* @@ -62,7 +66,7 @@ public void setUp() throws Exception { */ @Test public void testCreateSession() { - final SpeechSession session = service.createSession(); + SpeechSession session = service.createSession(); try { assertNotNull(session); assertNotNull(session.getSessionId()); @@ -76,7 +80,7 @@ public void testCreateSession() { */ @Test public void testCreateSessionSpeechModel() { - final SpeechSession session = service.createSession(SpeechModel.EN_BROADBAND16K); + SpeechSession session = service.createSession(SpeechModel.EN_BROADBAND16K); try { assertNotNull(session); assertNotNull(session.getSessionId()); @@ -90,7 +94,7 @@ public void testCreateSessionSpeechModel() { */ @Test public void testCreateSessionString() { - final SpeechSession session = service.createSession(EN_BROADBAND16K); + SpeechSession session = service.createSession(EN_BROADBAND16K); try { assertNotNull(session); assertNotNull(session.getSessionId()); @@ -104,7 +108,7 @@ public void testCreateSessionString() { */ @Test public void testGetModel() { - final SpeechModel model = service.getModel(EN_BROADBAND16K); + SpeechModel model = service.getModel(EN_BROADBAND16K); assertNotNull(model); assertNotNull(model.getName()); assertNotNull(model.getRate()); @@ -115,7 +119,7 @@ public void testGetModel() { */ @Test public void testGetModels() { - final List models = service.getModels(); + List models = service.getModels(); assertNotNull(models); assertTrue(!models.isEmpty()); } @@ -125,8 +129,8 @@ public void testGetModels() { */ @Test public void testGetRecognizeStatus() { - final SpeechSession session = service.createSession(SpeechModel.EN_BROADBAND16K); - final SessionStatus status = service.getRecognizeStatus(session); + SpeechSession session = service.createSession(SpeechModel.EN_BROADBAND16K); + SessionStatus status = service.getRecognizeStatus(session); try { assertNotNull(status); assertNotNull(status.getModel()); @@ -137,17 +141,30 @@ public void testGetRecognizeStatus() { } /** - * Test recognize file string. + * Test recognize audio file */ @Test public void testRecognizeFileString() { - final File audio = new File("src/test/resources/speech_to_text/sample1.wav"); - final SpeechResults results = service.recognize(audio); + File audio = new File("src/test/resources/speech_to_text/sample1.wav"); + SpeechResults results = service.recognize(audio); assertNotNull(results.getResults().get(0).getAlternatives().get(0).getTranscript()); } - private SpeechResults asyncResults; - private CountDownLatch lock = new CountDownLatch(1); + /** + * Test recognize file string recognize options. + */ + @Test + public void testRecognizeFileStringRecognizeOptions() { + File audio = new File("src/test/resources/speech_to_text/sample1.wav"); + String contentType = HttpMediaType.AUDIO_WAV; + RecognizeOptions options = new RecognizeOptions(); + options.continuous(true).timestamps(true).wordConfidence(true).model(EN_BROADBAND16K) + .contentType(contentType); + SpeechResults results = service.recognize(audio, options); + assertNotNull(results.getResults().get(0).getAlternatives().get(0).getTranscript()); + assertNotNull(results.getResults().get(0).getAlternatives().get(0).getTimestamps()); + assertNotNull(results.getResults().get(0).getAlternatives().get(0).getWordConfidences()); + } /** * Test recognize webSocket @@ -157,57 +174,44 @@ public void testRecognizeFileString() { */ @Test public void testRecognizeWebSocket() throws FileNotFoundException, InterruptedException { - File audio = new File("src/test/resources/speech_to_text/sample1.wav"); - - RecognizeOptions options = - new RecognizeOptions().continuous(true).interimResults(true) - .contentType(HttpMediaType.AUDIO_WAV); + RecognizeOptions options = new RecognizeOptions(); + options.continuous(true).interimResults(true); + options.inactivityTimeout(40).timestamps(true).maxAlternatives(2); + options.model(EN_BROADBAND16K).contentType(HttpMediaType.AUDIO_WAV); - service.recognizeWS(new FileInputStream(audio), options, new RecognizeDelegate() { + service.recognizeUsingWebSockets(new FileInputStream( + "src/test/resources/speech_to_text/sample1.wav"), options, new BaseRecognizeDelegate() { @Override - public void onMessage(SpeechResults speechResults, boolean fin) { - System.out.println(speechResults); - if (fin) { - asyncResults = speechResults; - lock.countDown(); - } + public void onConnected() { + System.out.println("onConnected()"); } @Override - public void onError(Exception e) { - Assert.assertTrue(false); - + public void onDisconnected() { + System.out.println("onDisconnected()"); + lock.countDown(); } @Override - public void onDisconnected() { - Assert.assertTrue(false); - + public void onError(Exception e) { + e.printStackTrace(); + lock.countDown(); } @Override - public void onConnected() {} + public void onMessage(SpeechResults speechResults) { + if (speechResults != null && speechResults.isFinal()) { + asyncResults = speechResults; + System.out.println(speechResults); + lock.countDown(); + } + } }); - lock.await(10000, TimeUnit.MILLISECONDS); + lock.await(20000, TimeUnit.MILLISECONDS); assertNotNull(asyncResults); } - /** - * Test recognize file string recognize options. - */ - @Test - public void testRecognizeFileStringRecognizeOptions() { - final File audio = new File("src/test/resources/speech_to_text/sample1.wav"); - final String contentType = HttpMediaType.AUDIO_WAV; - final RecognizeOptions options = new RecognizeOptions(); - options.continuous(true).timestamps(true).wordConfidence(true).model(EN_BROADBAND16K); - final SpeechResults results = service.recognize(audio, contentType, options); - assertNotNull(results.getResults().get(0).getAlternatives().get(0).getTranscript()); - assertNotNull(results.getResults().get(0).getAlternatives().get(0).getTimestamps()); - assertNotNull(results.getResults().get(0).getAlternatives().get(0).getWordConfidences()); - } - } diff --git a/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextTest.java b/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextTest.java index 048930c141d..54a3b47ec2c 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextTest.java +++ b/src/test/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToTextTest.java @@ -209,7 +209,7 @@ public void testRecognize() throws URISyntaxException { response().withHeaders( new Header(HttpHeaders.Names.CONTENT_TYPE, HttpMediaType.APPLICATION_JSON)).withBody( GsonSingleton.getGson().toJson(speechResults))); - final SpeechResults result = service.recognize(audio, HttpMediaType.AUDIO_WAV); + final SpeechResults result = service.recognize(audio); Assert.assertNotNull(result); Assert.assertEquals(result, speechResults); } @@ -251,7 +251,7 @@ public void testRecognizeMissingAudioFile() throws URISyntaxException { boolean didItHappen = false; try { - service.recognize(null, HttpMediaType.AUDIO_WAV); + service.recognize(null); } catch (final IllegalArgumentException e) { didItHappen = true; } From 211ac075a075d465acf35040a4ac5130ee03d5d1 Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 22:41:00 -0500 Subject: [PATCH 07/14] [speech-to-text] Added Websocket example #15 --- .../v1/RecognizeUsingWebSockets.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java diff --git a/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java b/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java new file mode 100644 index 00000000000..04513321589 --- /dev/null +++ b/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java @@ -0,0 +1,32 @@ +package com.ibm.watson.developer_cloud.speech_to_text.v1; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +import com.ibm.watson.developer_cloud.http.HttpMediaType; +import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; +import com.ibm.watson.developer_cloud.speech_to_text.v1.websocket.BaseRecognizeDelegate; + +/** + * Recognize using WebSockets a sample wav file and print the transcript into the console output. + */ +public class RecognizeUsingWebSockets { + public static void main(String[] args) throws FileNotFoundException { + SpeechToText service = new SpeechToText(); + service.setUsernameAndPassword("", ""); + + FileInputStream audio = new FileInputStream("src/test/resources/speech_to_text/sample1.wav"); + + RecognizeOptions options = + new RecognizeOptions().continuous(true).interimResults(true) + .contentType(HttpMediaType.AUDIO_WAV); + + service.recognizeUsingWebSockets(audio, options, new BaseRecognizeDelegate() { + + @Override + public void onMessage(SpeechResults speechResults) { + System.out.println(speechResults); + } + }); + } +} From f87a8495b4d014ac25dcbf334af1573573419965 Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 22:41:33 -0500 Subject: [PATCH 08/14] [concept-expansion] Removed label requirement --- .../v1/ConceptExpansionExample.java | 3 +-- .../concept_expansion/v1/ConceptExpansion.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansionExample.java b/examples/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansionExample.java index c0b51f917cc..58dc68205c7 100644 --- a/examples/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansionExample.java +++ b/examples/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansionExample.java @@ -23,9 +23,8 @@ public static void main(String[] args) { service.setUsernameAndPassword("", ""); String[] seeds = new String[] {"nyc", "dc", "london", "big cities"}; - String label = "demo"; - Job job = service.createJob(label, seeds); + Job job = service.createJob(seeds); while (service.getJobStatus(job) == Job.Status.AWAITING_WORK || service.getJobStatus(job) == Job.Status.IN_FLIGHT) { diff --git a/src/main/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansion.java b/src/main/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansion.java index 3951ed17add..757c5bb7788 100755 --- a/src/main/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansion.java +++ b/src/main/java/com/ibm/watson/developer_cloud/concept_expansion/v1/ConceptExpansion.java @@ -67,6 +67,17 @@ public ConceptExpansion() { setDataset(Dataset.MT_SAMPLES); } + /** + * Creates a {@link Job}. + * + * @param seeds List of terms to be used as seeds + * + * @return the {@link Job} + */ + public Job createJob(final String[] seeds) { + return createJob(null, seeds); + } + /** * Creates a {@link Job}. * @@ -76,7 +87,6 @@ public ConceptExpansion() { * @return the {@link Job} */ public Job createJob(final String label, final String[] seeds) { - Validate.notEmpty(label, "label cannot be null or empty"); Validate.notEmpty(seeds, "seeds cannot be null or empty"); Validate.notNull(dataset, "dataset cannot be null"); From 2f795b5ec95f3187a86d28b3e214277c5ffacc0c Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 23:04:12 -0500 Subject: [PATCH 09/14] [speech-to-text] Added CountDownLatch to show interim results when using web sockets #15 --- .../v1/RecognizeUsingWebSockets.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java b/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java index 04513321589..b33cb0fad1b 100644 --- a/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java +++ b/examples/java/com/ibm/watson/developer_cloud/speech_to_text/v1/RecognizeUsingWebSockets.java @@ -2,6 +2,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import com.ibm.watson.developer_cloud.http.HttpMediaType; import com.ibm.watson.developer_cloud.speech_to_text.v1.model.SpeechResults; @@ -11,22 +13,26 @@ * Recognize using WebSockets a sample wav file and print the transcript into the console output. */ public class RecognizeUsingWebSockets { - public static void main(String[] args) throws FileNotFoundException { + private static CountDownLatch lock = new CountDownLatch(1); + + public static void main(String[] args) throws FileNotFoundException, InterruptedException { SpeechToText service = new SpeechToText(); service.setUsernameAndPassword("", ""); FileInputStream audio = new FileInputStream("src/test/resources/speech_to_text/sample1.wav"); - RecognizeOptions options = - new RecognizeOptions().continuous(true).interimResults(true) - .contentType(HttpMediaType.AUDIO_WAV); + RecognizeOptions options = new RecognizeOptions(); + options.continuous(true).interimResults(true).contentType(HttpMediaType.AUDIO_WAV); service.recognizeUsingWebSockets(audio, options, new BaseRecognizeDelegate() { - @Override public void onMessage(SpeechResults speechResults) { System.out.println(speechResults); + if (speechResults.isFinal()) + lock.countDown(); } }); + + lock.await(20000, TimeUnit.MILLISECONDS); } } From bb11bf53c293ad7edd6df9e88995a4911ebf5d99 Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 23:04:30 -0500 Subject: [PATCH 10/14] [concept-insights] fixed create-delete corpus integration test --- .../concept_insights/v2/ConceptInsightsIT.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/ibm/watson/developer_cloud/concept_insights/v2/ConceptInsightsIT.java b/src/test/java/com/ibm/watson/developer_cloud/concept_insights/v2/ConceptInsightsIT.java index a4b1c47721a..da3cb60b8b1 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/concept_insights/v2/ConceptInsightsIT.java +++ b/src/test/java/com/ibm/watson/developer_cloud/concept_insights/v2/ConceptInsightsIT.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.junit.Assert; import org.junit.Before; @@ -316,8 +317,9 @@ public void testGetGraphs() { */ @Test public void testCreateAndDeleteCorpus() { + final String name = UUID.randomUUID().toString(); final Account account = service.getAccountsInfo().getAccounts().get(0); - Corpus corpus = new Corpus(account.getId(), "integration-test-corpus"); + Corpus corpus = new Corpus(account.getId(), name); try { service.createCorpus(corpus); corpus = service.getCorpus(corpus); From d0abc80b33f1c713b7f4d27a388b00effee2e9ce Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 23:23:48 -0500 Subject: [PATCH 11/14] updated credentials --- config.properties.enc | Bin 3808 -> 3616 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/config.properties.enc b/config.properties.enc index 3985663ea4b46ede183cdd1bee62315f41159981..fde13015d00c3623ccd70ad311bff6a3b84e1025 100644 GIT binary patch literal 3616 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 WT>hENgy>T(J@Ihxpp%4Nli3 Date: Fri, 15 Jan 2016 23:33:16 -0500 Subject: [PATCH 12/14] [ci skip] bump up version to 2.6.0 --- README.md | 8 ++++---- build.gradle | 2 +- .../ibm/watson/developer_cloud/service/WatsonService.java | 2 +- .../developer_cloud/speech_to_text/v1/SpeechToText.java | 7 ++++--- .../v1/websocket/WebSocketSpeechToTextClient.java | 2 +- .../developer_cloud/service/GenericServiceTest.java | 2 +- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 23b780231e2..a486bc920d3 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,13 @@ APIs and SDKs that use cognitive computing to solve complex problems. com.ibm.watson.developer_cloud java-sdk - 2.5.0 + 2.6.0 ``` ##### Gradle ```gradle -'com.ibm.watson.developer_cloud:java-sdk:2.5.0' +'com.ibm.watson.developer_cloud:java-sdk:2.6.0' ``` Now, you are ready to see some [examples](https://github.com/watson-developer-cloud/java-sdk/tree/master/examples/java/com/ibm/watson/developer_cloud). @@ -481,7 +481,7 @@ Gradle: ```sh $ cd java-sdk - $ gradle jar # build jar file (build/libs/watson-developer-cloud-2.5.0.jar) + $ gradle jar # build jar file (build/libs/watson-developer-cloud-2.6.0.jar) $ gradle test # run tests ``` @@ -551,4 +551,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). [apache_maven]: http://maven.apache.org/ [releases]: https://github.com/watson-developer-cloud/java-sdk/releases -[jar]: https://github.com/watson-developer-cloud/java-sdk/releases/download/java-sdk-2.5.0/java-sdk-2.5.0-jar-with-dependencies.jar +[jar]: https://github.com/watson-developer-cloud/java-sdk/releases/download/java-sdk-2.6.0/java-sdk-2.6.0-jar-with-dependencies.jar diff --git a/build.gradle b/build.gradle index 2c69f84db5d..bfa3083ae50 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ sourceCompatibility = 1.6 targetCompatibility = 1.6 group = 'com.ibm.watson.developercloud' archivesBaseName = 'watson-developer-cloud' -version = '2.5.0' +version = '2.6.0' description = 'Client library to use the IBM Watson Services and AlchemyAPI' diff --git a/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java b/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java index ab7517b823f..fcfaeaea5a2 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java +++ b/src/main/java/com/ibm/watson/developer_cloud/service/WatsonService.java @@ -285,7 +285,7 @@ public String getName() { * @return the user agent */ private final String getUserAgent() { - return "watson-developer-cloud-java-sdk-2.5.0"; + return "watson-developer-cloud-java-sdk-2.6.0"; } /** diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java index 3a8ee7ff511..4e6c314517c 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/SpeechToText.java @@ -291,7 +291,7 @@ public SpeechResults recognize(File audio, RecognizeOptions options) { * @param audio the audio file * @param contentType the media type of the audio. * @return the {@link SpeechResults} - * @deprecated Deprecated in 2.5.0
    + * @deprecated Deprecated in 2.6.0
    * Use {@link SpeechToText#recognize(File, RecognizeOptions)} * */ @@ -308,7 +308,7 @@ public SpeechResults recognize(File audio, String contentType) { * * @param options the {@link RecognizeOptions} * @return the {@link SpeechResults} - * @deprecated Deprecated in 2.5.0
    + * @deprecated Deprecated in 2.6.0
    * Use {@link SpeechToText#recognize(File, RecognizeOptions)} */ public SpeechResults recognize(File audio, String contentType, RecognizeOptions options) { @@ -353,7 +353,8 @@ public void recognizeUsingWebSockets(InputStream audio, RecognizeOptions options Validate.notNull(delegate, "delegate cannot be null"); String url = getEndPoint().replaceFirst("(https|http)", "wss"); - WebSocketSpeechToTextClient webSocket = new WebSocketSpeechToTextClient(url + PATH_RECOGNIZE, getToken()); + WebSocketSpeechToTextClient webSocket = + new WebSocketSpeechToTextClient(url + PATH_RECOGNIZE, getToken()); webSocket.recognize(audio, options, delegate); } } diff --git a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java index 2ba32df87b0..730f5b789be 100644 --- a/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java +++ b/src/main/java/com/ibm/watson/developer_cloud/speech_to_text/v1/websocket/WebSocketSpeechToTextClient.java @@ -221,7 +221,7 @@ private void sendInputStream(WebSocket ws, InputStream stream) throws IOExceptio else ws.sendBinary(Arrays.copyOfRange(buffer, 0, read)); - Thread.sleep(5); + Thread.sleep(10); } stream.close(); diff --git a/src/test/java/com/ibm/watson/developer_cloud/service/GenericServiceTest.java b/src/test/java/com/ibm/watson/developer_cloud/service/GenericServiceTest.java index 2cb89d48c52..0edf7dda7f4 100644 --- a/src/test/java/com/ibm/watson/developer_cloud/service/GenericServiceTest.java +++ b/src/test/java/com/ibm/watson/developer_cloud/service/GenericServiceTest.java @@ -170,7 +170,7 @@ public void testUserAgentIsSet() { mockAPICall(); service.getProfile(sampleText); mockServer.verify(new HttpRequest().withMethod("POST").withHeader( - new Header(HttpHeaders.USER_AGENT, "watson-developer-cloud-java-sdk-2.5.0"))); + new Header(HttpHeaders.USER_AGENT, "watson-developer-cloud-java-sdk-2.6.0"))); } @Test From 11a92f9f4f4336dfdc7b44189e8fc280686dc1ff Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 23:33:58 -0500 Subject: [PATCH 13/14] [maven-release-plugin] prepare release java-sdk-2.6.0 --- pom.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 98ec9ba2a0c..81bf1b4f1d1 100644 --- a/pom.xml +++ b/pom.xml @@ -1,9 +1,8 @@ - + 4.0.0 com.ibm.watson.developer_cloud - 2.5.1-SNAPSHOT + 2.6.0 jar java-sdk Watson Developer Cloud Java SDK @@ -76,7 +75,7 @@ SSH scm:git:git@github.com:watson-developer-cloud/java-sdk.git https://github.com/watson-developer-cloud/java-sdk - HEAD + java-sdk-2.6.0 From 11af761f8a7efca663806bed799965fba467d73f Mon Sep 17 00:00:00 2001 From: German Attanasio Ruiz Date: Fri, 15 Jan 2016 23:34:00 -0500 Subject: [PATCH 14/14] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 81bf1b4f1d1..7c04428672c 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.ibm.watson.developer_cloud - 2.6.0 + 2.6.1-SNAPSHOT jar java-sdk Watson Developer Cloud Java SDK @@ -75,7 +75,7 @@ SSH scm:git:git@github.com:watson-developer-cloud/java-sdk.git https://github.com/watson-developer-cloud/java-sdk - java-sdk-2.6.0 + HEAD