Compare commits

...

7 Commits

13 changed files with 442 additions and 266 deletions

View File

@@ -29,12 +29,13 @@ From here on much is controlled by key presses.
appended to the currently selected chapter, or to the last chapter if none is selected.
* Press and hold "T" to record a new phrase that is the start of a new paragraph. This adds the "post paragraph" gap to the previous sentence. Otherwise it does the same as "R".
* Press and hold "F" to record a "continuation" phrase. This sets the previous phrase's post-gap to be the "short" gap instead of the normal length gap.
* Press and hold "Y" to record a new phrase that is the start of a new section. This add the "post section" gap to the previous sentence. Otherwise it does the same as "R".
* Press "D" to delete the last phrase you recorded.
* Press "E" to re-record the currently selected phrase.
Each phrase you record will be briefly analysed using FFT to find the start and end of the audio and set
Each phrase you record can be automatically analysed to find the start and end of the audio and set
crop marks appropriately. These can be adjusted in the waveform display when a phrase is selected. You can also
re-run the analysis using either the default FFT method or using a peak detector method (finding the first and last points
re-run the analysis using either FFT or a peak detector method (finding the first and last points
where the audio amplitude rises above the backround noise).
The phrases also have a "post gap" associated with them. This is the amount of room noise (in milliseconds) to place between
@@ -53,18 +54,14 @@ edit the text of this ID to identify the recordings. You
may, for instance, change it to have the same text as the
audio contains.
To help with this the Haven On-Demand online speech recognition
service is integrated with the system and can be used to try and convert the
audio into text. Right clicking on a recording brings
up a menu which includes the option to try and convert
the audio into text. The detected text is then used to
replace the current recording ID / text.
The audio can also be automatically converted to text if you have an suitable command-line
executable that will work. One example is (on Linux) [DeepSpeech](https://github.com/mozilla/DeepSpeech) by Mozilla.
File layout
-----------
All data is stored in your "storage" directory (specified in Options). Each book (which is a directory named after the
title of the book) has an associated XML file (audiobook.abk) and a directory "files" where all the audio (stored as WAV
title of the book) has an associated XML file (audiobook.abx) and a directory "files" where all the audio (stored as WAV
files) is placed.
When you export the book as MP3 a new folder "export" is created within the book's folder where the MP3 files are placed.
@@ -81,3 +78,26 @@ Building
5. Build with `ant build`
6. Run with `java -jar ./AudiobookRecorder.jar`
----
Extra Resources
===============
* DeepSpeech wrapper script
This is a small script that will convert the audio into a format DeepSpeech likes and call the `deepspeech` executable, removing any extra rubbish from the output. It
also requires `sox` to be installed for the audio conversion.
```
#!/bin/bash
ID=$$
FILE=$1
BINPATH=${HOME}/local/bin
MODELS=${HOME}/ds/deepspeech-0.6.1-models
sox "$FILE" -r 16000 -c 1 -b 16 "/tmp/ds-${ID}.wav"
${BINPATH}/deepspeech --model ${MODELS}/output_graph.pbmm --lm ${MODELS}/lm.binary --trie ${MODELS}/trie --audio "/tmp/ds-${ID}.wav" 2>/dev/null
rm /tmp/ds-${ID}.wav
```

View File

@@ -1 +1 @@
version=0.3.6
version=0.3.8

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -11,6 +11,7 @@ import java.util.Enumeration;
import java.util.Properties;
import java.util.Queue;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.UUID;
@@ -18,6 +19,7 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import java.lang.reflect.Method;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Desktop;
@@ -177,6 +179,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
JSpinner postSentenceGap;
JSpinner gainPercent;
Timer waveformUpdater = new Timer();
JCheckBox locked;
JCheckBox attention;
JCheckBox rawAudio;
@@ -203,7 +206,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public static AudiobookRecorder window;
public Queue<Runnable>speechProcessQueue = null;
public Queue<Runnable>processQueue = null;
public QueueMonitor queueMonitor = null;
void buildToolbar(Container ob) {
Debug.trace();
@@ -419,7 +423,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
Debug.debugEnabled = CLI.isSet("debug");
Debug.traceEnabled = CLI.isSet("trace");
speechProcessQueue = new ArrayDeque<Runnable>();
processQueue = new ArrayDeque<Runnable>();
try {
@@ -444,8 +448,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
Options.loadPreferences();
queueMonitor = new QueueMonitor(processQueue);
for (int i = 0; i < Options.getInteger("process.threads"); i++) {
WorkerThread worker = new WorkerThread(speechProcessQueue);
WorkerThread worker = new WorkerThread(processQueue, queueMonitor);
queueMonitor.addThread(worker);
worker.start();
}
@@ -529,11 +536,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public void actionPerformed(ActionEvent e) {
Debug.trace();
if (selectedSentence != null) {
selectedSentence.autoTrimSampleFFT();
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
postSentenceGap.setValue(selectedSentence.getPostGap());
gainPercent.setValue((int)(selectedSentence.getGain() * 100d));
queueJob(new SentenceJob(selectedSentence) {
public void run() {
sentence.autoTrimSampleFFT();
updateWaveformMarkers();
}
});
}
}
});
@@ -542,11 +550,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public void actionPerformed(ActionEvent e) {
Debug.trace();
if (selectedSentence != null) {
selectedSentence.autoTrimSamplePeak();
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
postSentenceGap.setValue(selectedSentence.getPostGap());
gainPercent.setValue((int)(selectedSentence.getGain() * 100d));
queueJob(new SentenceJob(selectedSentence) {
public void run() {
sentence.autoTrimSamplePeak();
updateWaveformMarkers();
}
});
}
}
});
@@ -654,8 +663,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
doCutSplit.setEnabled(false);
selectCutMode.setSelected(false);
selectSplitMode.setSelected(false);
bookTreeModel.reload(selectedSentence);
selectedSentence.reloadTree();
}
});
controlsTop.add(locked);
@@ -676,7 +684,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
selectedSentence.setAttentionFlag(false);
}
}
bookTreeModel.reload(selectedSentence);
selectedSentence.reloadTree();
}
});
@@ -744,10 +752,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
centralPanel.add(sampleControl, BorderLayout.SOUTH);
statusBar = new JPanel();
statusBar.setLayout(new FlowLayout(FlowLayout.CENTER));
add(statusBar, BorderLayout.SOUTH);
statusLabel = new JLabel("Noise floor: " + getNoiseFloorDB() + "dB");
statusBar.add(statusLabel);
statusBar.add(queueMonitor);
buildToolbar(centralPanel);
@@ -931,12 +941,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
Debug.trace();
if (ev.getPropertyName().equals("dividerLocation")) {
if ((bookTreeModel != null) && (book != null)) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Debug.trace();
bookTreeModel.reload(book);
}
});
Debug.trace();
book.reloadTree();
}
}
}
@@ -1039,6 +1045,8 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public static void main(String args[]) {
Debug.trace();
Properties props = System.getProperties();
props.setProperty("sun.java2d.opengl", "true");
try {
config.load(AudiobookRecorder.class.getResourceAsStream("config.txt"));
} catch (Exception e) {
@@ -1191,10 +1199,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
Sentence snt = (Sentence)s.nextElement();
if (!snt.isLocked()) {
if (snt.getId().equals(snt.getText())) {
Runnable r = snt.getRecognitionRunnable();
snt.setQueued();
speechProcessQueue.add(r);
speechProcessQueue.notify();
queueJob(new SentenceJob(snt) {
public void run() {
sentence.doRecognition();
}
});
}
}
}
@@ -1227,7 +1236,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
JMenuObject o = (JMenuObject)e.getSource();
Sentence s = (Sentence)o.getObject();
if (!s.isLocked()) {
s.recognise();
queueJob(new SentenceJob(s) {
public void run() {
sentence.doRecognition();
}
});
}
}
});
@@ -1304,7 +1317,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
String type = (String)o.getObject2();
sent.setPostGapType(type);
sent.setPostGap(Options.getInteger("catenation.post-sentence"));
bookTreeModel.reload(sent);
sent.reloadTree();
}
});
setGapType.add(gapTypeSentence);
@@ -1317,7 +1330,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
String type = (String)o.getObject2();
sent.setPostGapType(type);
sent.setPostGap(Options.getInteger("catenation.short-sentence"));
bookTreeModel.reload(sent);
sent.reloadTree();
}
});
setGapType.add(gapTypeContinuation);
@@ -1330,7 +1343,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
String type = (String)o.getObject2();
sent.setPostGapType(type);
sent.setPostGap(Options.getInteger("catenation.post-paragraph"));
bookTreeModel.reload(sent);
sent.reloadTree();
}
});
setGapType.add(gapTypeParagraph);
@@ -1343,7 +1356,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
String type = (String)o.getObject2();
sent.setPostGapType(type);
sent.setPostGap(Options.getInteger("catenation.post-section"));
bookTreeModel.reload(sent);
sent.reloadTree();
}
});
setGapType.add(gapTypeSection);
@@ -1472,14 +1485,19 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public void actionPerformed(ActionEvent e) {
Debug.trace();
JMenuObject o = (JMenuObject)e.getSource();
Chapter chap = (Chapter)o.getObject();
Chapter c = (Chapter)o.getObject();
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
if (!snt.isProcessed()) {
queueJob(new SentenceJob(snt) {
public void run() {
sentence.autoTrimSampleFFT();
updateWaveformMarkers();
}
});
}
}
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.Peak, AutoTrimThread.NewOnly);
Thread nt = new Thread(t);
nt.start();
ed.setVisible(true);
}
});
@@ -1487,14 +1505,18 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public void actionPerformed(ActionEvent e) {
Debug.trace();
JMenuObject o = (JMenuObject)e.getSource();
Chapter chap = (Chapter)o.getObject();
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.FFT, AutoTrimThread.NewOnly);
Thread nt = new Thread(t);
nt.start();
ed.setVisible(true);
Chapter c = (Chapter)o.getObject();
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
if (!snt.isProcessed()) {
queueJob(new SentenceJob(snt) {
public void run() {
sentence.autoTrimSampleFFT();
updateWaveformMarkers();
}
});
}
}
}
});
@@ -1502,14 +1524,16 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public void actionPerformed(ActionEvent e) {
Debug.trace();
JMenuObject o = (JMenuObject)e.getSource();
Chapter chap = (Chapter)o.getObject();
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.Peak, AutoTrimThread.All);
Thread nt = new Thread(t);
nt.start();
ed.setVisible(true);
Chapter c = (Chapter)o.getObject();
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
queueJob(new SentenceJob(snt) {
public void run() {
sentence.autoTrimSamplePeak();
updateWaveformMarkers();
}
});
}
}
});
@@ -1517,14 +1541,16 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
public void actionPerformed(ActionEvent e) {
Debug.trace();
JMenuObject o = (JMenuObject)e.getSource();
Chapter chap = (Chapter)o.getObject();
ProgressDialog ed = new ProgressDialog("Auto-trimming " + chap.getName());
AutoTrimThread t = new AutoTrimThread(chap, ed, AutoTrimThread.FFT, AutoTrimThread.All);
Thread nt = new Thread(t);
nt.start();
ed.setVisible(true);
Chapter c = (Chapter)o.getObject();
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
queueJob(new SentenceJob(snt) {
public void run() {
sentence.autoTrimSampleFFT();
updateWaveformMarkers();
}
});
}
}
});
@@ -1597,7 +1623,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
snt.setLocked(true);
bookTreeModel.reload(snt);
snt.reloadTree();
}
}
});
@@ -1610,7 +1636,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
for (Enumeration s = c.children(); s.hasMoreElements();) {
Sentence snt = (Sentence)s.nextElement();
snt.setLocked(false);
bookTreeModel.reload(snt);
snt.reloadTree();
}
}
});
@@ -1658,13 +1684,11 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
Sentence snt = (Sentence)s.nextElement();
if (!snt.isLocked()) {
if (!snt.beenDetected()) {
Debug.d("Queueing recognition of", snt.getId());
synchronized(speechProcessQueue) {
Runnable r = snt.getRecognitionRunnable();
snt.setQueued();
speechProcessQueue.add(r);
speechProcessQueue.notify();
}
queueJob(new SentenceJob(snt) {
public void run() {
sentence.doRecognition();
}
});
}
}
}
@@ -2073,7 +2097,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
if (recording == null) return;
recording.stopRecording();
bookTreeModel.reload(book);
// book.reloadTree();
bookTree.expandPath(new TreePath(((DefaultMutableTreeNode)recording.getParent()).getPath()));
bookTree.setSelectionPath(new TreePath(recording.getPath()));
@@ -2188,8 +2212,12 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
bookTree.setEditable(true);
bookTree.setUI(new CustomTreeUI(mainScroll));
bookTree.setCellRenderer(new BookTreeRenderer());
try {
bookTree.setCellRenderer(new BookTreeRenderer());
} catch (Exception ex) {
ex.printStackTrace();
bookTree.setCellRenderer(new BookTreeRenderer());
}
InputMap im = bookTree.getInputMap(JComponent.WHEN_FOCUSED);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "startStopPlayback");
@@ -2283,7 +2311,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
} else {
book.setIcon(Icons.book);
}
bookTreeModel.reload(book);
book.reloadTree();
bookTree.expandPath(new TreePath(book.getPath()));
@@ -2319,7 +2347,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
ImageIcon i = new ImageIcon(cf.getAbsolutePath());
Image ri = Utils.getScaledImage(i.getImage(), 22, 22);
book.setIcon(new ImageIcon(ri));
bookTreeModel.reload(book);
book.reloadTree();
}
} catch (Exception e) {
e.printStackTrace();
@@ -2719,55 +2747,6 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
}
}
class AutoTrimThread implements Runnable {
ProgressDialog dialog;
Chapter chapter;
int type;
int scope;
public final static int FFT = 0;
public final static int Peak = 1;
public final static int NewOnly = 0;
public final static int All = 1;
public AutoTrimThread(Chapter c, ProgressDialog e, int t, int sc) {
super();
Debug.trace();
dialog = e;
chapter = c;
type = t;
scope = sc;
}
@SuppressWarnings("unchecked")
public void run() {
Debug.trace();
int numKids = chapter.getChildCount();
int kidCount = 0;
for (Enumeration s = chapter.children(); s.hasMoreElements();) {
kidCount++;
dialog.setProgress(kidCount * 2000 / numKids);
Sentence snt = (Sentence)s.nextElement();
if (scope == NewOnly) {
if (snt.isProcessed()) continue;
}
switch (type) {
case FFT:
snt.autoTrimSampleFFT();
break;
case Peak:
snt.autoTrimSamplePeak();
break;
}
}
dialog.closeDialog();
}
}
class ExportThread implements Runnable {
ProgressDialog exportDialog;
Chapter chapter;
@@ -3503,7 +3482,7 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
ImageIcon i = new ImageIcon(dest.getAbsolutePath());
Image ri = Utils.getScaledImage(i.getImage(), 22, 22);
book.setIcon(new ImageIcon(ri));
bookTreeModel.reload(book);
book.reloadTree();
} catch (Exception e) {
e.printStackTrace();
@@ -3516,16 +3495,42 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
updateWaveform(false);
}
public void updateWaveform(boolean force) {
TimerTask waveformUpdaterTask = null;
synchronized public void updateWaveform(boolean force) {
Debug.trace();
if (selectedSentence != null) {
if ((!force) && (sampleWaveform.getId() != null) && (sampleWaveform.getId().equals(selectedSentence.getId()))) return;
sampleWaveform.setId(selectedSentence.getId());
if (rawAudio.isSelected()) {
sampleWaveform.setData(selectedSentence.getRawAudioData());
} else {
sampleWaveform.setData(selectedSentence.getDoubleAudioData(effectsEnabled));
synchronized (waveformUpdater) {
try {
if (waveformUpdaterTask != null) {
waveformUpdaterTask.cancel();
}
} catch (Exception ex) {
}
waveformUpdaterTask = new TimerTask() {
public void run() {
sampleWaveform.setId(selectedSentence.getId());
if (rawAudio.isSelected()) {
sampleWaveform.setData(selectedSentence.getRawAudioData());
} else {
sampleWaveform.setData(selectedSentence.getDoubleAudioData(effectsEnabled));
}
}
};
waveformUpdater.schedule(waveformUpdaterTask, 20);
}
}
}
synchronized public void updateWaveformMarkers() {
if (selectedSentence != null) {
sampleWaveform.setMarkers(selectedSentence.getStartOffset(), selectedSentence.getEndOffset());
sampleWaveform.setAltMarkers(selectedSentence.getStartCrossing(), selectedSentence.getEndCrossing());
}
}
@@ -4185,4 +4190,14 @@ public class AudiobookRecorder extends JFrame implements DocumentListener {
return null;
}
public void queueJob(Runnable r) {
synchronized(processQueue) {
processQueue.add(r);
if (r instanceof SentenceJob) {
SentenceJob sj = (SentenceJob)r;
sj.setQueued();
}
processQueue.notify();
}
}
}

View File

@@ -8,6 +8,7 @@ import java.util.UUID;
import java.util.Properties;
import javax.sound.sampled.AudioFormat;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.ImageIcon;
import javax.swing.tree.DefaultTreeModel;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -217,7 +218,7 @@ public class Book extends BookTreeNode {
oldDir.renameTo(newDir);
name = newName;
AudiobookRecorder.window.saveBookStructure();
AudiobookRecorder.window.bookTreeModel.reload(this);
reloadTree();
Options.set("path.last-book", name);
Options.savePreferences();
AudiobookRecorder.window.setTitle("AudioBook Recorder :: " + name);
@@ -447,4 +448,13 @@ public class Book extends BookTreeNode {
public void setLocation(File l) {
location = l;
}
public void reloadTree() {
Debug.trace();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
AudiobookRecorder.window.bookTreeModel.reload(Book.this);
}
});
}
}

