Dumped Sphinx and instead using Haven OnDemand speech API
This commit is contained in:
@@ -13,9 +13,6 @@ import java.util.prefs.*;
|
||||
import java.io.*;
|
||||
import it.sauronsoftware.jave.*;
|
||||
import com.mpatric.mp3agic.*;
|
||||
import edu.cmu.sphinx.api.*;
|
||||
import edu.cmu.sphinx.decoder.adaptation.*;
|
||||
import edu.cmu.sphinx.result.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.zip.*;
|
||||
import javax.swing.filechooser.*;
|
||||
@@ -90,30 +87,19 @@ public class AudiobookRecorder extends JFrame {
|
||||
|
||||
SourceDataLine play = null;
|
||||
|
||||
public HavenQueue havenQueue = new HavenQueue();
|
||||
|
||||
|
||||
public TargetDataLine microphone = null;
|
||||
public AudioInputStream microphoneStream = null;
|
||||
|
||||
public Configuration sphinxConfig;
|
||||
public StreamSpeechRecognizer recognizer;
|
||||
|
||||
public static AudiobookRecorder window;
|
||||
|
||||
void initSphinx() {
|
||||
sphinxConfig = new Configuration();
|
||||
|
||||
sphinxConfig.setAcousticModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us");
|
||||
sphinxConfig.setDictionaryPath("resource:/edu/cmu/sphinx/models/en-us/cmudict-en-us.dict");
|
||||
sphinxConfig.setLanguageModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us.lm.bin");
|
||||
|
||||
try {
|
||||
recognizer = new StreamSpeechRecognizer(sphinxConfig);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
void buildToolbar(Container ob) {
|
||||
toolBar = new MainToolBar(this);
|
||||
toolBar.addSeparator();
|
||||
toolBar.add(Box.createHorizontalGlue());
|
||||
toolBar.add(havenQueue);
|
||||
ob.add(toolBar, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
@@ -715,41 +701,6 @@ public class AudiobookRecorder extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
class BatchConversionThread implements Runnable {
|
||||
Chapter chapter;
|
||||
|
||||
public BatchConversionThread(Chapter c) {
|
||||
chapter = c;
|
||||
}
|
||||
public void run() {
|
||||
try {
|
||||
Configuration sphinxConfig = new Configuration();
|
||||
|
||||
sphinxConfig.setAcousticModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us");
|
||||
sphinxConfig.setDictionaryPath("resource:/edu/cmu/sphinx/models/en-us/cmudict-en-us.dict");
|
||||
sphinxConfig.setLanguageModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us.lm.bin");
|
||||
|
||||
sphinxConfig.setSampleRate((int)(book.getAudioFormat().getSampleRate() / 4f));
|
||||
|
||||
StreamSpeechRecognizer recognizer;
|
||||
|
||||
recognizer = new StreamSpeechRecognizer(sphinxConfig);
|
||||
|
||||
|
||||
for (Enumeration s = chapter.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (!snt.isLocked()) {
|
||||
if (snt.getId().equals(snt.getText())) {
|
||||
snt.doRecognition(recognizer);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void treePopup(MouseEvent e) {
|
||||
|
||||
@@ -771,7 +722,7 @@ public class AudiobookRecorder extends JFrame {
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Sentence s = (Sentence)o.getObject();
|
||||
if (!s.isLocked()) {
|
||||
s.recognise();
|
||||
havenQueue.submit(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1031,9 +982,12 @@ public class AudiobookRecorder extends JFrame {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JMenuObject o = (JMenuObject)e.getSource();
|
||||
Chapter c = (Chapter)o.getObject();
|
||||
BatchConversionThread r = new BatchConversionThread(c);
|
||||
Thread t = new Thread(r);
|
||||
t.start();
|
||||
for (Enumeration s = c.children(); s.hasMoreElements();) {
|
||||
Sentence snt = (Sentence)s.nextElement();
|
||||
if (snt.getId().equals(snt.getText())) {
|
||||
havenQueue.submit(snt);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
|
||||
if (value instanceof Sentence) {
|
||||
Sentence s = (Sentence)value;
|
||||
|
||||
if (s.getOverrideText() != null) {
|
||||
ret.setText(s.getOverrideText());
|
||||
}
|
||||
|
||||
if (s.getAttentionFlag()) {
|
||||
ret.setForeground(new Color(0xFF, 0xFF, 0x00));
|
||||
ret.setIcon(Icons.attention);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package uk.co.majenko.audiobookrecorder;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Timer;
|
||||
|
||||
public class HavenQueue extends JPanel {
|
||||
ConcurrentLinkedQueue<Sentence> sentenceList = new ConcurrentLinkedQueue<Sentence>();
|
||||
|
||||
Timer timer = new Timer();
|
||||
|
||||
Sentence currentSentence = null;
|
||||
|
||||
JLabel count;
|
||||
|
||||
public HavenQueue() {
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 30000);
|
||||
count = new JLabel("Haven queue: 0");
|
||||
setLayout(new BorderLayout());
|
||||
add(count, BorderLayout.CENTER);
|
||||
|
||||
count.setOpaque(false);
|
||||
setOpaque(false);
|
||||
count.setForeground(Color.WHITE);
|
||||
}
|
||||
|
||||
public void processQueue() {
|
||||
|
||||
count.setText("Haven queue: " + sentenceList.size());
|
||||
|
||||
if (currentSentence == null) {
|
||||
// Grab a new sentence to process.
|
||||
currentSentence = sentenceList.poll();
|
||||
|
||||
if (currentSentence != null) {
|
||||
if (!currentSentence.postHavenData()) { // Failed. Add to the end of the queue and wait a bit
|
||||
submit(currentSentence);
|
||||
currentSentence = null;
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 30000);
|
||||
return;
|
||||
}
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentSentence != null) {
|
||||
currentSentence.processPendingHaven();
|
||||
int status = currentSentence.getHavenStatus();
|
||||
switch (status) {
|
||||
case 0: // Um... not running...?
|
||||
currentSentence = null;
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 30000);
|
||||
return;
|
||||
case 1: // Still processing...
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 5000);
|
||||
return;
|
||||
case 2: // Finished
|
||||
currentSentence = null;
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 30000);
|
||||
return;
|
||||
case 3: // Failed
|
||||
currentSentence = null;
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 30000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
timer.schedule(new TimerTask() { public void run() { processQueue(); }}, 30000);
|
||||
}
|
||||
|
||||
public void submit(Sentence s) {
|
||||
s.setOverrideText("[queued...]");
|
||||
AudiobookRecorder.window.bookTreeModel.reload(s);
|
||||
sentenceList.add(s);
|
||||
count.setText("Haven queue: " + sentenceList.size());
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,8 @@ public class Options extends JDialog {
|
||||
JCheckBox enableParsing;
|
||||
JSpinner cacheSize;
|
||||
|
||||
JTextField havenApiKey;
|
||||
|
||||
Equaliser equaliser;
|
||||
|
||||
JTextArea startupScript;
|
||||
@@ -98,6 +100,27 @@ public class Options extends JDialog {
|
||||
return o;
|
||||
}
|
||||
|
||||
JTextField addTextField(JPanel panel, String label, String def) {
|
||||
JLabel l = new JLabel(label);
|
||||
constraint.gridx = 0;
|
||||
constraint.gridwidth = 1;
|
||||
constraint.gridheight = 1;
|
||||
constraint.anchor = GridBagConstraints.LINE_START;
|
||||
panel.add(l, constraint);
|
||||
|
||||
JTextField a = new JTextField(def);
|
||||
constraint.gridx = 1;
|
||||
|
||||
constraint.fill = GridBagConstraints.HORIZONTAL;
|
||||
panel.add(a, constraint);
|
||||
|
||||
constraint.fill = GridBagConstraints.NONE;
|
||||
|
||||
constraint.gridy++;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
JTextField addFilePath(JPanel panel, String label, String path, boolean dironly) {
|
||||
JLabel l = new JLabel(label);
|
||||
constraint.gridx = 0;
|
||||
@@ -276,7 +299,8 @@ public class Options extends JDialog {
|
||||
|
||||
addSeparator(optionsPanel);
|
||||
|
||||
enableParsing = addCheckBox(optionsPanel, "Enable automatic sphinx speech-to-text (**SLOW**)", getBoolean("process.sphinx"));
|
||||
enableParsing = addCheckBox(optionsPanel, "Enable automatic speech-to-text submission", getBoolean("process.haven.auto"));
|
||||
havenApiKey = addTextField(optionsPanel, "Haven OnDemand API Key", get("process.haven.apikey"));
|
||||
|
||||
addSeparator(optionsPanel);
|
||||
|
||||
@@ -463,6 +487,7 @@ public class Options extends JDialog {
|
||||
defaultPrefs.put("audio.export.samplerate", "44100");
|
||||
|
||||
defaultPrefs.put("process.sphinx", "false");
|
||||
defaultPrefs.put("process.haven.apikey", "");
|
||||
|
||||
defaultPrefs.put("cache.size", "100");
|
||||
|
||||
@@ -603,6 +628,7 @@ public class Options extends JDialog {
|
||||
set("audio.export.bitrate", ((KVPair)bitRate.getSelectedItem()).key);
|
||||
set("audio.export.samplerate", ((KVPair)exportRate.getSelectedItem()).key);
|
||||
set("process.sphinx", enableParsing.isSelected());
|
||||
set("process.haven.apikey", havenApiKey.getText());
|
||||
set("cache.size", cacheSize.getValue());
|
||||
|
||||
for (int i = 0; i < 31; i++) {
|
||||
|
||||
@@ -9,12 +9,25 @@ import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import javax.swing.tree.*;
|
||||
import javax.sound.sampled.*;
|
||||
import edu.cmu.sphinx.api.*;
|
||||
import edu.cmu.sphinx.decoder.adaptation.*;
|
||||
import edu.cmu.sphinx.result.*;
|
||||
import davaguine.jeq.spi.EqualizerInputStream;
|
||||
import davaguine.jeq.core.IIRControls;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.http.entity.mime.content.FileBody;
|
||||
import org.apache.http.entity.mime.content.StringBody;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import org.json.*;
|
||||
|
||||
import java.util.Timer;
|
||||
|
||||
|
||||
public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
|
||||
String text;
|
||||
@@ -34,6 +47,19 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
boolean inSample;
|
||||
boolean attention = false;
|
||||
|
||||
String havenJobId = "";
|
||||
|
||||
// 0: Not processed
|
||||
// 1: Submitted
|
||||
// 2: Procesisng finished
|
||||
// 3: Processing failed
|
||||
int havenStatus = 0;
|
||||
|
||||
String overrideText = null;
|
||||
|
||||
public void setOverrideText(String s) { overrideText = s; }
|
||||
public String getOverrideText() { return overrideText; }
|
||||
|
||||
TargetDataLine line;
|
||||
AudioInputStream inputStream;
|
||||
AudioFormat storedFormat = null;
|
||||
@@ -160,7 +186,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
} else if (tm.equals("fft")) {
|
||||
autoTrimSampleFFT();
|
||||
}
|
||||
if (Options.getBoolean("process.sphinx")) {
|
||||
if (Options.getBoolean("process.haven.auto")) {
|
||||
recognise();
|
||||
}
|
||||
}
|
||||
@@ -330,6 +356,7 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
}
|
||||
|
||||
public void setText(String t) {
|
||||
overrideText = null;
|
||||
text = t;
|
||||
}
|
||||
|
||||
@@ -575,72 +602,8 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void doRecognition(StreamSpeechRecognizer recognizer) {
|
||||
try {
|
||||
setText("[recognising...]");
|
||||
AudiobookRecorder.window.bookTreeModel.reload(this);
|
||||
|
||||
AudioFormat format = getAudioFormat();
|
||||
|
||||
byte[] inData = getRawAudioData();
|
||||
int inLength = inData.length;
|
||||
int bps = format.getFrameSize();
|
||||
int inSamples = inLength / bps;
|
||||
|
||||
int outSamples = inSamples / 4;
|
||||
byte[] outData = new byte[outSamples * bps];
|
||||
|
||||
for (int i = 0; i < outSamples; i++) {
|
||||
for (int j = 0; j < bps; j++) {
|
||||
outData[i * bps + j] = inData[(i * 4) * bps + j];
|
||||
}
|
||||
}
|
||||
|
||||
ByteArrayInputStream bas = new ByteArrayInputStream(outData);
|
||||
recognizer.startRecognition(bas);
|
||||
SpeechResult result;
|
||||
String res = "";
|
||||
while ((result = recognizer.getResult()) != null) {
|
||||
res += result.getHypothesis();
|
||||
res += " ";
|
||||
}
|
||||
recognizer.stopRecognition();
|
||||
|
||||
text = res;
|
||||
|
||||
AudiobookRecorder.window.bookTreeModel.reload(Sentence.this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void recognise() {
|
||||
Thread t = new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Configuration sphinxConfig = new Configuration();
|
||||
|
||||
sphinxConfig.setAcousticModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us");
|
||||
sphinxConfig.setDictionaryPath("resource:/edu/cmu/sphinx/models/en-us/cmudict-en-us.dict");
|
||||
sphinxConfig.setLanguageModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us.lm.bin");
|
||||
|
||||
AudioInputStream s = AudioSystem.getAudioInputStream(getFile());
|
||||
AudioFormat format = getAudioFormat();
|
||||
|
||||
sphinxConfig.setSampleRate((int)(format.getSampleRate() / 4f));
|
||||
|
||||
StreamSpeechRecognizer recognizer;
|
||||
|
||||
recognizer = new StreamSpeechRecognizer(sphinxConfig);
|
||||
|
||||
doRecognition(recognizer);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
t.start();
|
||||
AudiobookRecorder.window.havenQueue.submit(Sentence.this);
|
||||
}
|
||||
|
||||
public void setLocked(boolean l) {
|
||||
@@ -741,4 +704,134 @@ public class Sentence extends DefaultMutableTreeNode implements Cacheable {
|
||||
public boolean getAttentionFlag() {
|
||||
return attention;
|
||||
}
|
||||
|
||||
public String getHavenJobId() {
|
||||
return havenJobId;
|
||||
}
|
||||
|
||||
public void setHavenJobId(String i) {
|
||||
havenJobId = i;
|
||||
}
|
||||
|
||||
public int getHavenStatus() {
|
||||
return havenStatus;
|
||||
}
|
||||
|
||||
public void setHavenStatus(int i) {
|
||||
havenStatus = i;
|
||||
}
|
||||
|
||||
public boolean postHavenData() {
|
||||
String apiKey = Options.get("process.haven.apikey");
|
||||
if (apiKey == null || apiKey.equals("")) return false;
|
||||
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
|
||||
setOverrideText("[submitting...]");
|
||||
AudiobookRecorder.window.bookTreeModel.reload(this);
|
||||
|
||||
try {
|
||||
HttpPost httppost = new HttpPost("https://api.havenondemand.com/1/api/async/recognizespeech/v2?apikey=" + apiKey);
|
||||
|
||||
FileBody bin = new FileBody(getFile());
|
||||
StringBody language = new StringBody("en-GB");
|
||||
|
||||
HttpEntity reqEntity = MultipartEntityBuilder.create()
|
||||
.addPart("language_model", language)
|
||||
.addPart("file", bin)
|
||||
.build();
|
||||
|
||||
httppost.setEntity(reqEntity);
|
||||
|
||||
CloseableHttpResponse response = httpclient.execute(httppost);
|
||||
try {
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
System.err.println("Error posting data: " + response.getStatusLine().getStatusCode());
|
||||
return false;
|
||||
}
|
||||
|
||||
HttpEntity resEntity = response.getEntity();
|
||||
if (resEntity != null) {
|
||||
JSONObject obj = new JSONObject(EntityUtils.toString(resEntity));
|
||||
havenJobId = obj.getString("jobID");
|
||||
System.err.println("Submitted new Haven OnDemand job #" + havenJobId);
|
||||
havenStatus = 1;
|
||||
}
|
||||
EntityUtils.consume(resEntity);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
// eddec91c-6018-4dcd-bd8d-5e96b23e334c --form "language_model=en-US" --form "file=@3e67460c-f298-4e2c-a412-d375d489e1b3.wav"
|
||||
}
|
||||
|
||||
public void processPendingHaven() {
|
||||
if (havenStatus != 1) return;
|
||||
|
||||
|
||||
String apiKey = Options.get("process.haven.apikey");
|
||||
if (apiKey == null || apiKey.equals("")) return;
|
||||
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
|
||||
|
||||
try {
|
||||
HttpPost httppost = new HttpPost("https://api.havenondemand.com/1/job/status/" + havenJobId + "?apikey=" + apiKey);
|
||||
|
||||
HttpEntity reqEntity = MultipartEntityBuilder.create().build();
|
||||
httppost.setEntity(reqEntity);
|
||||
|
||||
CloseableHttpResponse response = httpclient.execute(httppost);
|
||||
try {
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
havenStatus = 3;
|
||||
return;
|
||||
}
|
||||
|
||||
HttpEntity resEntity = response.getEntity();
|
||||
if (resEntity != null) {
|
||||
JSONObject obj = new JSONObject(EntityUtils.toString(resEntity));
|
||||
|
||||
System.err.println(havenJobId + ": " + obj.getString("status"));
|
||||
|
||||
if (obj.getString("status").equals("finished")) {
|
||||
havenStatus = 2;
|
||||
JSONArray textItems = obj.getJSONArray("actions").getJSONObject(0).getJSONObject("result").getJSONArray("items");
|
||||
|
||||
StringBuilder out = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < textItems.length(); i++) {
|
||||
out.append(textItems.getJSONObject(i).getString("text"));
|
||||
out.append(" ");
|
||||
}
|
||||
String result = out.toString();
|
||||
setText(result.trim());
|
||||
AudiobookRecorder.window.bookTreeModel.reload(Sentence.this);
|
||||
System.err.println(result);
|
||||
} else if (obj.getString("status").equals("queued")) {
|
||||
havenStatus = 1;
|
||||
setOverrideText("[processing...]");
|
||||
AudiobookRecorder.window.bookTreeModel.reload(Sentence.this);
|
||||
} else {
|
||||
text = id;
|
||||
AudiobookRecorder.window.bookTreeModel.reload(Sentence.this);
|
||||
havenStatus = 3;
|
||||
return;
|
||||
}
|
||||
}
|
||||
EntityUtils.consume(resEntity);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user