View File

@@ -2,9 +2,11 @@ package uk.co.majenko.audiobookrecorder;
import java.awt.Component;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.SwingConstants;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
@@ -84,7 +86,8 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
}
JLabel time = new JLabel(Utils.secToTime(s.getLength(), "mm:ss.SSS") + " ");
JLabel time = new JLabelFixedWidth(75, " " + Utils.secToTime(s.getLength(), "ss.SSS") + " ");
time.setHorizontalAlignment(SwingConstants.RIGHT);
ctx.gridx = 0;
ctx.gridy = 0;
@@ -93,17 +96,18 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
ctx.anchor = GridBagConstraints.LINE_START;
p.add(ret, ctx);
if (s.isDetecting()) {
JLabel eff = new JLabel(" recognising... ");
if (s.isProcessing()) {
JLabel eff = new JLabel();
eff.setIcon(Icons.processing);
ctx.weightx = 0.0d;
ctx.gridx = 1;
p.add(eff);
} else if (s.isQueued()) {
JLabel eff = new JLabel(" queued ");
JLabel eff = new JLabel();
eff.setIcon(Icons.queued);
ctx.weightx = 0.0d;
ctx.gridx = 1;
p.add(eff);
} else if (s.isQueued()) {
}
String effectChain = s.getEffectChain();
@@ -121,7 +125,8 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
ctx.gridx = 3;
ctx.anchor = GridBagConstraints.LINE_END;
int peak = s.getPeakDB();
JLabel peakLabel = new JLabel(peak + "dB ");
JLabel peakLabel = new JLabelFixedWidth(50, peak + "dB ");
peakLabel.setHorizontalAlignment(SwingConstants.RIGHT);
if (peak > 0) {
peakLabel.setForeground(new Color(0xCC, 0x00, 0x00));
}

View File

@@ -40,4 +40,6 @@ public class Icons {
static public final ImageIcon disable = new ImageIcon(Icons.class.getResource("icons/disable-effects.png"));
static public final ImageIcon manuscript = new ImageIcon(Icons.class.getResource("icons/manuscript.png"));
static public final ImageIcon tooltip = new ImageIcon(Icons.class.getResource("icons/tooltip.png"));
static public final ImageIcon queued = new ImageIcon(Icons.class.getResource("icons/queued.png"));
static public final ImageIcon processing = new ImageIcon(Icons.class.getResource("icons/processing.png"));
}

View File

@@ -0,0 +1,38 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.JLabel;
import java.awt.Dimension;
public class JLabelFixedWidth extends JLabel {
int width = 0;
int height = 0;
Dimension size;
public JLabelFixedWidth(int w, String txt) {
super(txt);
JLabel t = new JLabel(txt);
size = t.getPreferredSize();
size.width = w;
}
public JLabelFixedWidth(int w) {
super();
JLabel t = new JLabel("nothing");
size = t.getPreferredSize();
size.width = w;
}
@Override
public Dimension getPreferredSize() {
return size;
}
@Override
public Dimension getMaximumSize() {
return size;
}
@Override
public Dimension getMinimumSize() {
return size;
}
}

View File

@@ -0,0 +1,71 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.JPanel;
import java.util.ArrayList;
import java.util.Queue;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Color;
public class QueueMonitor extends JPanel {
ArrayList<WorkerThread> threadList = new ArrayList<WorkerThread>();
Queue queue;
public QueueMonitor(Queue q) {
super();
queue = q;
}
public void addThread(WorkerThread t) {
threadList.add(t);
}
public void purgeQueue() {
synchronized (queue) {
queue.clear();
repaint();
}
}
@Override
public Dimension getPreferredSize() {
return new Dimension(100 + (24 * threadList.size()), 24);
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
@Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
@Override
public void paintComponent(Graphics g) {
Rectangle size = g.getClipBounds();
g.setColor(getBackground());
g.fillRect(0, 0, size.width - 1, size.height - 1);
g.setColor(new Color(10, 10, 10));
g.drawRect(0, 0, size.width - 1, size.height - 1);
g.setFont(getFont());
for (int i = 0; i < threadList.size(); i++) {
WorkerThread t = threadList.get(i);
if (t.isRunning()) {
g.setColor(new Color(50, 200, 0));
} else {
g.setColor(new Color(80, 0, 0));
}
g.fillOval(i * 24 + 4, 4, 22 - 8, 22 - 8);
}
g.setColor(getForeground());
g.drawString("Queued: " + queue.size(), threadList.size() * 24 + 4, 16);
}
}

View File

@@ -33,6 +33,7 @@ import java.io.FileInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFileFormat;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import java.io.BufferedReader;
import java.io.InputStream;
import java.util.UUID;
@@ -65,8 +66,11 @@ public class Sentence extends BookTreeNode implements Cacheable {
boolean attention = false;
boolean processed = false;
boolean isDetected = false;
boolean detecting = false;
boolean queued = false;
int state = IDLE;
static final int IDLE = 0;
static final int QUEUED = 1;
static final int PROCESSING = 2;
String effectChain = null;
@@ -218,7 +222,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
if (text.equals("")) text = id;
if ((crossStartOffset == -1) || (crossEndOffset == -1)) {
updateCrossings(true);
updateCrossings();
}
if (runtime <= 0.01d) getLength();
@@ -256,26 +260,40 @@ public class Sentence extends BookTreeNode implements Cacheable {
CacheManager.removeFromCache(this);
if (!id.equals("room-noise")) {
autoTrimSample(true);
autoTrimSample();
if (Options.getBoolean("process.sphinx")) {
recognise();
AudiobookRecorder.window.queueJob(new SentenceJob(this) {
public void run() {
sentence.doRecognition();
}
});
}
}
}
public void autoTrimSample() {
public void autoTrimSample(boolean ignored) {
Debug.trace();
autoTrimSample(false);
autoTrimSample();
}
public void autoTrimSample(boolean useRaw) {
public void autoTrimSample() {
Debug.trace();
String tm = Options.get("audio.recording.trim");
if (tm.equals("peak")) {
autoTrimSamplePeak(useRaw);
AudiobookRecorder.window.queueJob(new SentenceJob(this) {
public void run() {
sentence.autoTrimSamplePeak();
AudiobookRecorder.window.updateWaveformMarkers();
}
});
} else if (tm.equals("fft")) {
autoTrimSampleFFT(useRaw);
AudiobookRecorder.window.queueJob(new SentenceJob(this) {
public void run() {
sentence.autoTrimSampleFFT();
AudiobookRecorder.window.updateWaveformMarkers();
}
});
} else {
startOffset = 0;
crossStartOffset = 0;
@@ -284,13 +302,13 @@ public class Sentence extends BookTreeNode implements Cacheable {
processed = false;
// peak = -1d;
}
AudiobookRecorder.window.updateWaveform(true);
}
public static final int FFTBuckets = 1024;
public void autoTrimSampleFFT() {
public void autoTrimSampleFFT(boolean ignored) {
Debug.trace();
autoTrimSampleFFT(false);
}
public double bucketDifference(double[] a, double[] b) {
@@ -304,16 +322,12 @@ public class Sentence extends BookTreeNode implements Cacheable {
return diff;
}
public void autoTrimSampleFFT(boolean useRaw) {
public void autoTrimSampleFFT() {
Debug.trace();
crossStartOffset = -1;
crossEndOffset = -1;
double[][] samples;
if (useRaw) {
samples = getRawAudioData();
} else {
samples = getProcessedAudioData();
}
samples = getProcessedAudioData();
if (samples == null) {
return;
}
@@ -385,7 +399,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
if (endOffset <= startOffset) endOffset = startOffset + fftSize;
if (endOffset < 0) endOffset = 0;
if (endOffset >= samples[LEFT].length) endOffset = samples[LEFT].length;
updateCrossings(useRaw);
updateCrossings();
intens = null;
samples = null;
processed = true;
@@ -436,21 +450,17 @@ public class Sentence extends BookTreeNode implements Cacheable {
}
public void autoTrimSamplePeak() {
public void autoTrimSamplePeak(boolean ignored) {
Debug.trace();
autoTrimSamplePeak(false);
autoTrimSamplePeak();
}
public void autoTrimSamplePeak(boolean useRaw) {
public void autoTrimSamplePeak() {
Debug.trace();
crossStartOffset = -1;
crossEndOffset = -1;
double[][] samples;
if (useRaw) {
samples = getRawAudioData();
} else {
samples = getProcessedAudioData();
}
samples = getProcessedAudioData();
if (samples == null) return;
double noiseFloor = AudiobookRecorder.window.getNoiseFloor();
noiseFloor *= 1.1;
@@ -490,7 +500,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
if (startOffset < 0) startOffset = 0;
if (endOffset >= samples[LEFT].length) endOffset = samples[LEFT].length-1;
updateCrossings(useRaw);
updateCrossings();
processed = true;
reloadTree();
}
@@ -597,38 +607,23 @@ public class Sentence extends BookTreeNode implements Cacheable {
public void updateCrossings() {
Debug.trace();
updateCrossings(false);
}
public void updateCrossings(boolean useRaw) {
Debug.trace();
updateStartCrossing(useRaw);
updateEndCrossing(useRaw);
updateStartCrossing();
updateEndCrossing();
runtime = -1d;
getLength();
}
public void updateStartCrossing() {
Debug.trace();
updateStartCrossing(false);
}
public void updateStartCrossing(boolean useRaw) {
Debug.trace();
if (crossStartOffset == -1) {
crossStartOffset = findNearestZeroCrossing(useRaw, startOffset, 4096);
crossStartOffset = findNearestZeroCrossing(startOffset, 4096);
}
}
public void updateEndCrossing() {
Debug.trace();
updateEndCrossing(false);
}
public void updateEndCrossing(boolean useRaw) {
Debug.trace();
if (crossEndOffset == -1) {
crossEndOffset = findNearestZeroCrossing(useRaw, endOffset, 4096);
crossEndOffset = findNearestZeroCrossing(endOffset, 4096);
}
}
@@ -694,20 +689,8 @@ public class Sentence extends BookTreeNode implements Cacheable {
return null;
}
public Runnable getRecognitionRunnable() {
Runnable r = new Runnable() {
public void run() {
Debug.d("Starting recognition of", getId());
doRecognition();
}
};
return r;
}
public void doRecognition() {
Debug.trace();
detecting = true;
queued = false;
try {
reloadTree();
@@ -727,18 +710,10 @@ public class Sentence extends BookTreeNode implements Cacheable {
setText(res);
isDetected = true;
detecting = false;
reloadTree();
} catch (Exception e) {
e.printStackTrace();
}
detecting = false;
}
public void recognise() {
Debug.trace();
Thread t = new Thread(getRecognitionRunnable());
t.start();
}
public void setLocked(boolean l) {
@@ -776,18 +751,9 @@ public class Sentence extends BookTreeNode implements Cacheable {
}
public int findNearestZeroCrossing(int pos, int range) {
Debug.trace();
return findNearestZeroCrossing(false, pos, range);
}
public int findNearestZeroCrossing(boolean useRaw, int pos, int range) {
Debug.trace();
double[][] data = null;
if (useRaw) {
data = getRawAudioData();
} else {
data = getProcessedAudioData();
}
data = getProcessedAudioData();
if (data == null) return 0;
if (data[LEFT].length == 0) return 0;
@@ -855,7 +821,6 @@ public class Sentence extends BookTreeNode implements Cacheable {
File to = sentence.getFile();
Files.copy(from.toPath(), to.toPath());
// sentence.updateCrossings();
return sentence;
}
@@ -873,24 +838,15 @@ public class Sentence extends BookTreeNode implements Cacheable {
public double getPeakValue() {
Debug.trace();
return getPeakValue(false, true);
return getPeakValue(true);
}
public double getPeakValue(boolean useRaw) {
Debug.trace();
return getPeakValue(useRaw, true);
}
public double getPeakValue(boolean useRaw, boolean applyGain) {
public double getPeakValue(boolean applyGain) {
Debug.trace();
double oldGain = gain;
gain = 1.0d;
double[][] samples = null;
if (useRaw) {
samples = getRawAudioData();
} else {
samples = getProcessedAudioData(true, applyGain);
}
samples = getProcessedAudioData(true, applyGain);
gain = oldGain;
if (samples == null) {
return 0;
@@ -937,7 +893,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
public double normalize(double low, double high) {
Debug.trace();
if (locked) return gain;
double max = getPeakValue(true, false);
double max = getPeakValue(false);
double d = 0.708 / max;
if (d > 1d) d = 1d;
if (d < low) d = low;
@@ -952,7 +908,7 @@ public class Sentence extends BookTreeNode implements Cacheable {
public double normalize() {
Debug.trace();
if (locked) return gain;
double max = getPeakValue(true, false);
double max = getPeakValue(false);
double d = 0.708 / max;
if (d > 1d) d = 1d;
setGain(d);
@@ -1427,21 +1383,23 @@ public class Sentence extends BookTreeNode implements Cacheable {
String def = AudiobookRecorder.window.getDefaultEffectsChain();
Effect eff = AudiobookRecorder.window.effects.get(def);
if (effectsEnabled) {
if (eff != null) {
eff.init(getAudioFormat().getFrameRate());
eff.process(processedAudio);
}
if ((def != null) && (AudiobookRecorder.window.effects != null)) {
Effect eff = AudiobookRecorder.window.effects.get(def);
if (effectsEnabled) {
if (eff != null) {
eff.init(getAudioFormat().getFrameRate());
eff.process(processedAudio);
}
if (effectChain != null) {
// Don't double up the default chain
if (!effectChain.equals(def)) {
eff = AudiobookRecorder.window.effects.get(effectChain);
if (eff != null) {
eff.init(getAudioFormat().getFrameRate());
eff.process(processedAudio);
if (effectChain != null) {
// Don't double up the default chain
if (!effectChain.equals(def)) {
eff = AudiobookRecorder.window.effects.get(effectChain);
if (eff != null) {
eff.init(getAudioFormat().getFrameRate());
eff.process(processedAudio);
}
}
}
}
@@ -1670,9 +1628,11 @@ public class Sentence extends BookTreeNode implements Cacheable {
Debug.trace();
if (id.equals("room-noise")) return;
if (getParent() == null) return;
synchronized (AudiobookRecorder.window.bookTreeModel) {
AudiobookRecorder.window.bookTreeModel.reload(this);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
AudiobookRecorder.window.bookTreeModel.reload(Sentence.this);
}
});
}
public double getPeak() {
@@ -1707,20 +1667,31 @@ public class Sentence extends BookTreeNode implements Cacheable {
return (int)db;
}
public boolean isDetecting() {
return detecting;
}
public boolean beenDetected() {
return isDetected;
}
public boolean isProcessing() {
return state == PROCESSING;
}
public boolean isQueued() {
return queued;
return state == QUEUED;
}
public void setProcessing() {
state = PROCESSING;
reloadTree();
}
public void setQueued() {
queued = true;
state = QUEUED;
reloadTree();
}
public void setDequeued() {
state = IDLE;
reloadTree();
}
}

View File

@@ -0,0 +1,25 @@
package uk.co.majenko.audiobookrecorder;
import java.lang.Runnable;
public abstract class SentenceJob implements Runnable {
protected Sentence sentence;
public SentenceJob(Sentence s) {
sentence = s;
}
public void setQueued() {
sentence.setQueued();
}
public void setDequeued() {
sentence.setDequeued();
}
public void setProcessing() {
sentence.setProcessing();
}
public abstract void run();
}

View File

@@ -5,9 +5,13 @@ import java.util.Queue;
public class WorkerThread extends Thread {
private static int instance = 0;
private final Queue<Runnable> queue;
private final QueueMonitor monitor;
private boolean running = false;
public WorkerThread(Queue<Runnable> queue) {
public WorkerThread(Queue<Runnable> queue, QueueMonitor mon) {
this.queue = queue;
monitor = mon;
setName("Worker Thread " + (instance++));
}
@@ -30,8 +34,19 @@ public class WorkerThread extends Thread {
work = queue.remove();
}
// Process the work item
running = true;
monitor.repaint();
if (work instanceof SentenceJob) {
SentenceJob sj = (SentenceJob)work;
sj.setProcessing();
}
work.run();
if (work instanceof SentenceJob) {
SentenceJob sj = (SentenceJob)work;
sj.setDequeued();
}
running = false;
monitor.repaint();
}
catch ( InterruptedException ie ) {
ie.printStackTrace();
@@ -40,4 +55,8 @@ public class WorkerThread extends Thread {
}
Debug.d(getName(), "died");
}
public boolean isRunning() {
return running;
}
}