Initial import
46
build.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<project name="Audiobook Recorder" default="build">
|
||||||
|
|
||||||
|
<target name="clean" description="Clean out the build directories">
|
||||||
|
<delete dir="bin" />
|
||||||
|
<delete file="AudiobookRecorder.jar" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="compile" description="Compile" depends="">
|
||||||
|
<mkdir dir="bin" />
|
||||||
|
<path id="class.path">
|
||||||
|
<fileset dir="deps">
|
||||||
|
<include name="*.jar" />
|
||||||
|
</fileset>
|
||||||
|
</path>
|
||||||
|
<javac target="1.8"
|
||||||
|
source="1.8"
|
||||||
|
debug="true"
|
||||||
|
debuglevel="lines,vars,source"
|
||||||
|
encoding="UTF-8"
|
||||||
|
bootclasspath="${bootclass.path}"
|
||||||
|
includeAntRuntime="false"
|
||||||
|
deprecation="true"
|
||||||
|
srcdir="src"
|
||||||
|
destdir="bin">
|
||||||
|
<classpath refid="class.path" />
|
||||||
|
<compilerarg value="-Xlint:unchecked"/>
|
||||||
|
</javac>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="build" depends="compile" description="Build">
|
||||||
|
<jar basedir="bin" destfile="AudiobookRecorder.jar">
|
||||||
|
<fileset dir="bin" />
|
||||||
|
<fileset dir="resources" />
|
||||||
|
<zipgroupfileset dir="deps" includes="*.jar" />
|
||||||
|
<manifest>
|
||||||
|
<attribute name="Main-Class" value="uk.co.majenko.audiobookrecorder.AudiobookRecorder" />
|
||||||
|
<attribute name="Class-Path" value="AudiobookRecorder.jar" />
|
||||||
|
<!--attribute name="Version" value="${Version}" />
|
||||||
|
<attribute name="Release" value="${Release}" /-->
|
||||||
|
</manifest>
|
||||||
|
</jar>
|
||||||
|
<chmod perm="0755" file="uecide.jar" />
|
||||||
|
</target>
|
||||||
|
|
||||||
|
</project>
|
||||||
BIN
deps/JTattoo-1.6.10.jar
vendored
Normal file
BIN
deps/sphinx4-core-5prealpha-SNAPSHOT.jar
vendored
Normal file
BIN
deps/sphinx4-data-5prealpha-SNAPSHOT.jar
vendored
Normal file
BIN
resources/uk/co/majenko/audiobookrecorder/icons/book.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/chapter.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/new-chapter.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/new.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/open.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/play.png
Normal file
|
After Width: | Height: | Size: 736 B |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/playon.png
Normal file
|
After Width: | Height: | Size: 748 B |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/record-room.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/record.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/save.png
Normal file
|
After Width: | Height: | Size: 798 B |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/sentence.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
resources/uk/co/majenko/audiobookrecorder/icons/stop.png
Normal file
|
After Width: | Height: | Size: 736 B |
884
src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java
Normal file
@@ -0,0 +1,884 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
import java.lang.reflect.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.prefs.*;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public class AudiobookRecorder extends JFrame {
|
||||||
|
|
||||||
|
MainToolBar toolBar;
|
||||||
|
|
||||||
|
JMenuBar menuBar;
|
||||||
|
JMenu fileMenu;
|
||||||
|
JMenu bookMenu;
|
||||||
|
JMenu toolsMenu;
|
||||||
|
|
||||||
|
JMenuItem fileNewBook;
|
||||||
|
JMenuItem fileOpenBook;
|
||||||
|
JMenuItem fileExit;
|
||||||
|
|
||||||
|
JMenuItem bookNewChapter;
|
||||||
|
JMenuItem bookExportAudio;
|
||||||
|
|
||||||
|
JMenuItem toolsOptions;
|
||||||
|
|
||||||
|
FlashPanel centralPanel;
|
||||||
|
|
||||||
|
JPanel statusBar;
|
||||||
|
|
||||||
|
JLabel statusFormat;
|
||||||
|
|
||||||
|
JScrollPane mainScroll;
|
||||||
|
|
||||||
|
Book book = null;
|
||||||
|
|
||||||
|
JTree bookTree;
|
||||||
|
DefaultTreeModel bookTreeModel;
|
||||||
|
|
||||||
|
Sentence recording = null;
|
||||||
|
Sentence playing = null;
|
||||||
|
Sentence roomNoise = null;
|
||||||
|
Sentence selectedSentence = null;
|
||||||
|
|
||||||
|
JPanel sampleControl;
|
||||||
|
Waveform sampleWaveform;
|
||||||
|
|
||||||
|
JSpinner startOffset;
|
||||||
|
JSpinner endOffset;
|
||||||
|
JSpinner postSentenceGap;
|
||||||
|
|
||||||
|
Thread playingThread = null;
|
||||||
|
|
||||||
|
public static AudiobookRecorder window;
|
||||||
|
|
||||||
|
void buildToolbar(Container ob) {
|
||||||
|
toolBar = new MainToolBar(this);
|
||||||
|
toolBar.disableBook();
|
||||||
|
toolBar.disableSentence();
|
||||||
|
ob.add(toolBar, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildMenu(Container ob) {
|
||||||
|
menuBar = new JMenuBar();
|
||||||
|
|
||||||
|
fileMenu = new JMenu("File");
|
||||||
|
|
||||||
|
fileNewBook = new JMenuItem("New Book...");
|
||||||
|
fileNewBook.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
createNewBook();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileOpenBook = new JMenuItem("Open Book...");
|
||||||
|
fileExit = new JMenuItem("Exit");
|
||||||
|
|
||||||
|
fileMenu.add(fileNewBook);
|
||||||
|
fileMenu.add(fileOpenBook);
|
||||||
|
fileMenu.addSeparator();
|
||||||
|
fileMenu.add(fileExit);
|
||||||
|
|
||||||
|
menuBar.add(fileMenu);
|
||||||
|
|
||||||
|
|
||||||
|
bookMenu = new JMenu("Book");
|
||||||
|
|
||||||
|
bookNewChapter = new JMenuItem("New Chapter");
|
||||||
|
bookExportAudio = new JMenuItem("Export Audio...");
|
||||||
|
|
||||||
|
bookMenu.add(bookNewChapter);
|
||||||
|
bookMenu.add(bookExportAudio);
|
||||||
|
|
||||||
|
menuBar.add(bookMenu);
|
||||||
|
|
||||||
|
toolsMenu = new JMenu("Tools");
|
||||||
|
|
||||||
|
toolsOptions = new JMenuItem("Options");
|
||||||
|
toolsOptions.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
Options o = new Options(AudiobookRecorder.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
toolsMenu.add(toolsOptions);
|
||||||
|
|
||||||
|
menuBar.add(toolsMenu);
|
||||||
|
|
||||||
|
ob.add(menuBar, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
setPreferredSize(new Dimension(700, 500));
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudiobookRecorder() {
|
||||||
|
|
||||||
|
window = this;
|
||||||
|
|
||||||
|
|
||||||
|
Icons.loadIcons();
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
String clsname = "com.jtattoo.plaf.aluminium.AluminiumLookAndFeel";
|
||||||
|
UIManager.setLookAndFeel(clsname);
|
||||||
|
|
||||||
|
Properties p = new Properties();
|
||||||
|
p.put("windowDecoration", "off");
|
||||||
|
p.put("logoString", "Audiobook");
|
||||||
|
p.put("textAntiAliasing", "on");
|
||||||
|
|
||||||
|
Class<?> cls = Class.forName(clsname);
|
||||||
|
Class[] cArg = new Class[1];
|
||||||
|
cArg[0] = Properties.class;
|
||||||
|
Method mth = cls.getMethod("setCurrentTheme", cArg);
|
||||||
|
mth.invoke(cls, p);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Options.loadPreferences();
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buildMenu(this);
|
||||||
|
|
||||||
|
centralPanel = new FlashPanel();
|
||||||
|
centralPanel.setLayout(new BorderLayout());
|
||||||
|
add(centralPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
sampleControl = new JPanel();
|
||||||
|
sampleControl.setLayout(new BorderLayout());
|
||||||
|
sampleControl.setPreferredSize(new Dimension(400, 150));
|
||||||
|
sampleWaveform = new Waveform();
|
||||||
|
|
||||||
|
sampleControl.add(sampleWaveform, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
startOffset = new JSpinner(new SteppedNumericSpinnerModel(0, 0, 1, 0));
|
||||||
|
startOffset.setPreferredSize(new Dimension(100, 20));
|
||||||
|
endOffset = new JSpinner(new SteppedNumericSpinnerModel(0, 0, 1, 0));
|
||||||
|
endOffset.setPreferredSize(new Dimension(100, 20));
|
||||||
|
postSentenceGap = new JSpinner(new SteppedNumericSpinnerModel(0, 5000, 100, 0));
|
||||||
|
postSentenceGap.setPreferredSize(new Dimension(75, 20));
|
||||||
|
|
||||||
|
startOffset.addChangeListener(new ChangeListener() {
|
||||||
|
public void stateChanged(ChangeEvent e) {
|
||||||
|
JSpinner ob = (JSpinner)e.getSource();
|
||||||
|
if (selectedSentence != null) {
|
||||||
|
selectedSentence.setStartOffset((Integer)ob.getValue());
|
||||||
|
sampleWaveform.setLeftMarker((Integer)ob.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
endOffset.addChangeListener(new ChangeListener() {
|
||||||
|
public void stateChanged(ChangeEvent e) {
|
||||||
|
JSpinner ob = (JSpinner)e.getSource();
|
||||||
|
if (selectedSentence != null) {
|
||||||
|
selectedSentence.setEndOffset((Integer)ob.getValue());
|
||||||
|
sampleWaveform.setRightMarker((Integer)ob.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
postSentenceGap.addChangeListener(new ChangeListener() {
|
||||||
|
public void stateChanged(ChangeEvent e) {
|
||||||
|
JSpinner ob = (JSpinner)e.getSource();
|
||||||
|
if (selectedSentence != null) {
|
||||||
|
selectedSentence.setPostGap((Integer)ob.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JPanel controls = new JPanel();
|
||||||
|
controls.add(new JLabel("Start Offset:"));
|
||||||
|
|
||||||
|
JButton startFastDown = new JButton("<<");
|
||||||
|
startFastDown.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
|
||||||
|
int f = (Integer)startOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f -= (max / 10);
|
||||||
|
if (f < 0) f = 0;
|
||||||
|
startOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(startFastDown);
|
||||||
|
|
||||||
|
JButton startSlowDown = new JButton("<");
|
||||||
|
startSlowDown.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
|
||||||
|
int f = (Integer)startOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f -= (max / 100);
|
||||||
|
if (f < 0) f = 0;
|
||||||
|
startOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(startSlowDown);
|
||||||
|
|
||||||
|
controls.add(startOffset);
|
||||||
|
|
||||||
|
JButton startSlowUp = new JButton(">");
|
||||||
|
startSlowUp.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
|
||||||
|
int f = (Integer)startOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f += (max / 100);
|
||||||
|
if (f > max) f = max;
|
||||||
|
startOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(startSlowUp);
|
||||||
|
|
||||||
|
JButton startFastUp = new JButton(">>");
|
||||||
|
startFastUp.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)startOffset.getModel();
|
||||||
|
int f = (Integer)startOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f += (max / 10);
|
||||||
|
if (f > max) f = max;
|
||||||
|
startOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(startFastUp);
|
||||||
|
|
||||||
|
|
||||||
|
controls.add(new JLabel("End Offset:"));
|
||||||
|
|
||||||
|
JButton endFastDown = new JButton("<<");
|
||||||
|
endFastDown.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
|
||||||
|
int f = (Integer)endOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f -= (max / 10);
|
||||||
|
if (f < 0) f = 0;
|
||||||
|
endOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(endFastDown);
|
||||||
|
|
||||||
|
JButton endSlowDown = new JButton("<");
|
||||||
|
endSlowDown.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
|
||||||
|
int f = (Integer)endOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f -= (max / 100);
|
||||||
|
if (f < 0) f = 0;
|
||||||
|
endOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(endSlowDown);
|
||||||
|
|
||||||
|
controls.add(endOffset);
|
||||||
|
|
||||||
|
JButton endSlowUp = new JButton(">");
|
||||||
|
endSlowUp.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
|
||||||
|
int f = (Integer)endOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f += (max / 100);
|
||||||
|
if (f > max) f = max;
|
||||||
|
endOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(endSlowUp);
|
||||||
|
|
||||||
|
JButton endFastUp = new JButton(">>");
|
||||||
|
endFastUp.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
SteppedNumericSpinnerModel m = (SteppedNumericSpinnerModel)endOffset.getModel();
|
||||||
|
int f = (Integer)endOffset.getValue();
|
||||||
|
int max = m.getMaximum();
|
||||||
|
f += (max / 10);
|
||||||
|
if (f > max) f = max;
|
||||||
|
endOffset.setValue(f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
controls.add(endFastUp);
|
||||||
|
|
||||||
|
controls.add(new JLabel("Post gap:"));
|
||||||
|
controls.add(postSentenceGap);
|
||||||
|
controls.add(new JLabel("ms"));
|
||||||
|
|
||||||
|
sampleControl.add(controls, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
centralPanel.add(sampleControl, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
statusBar = new JPanel();
|
||||||
|
add(statusBar, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
statusFormat = new JLabel(Options.getAudioFormat().toString());
|
||||||
|
statusBar.add(statusFormat);
|
||||||
|
|
||||||
|
buildToolbar(centralPanel);
|
||||||
|
|
||||||
|
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("R"), "startRecord");
|
||||||
|
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released R"), "stopRecord");
|
||||||
|
|
||||||
|
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released D"), "deleteLast");
|
||||||
|
|
||||||
|
centralPanel.getActionMap().put("startRecord", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (bookTree.isEditing()) return;
|
||||||
|
startRecording();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
centralPanel.getActionMap().put("stopRecord", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (bookTree.isEditing()) return;
|
||||||
|
stopRecording();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
centralPanel.getActionMap().put("deleteLast", new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (bookTree.isEditing()) return;
|
||||||
|
deleteLastRecording();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mainScroll = new JScrollPane();
|
||||||
|
centralPanel.add(mainScroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
setTitle("AudioBook Recorder");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]) {
|
||||||
|
AudiobookRecorder frame = new AudiobookRecorder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createNewBook() {
|
||||||
|
BookInfoPanel info = new BookInfoPanel("", "", "", "");
|
||||||
|
int r = JOptionPane.showConfirmDialog(this, info, "New Book", JOptionPane.OK_CANCEL_OPTION);
|
||||||
|
if (r != JOptionPane.OK_OPTION) return;
|
||||||
|
|
||||||
|
System.err.println("Name: " + info.getTitle());
|
||||||
|
System.err.println("Author: " + info.getAuthor());
|
||||||
|
System.err.println("Genre: " + info.getGenre());
|
||||||
|
System.err.println("Comment: " + info.getComment());
|
||||||
|
|
||||||
|
String name = info.getTitle();
|
||||||
|
|
||||||
|
File bookdir = new File(Options.get("path.storage"), name);
|
||||||
|
if (bookdir.exists()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "File already exists.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Properties prefs = new Properties();
|
||||||
|
|
||||||
|
prefs.setProperty("book.name", info.getTitle());
|
||||||
|
prefs.setProperty("book.author", info.getAuthor());
|
||||||
|
prefs.setProperty("book.genre", info.getGenre());
|
||||||
|
prefs.setProperty("book.comment", info.getComment());
|
||||||
|
prefs.setProperty("chapter.open.name", "Opening Credits");
|
||||||
|
prefs.setProperty("chapter.open.pre-gap", Options.get("catenation.pre-chapter"));
|
||||||
|
prefs.setProperty("chapter.open.post-gap", Options.get("catenation.post-chapter"));
|
||||||
|
prefs.setProperty("chapter.0001.name", "Chapter 1");
|
||||||
|
prefs.setProperty("chapter.0001.pre-gap", Options.get("catenation.pre-chapter"));
|
||||||
|
prefs.setProperty("chapter.0001.post-gap", Options.get("catenation.post-chapter"));
|
||||||
|
prefs.setProperty("chapter.close.name", "Closing Credits");
|
||||||
|
prefs.setProperty("chapter.close.pre-gap", Options.get("catenation.pre-chapter"));
|
||||||
|
prefs.setProperty("chapter.close.post-gap", Options.get("catenation.post-chapter"));
|
||||||
|
|
||||||
|
buildBook(prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
class JMenuObject extends JMenuItem {
|
||||||
|
Object ob;
|
||||||
|
|
||||||
|
public JMenuObject(String p) {
|
||||||
|
super(p);
|
||||||
|
ob = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JMenuObject(String p, Object o) {
|
||||||
|
super(p);
|
||||||
|
ob = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setObject(Object o) {
|
||||||
|
ob = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getObject() {
|
||||||
|
return ob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void treePopup(MouseEvent e) {
|
||||||
|
|
||||||
|
int selRow = bookTree.getRowForLocation(e.getX(), e.getY());
|
||||||
|
TreePath selPath = bookTree.getPathForLocation(e.getX(), e.getY());
|
||||||
|
if (selRow != -1) {
|
||||||
|
|
||||||
|
DefaultMutableTreeNode node = (DefaultMutableTreeNode)selPath.getLastPathComponent();
|
||||||
|
|
||||||
|
if (node instanceof Sentence) {
|
||||||
|
Sentence s = (Sentence)node;
|
||||||
|
|
||||||
|
bookTree.setSelectionPath(new TreePath(s.getPath()));
|
||||||
|
|
||||||
|
JPopupMenu menu = new JPopupMenu();
|
||||||
|
JMenuObject ins = new JMenuObject("Insert sentence above", s);
|
||||||
|
JMenuObject del = new JMenuObject("Delete sentence", s);
|
||||||
|
JMenuObject edit = new JMenuObject("Edit text", s);
|
||||||
|
JMenuObject process = new JMenuObject("Reprocess audio", s);
|
||||||
|
|
||||||
|
del.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
JMenuObject o = (JMenuObject)e.getSource();
|
||||||
|
Sentence s = (Sentence)o.getObject();
|
||||||
|
s.deleteFiles();
|
||||||
|
Chapter c = (Chapter)s.getParent();
|
||||||
|
bookTreeModel.removeNodeFromParent(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
edit.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
JMenuObject o = (JMenuObject)e.getSource();
|
||||||
|
Sentence s = (Sentence)o.getObject();
|
||||||
|
s.editText();
|
||||||
|
Chapter c = (Chapter)s.getParent();
|
||||||
|
bookTreeModel.reload(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.add(del);
|
||||||
|
menu.add(edit);
|
||||||
|
menu.add(process);
|
||||||
|
menu.show(bookTree, e.getX(), e.getY());
|
||||||
|
} else if (node instanceof Chapter) {
|
||||||
|
Chapter c = (Chapter)node;
|
||||||
|
|
||||||
|
bookTree.setSelectionPath(new TreePath(c.getPath()));
|
||||||
|
|
||||||
|
JPopupMenu menu = new JPopupMenu();
|
||||||
|
JMenuObject ren = new JMenuObject("Rename chapter", c);
|
||||||
|
|
||||||
|
ren.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
JMenuObject o = (JMenuObject)e.getSource();
|
||||||
|
Chapter c = (Chapter)o.getObject();
|
||||||
|
c.renameChapter();
|
||||||
|
bookTreeModel.reload(c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.add(ren);
|
||||||
|
menu.show(bookTree, e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startRecording() {
|
||||||
|
|
||||||
|
if (recording != null) return;
|
||||||
|
if (book == null) return;
|
||||||
|
|
||||||
|
toolBar.disableBook();
|
||||||
|
toolBar.disableSentence();
|
||||||
|
|
||||||
|
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)bookTree.getLastSelectedPathComponent();
|
||||||
|
|
||||||
|
if (selectedNode == null) {
|
||||||
|
selectedNode = book.getLastChapter();
|
||||||
|
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode instanceof Book) {
|
||||||
|
selectedNode = book.getLastChapter();
|
||||||
|
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode instanceof Sentence) {
|
||||||
|
selectedNode = (DefaultMutableTreeNode)selectedNode.getParent();
|
||||||
|
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Chapter c = (Chapter)selectedNode;
|
||||||
|
|
||||||
|
Sentence s = new Sentence();
|
||||||
|
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||||
|
|
||||||
|
bookTree.expandPath(new TreePath(c.getPath()));
|
||||||
|
bookTree.setSelectionPath(new TreePath(s.getPath()));
|
||||||
|
bookTree.scrollPathToVisible(new TreePath(s.getPath()));
|
||||||
|
|
||||||
|
if (s.startRecording()) {
|
||||||
|
recording = s;
|
||||||
|
centralPanel.setFlash(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRecording() {
|
||||||
|
if (recording == null) return;
|
||||||
|
recording.stopRecording();
|
||||||
|
|
||||||
|
bookTreeModel.reload(book);
|
||||||
|
|
||||||
|
bookTree.expandPath(new TreePath(((DefaultMutableTreeNode)recording.getParent()).getPath()));
|
||||||
|
bookTree.setSelectionPath(new TreePath(recording.getPath()));
|
||||||
|
bookTree.scrollPathToVisible(new TreePath(recording.getPath()));
|
||||||
|
|
||||||
|
toolBar.enableBook();
|
||||||
|
toolBar.enableSentence();
|
||||||
|
|
||||||
|
centralPanel.setFlash(false);
|
||||||
|
recording = null;
|
||||||
|
saveBookStructure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteLastRecording() {
|
||||||
|
if (recording != null) return;
|
||||||
|
if (book == null) return;
|
||||||
|
|
||||||
|
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode)bookTree.getLastSelectedPathComponent();
|
||||||
|
|
||||||
|
if (selectedNode == null) {
|
||||||
|
selectedNode = book.getLastChapter();
|
||||||
|
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode instanceof Book) {
|
||||||
|
selectedNode = book.getLastChapter();
|
||||||
|
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedNode instanceof Sentence) {
|
||||||
|
selectedNode = (DefaultMutableTreeNode)selectedNode.getParent();
|
||||||
|
bookTree.setSelectionPath(new TreePath(selectedNode.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Chapter c = (Chapter)selectedNode;
|
||||||
|
|
||||||
|
Sentence s = c.getLastSentence();
|
||||||
|
if (s == null) return;
|
||||||
|
|
||||||
|
s.deleteFiles();
|
||||||
|
bookTreeModel.removeNodeFromParent(s);
|
||||||
|
s = c.getLastSentence();
|
||||||
|
|
||||||
|
bookTree.expandPath(new TreePath(selectedNode.getPath()));
|
||||||
|
if (s != null) {
|
||||||
|
bookTree.setSelectionPath(new TreePath(s.getPath()));
|
||||||
|
bookTree.scrollPathToVisible(new TreePath(s.getPath()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChapter() {
|
||||||
|
Chapter c = book.addChapter();
|
||||||
|
Chapter lc = book.getLastChapter();
|
||||||
|
int i = bookTreeModel.getIndexOfChild(book, lc);
|
||||||
|
bookTreeModel.insertNodeInto(c, book, i+1);
|
||||||
|
bookTree.scrollPathToVisible(new TreePath(c.getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void saveBookStructure() {
|
||||||
|
File bookRoot = new File(Options.get("path.storage"), book.getName());
|
||||||
|
if (!bookRoot.exists()) {
|
||||||
|
bookRoot.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
File config = new File(bookRoot, "audiobook.abk");
|
||||||
|
Properties prefs = new Properties();
|
||||||
|
|
||||||
|
|
||||||
|
prefs.setProperty("book.name", book.getName());
|
||||||
|
prefs.setProperty("book.author", book.getAuthor());
|
||||||
|
prefs.setProperty("book.genre", book.getGenre());
|
||||||
|
prefs.setProperty("book.comment", book.getComment());
|
||||||
|
|
||||||
|
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
|
||||||
|
|
||||||
|
Chapter c = o.nextElement();
|
||||||
|
String keybase = "chapter." + c.getId();
|
||||||
|
prefs.setProperty(keybase + ".name", c.getName());
|
||||||
|
prefs.setProperty(keybase + ".pre-gap", Integer.toString(c.getPreGap()));
|
||||||
|
prefs.setProperty(keybase + ".post-gap", Integer.toString(c.getPostGap()));
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
|
||||||
|
Sentence snt = s.nextElement();
|
||||||
|
prefs.setProperty(String.format("%s.sentence.%08d.id", keybase, i), snt.getId());
|
||||||
|
prefs.setProperty(String.format("%s.sentence.%08d.text", keybase, i), snt.getText());
|
||||||
|
prefs.setProperty(String.format("%s.sentence.%08d.post-gap", keybase, i), Integer.toString(snt.getPostGap()));
|
||||||
|
prefs.setProperty(String.format("%s.sentence.%08d.start-offset", keybase, i), Integer.toString(snt.getStartOffset()));
|
||||||
|
prefs.setProperty(String.format("%s.sentence.%08d.end-offset", keybase, i), Integer.toString(snt.getEndOffset()));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileOutputStream fos = new FileOutputStream(config);
|
||||||
|
prefs.storeToXML(fos, "Audiobook Recorder Description");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadBookStructure(File f) {
|
||||||
|
try {
|
||||||
|
Properties prefs = new Properties();
|
||||||
|
FileInputStream fis = new FileInputStream(f);
|
||||||
|
prefs.loadFromXML(fis);
|
||||||
|
|
||||||
|
buildBook(prefs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void buildBook(Properties prefs) {
|
||||||
|
|
||||||
|
book = new Book(prefs.getProperty("book.name"));
|
||||||
|
|
||||||
|
book.setAuthor(prefs.getProperty("book.author"));
|
||||||
|
book.setGenre(prefs.getProperty("book.genre"));
|
||||||
|
book.setComment(prefs.getProperty("book.comment"));
|
||||||
|
|
||||||
|
bookTreeModel = new DefaultTreeModel(book);
|
||||||
|
bookTree = new JTree(bookTreeModel);
|
||||||
|
bookTree.setEditable(true);
|
||||||
|
bookTree.setUI(new CustomTreeUI(mainScroll));
|
||||||
|
|
||||||
|
roomNoise = new Sentence("room-noise", "Room Noise");
|
||||||
|
|
||||||
|
bookTree.addTreeSelectionListener(new TreeSelectionListener() {
|
||||||
|
public void valueChanged(TreeSelectionEvent e) {
|
||||||
|
DefaultMutableTreeNode n = (DefaultMutableTreeNode)bookTree.getLastSelectedPathComponent();
|
||||||
|
if (n instanceof Sentence) {
|
||||||
|
Sentence s = (Sentence)n;
|
||||||
|
selectedSentence = s;
|
||||||
|
sampleWaveform.setData(s.getAudioData());
|
||||||
|
sampleWaveform.setMarkers(s.getStartOffset(), s.getEndOffset());
|
||||||
|
startOffset.setValue(s.getStartOffset());
|
||||||
|
endOffset.setValue(s.getEndOffset());
|
||||||
|
postSentenceGap.setValue(s.getPostGap());
|
||||||
|
|
||||||
|
int samples = s.getSampleSize();
|
||||||
|
|
||||||
|
((SteppedNumericSpinnerModel)startOffset.getModel()).setMaximum(samples);
|
||||||
|
((SteppedNumericSpinnerModel)endOffset.getModel()).setMaximum(samples);
|
||||||
|
|
||||||
|
toolBar.enableSentence();
|
||||||
|
} else {
|
||||||
|
selectedSentence = null;
|
||||||
|
toolBar.disableSentence();
|
||||||
|
sampleWaveform.clearData();
|
||||||
|
startOffset.setValue(0);
|
||||||
|
endOffset.setValue(0);
|
||||||
|
postSentenceGap.setValue(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
bookTree.addMouseListener(new MouseAdapter() {
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.isPopupTrigger()) {
|
||||||
|
treePopup(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void mouseReleased(MouseEvent e) {
|
||||||
|
if (e.isPopupTrigger()) {
|
||||||
|
treePopup(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
mainScroll.setViewportView(bookTree);
|
||||||
|
|
||||||
|
|
||||||
|
Chapter c = new Chapter("open", prefs.getProperty("chapter.open.name"));
|
||||||
|
c.setPostGap(s2i(prefs.getProperty("chapter.open.post-gap")));
|
||||||
|
c.setPreGap(s2i(prefs.getProperty("chapter.open.pre-gap")));
|
||||||
|
bookTreeModel.insertNodeInto(c, book, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000000; i++) {
|
||||||
|
String id = prefs.getProperty(String.format("chapter.open.sentence.%08d.id", i));
|
||||||
|
String text = prefs.getProperty(String.format("chapter.open.sentence.%08d.text", i));
|
||||||
|
int gap = s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.post-gap", i)));
|
||||||
|
if (id == null) break;
|
||||||
|
Sentence s = new Sentence(id, text);
|
||||||
|
s.setPostGap(gap);
|
||||||
|
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.start-offset", i))));
|
||||||
|
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.end-offset", i))));
|
||||||
|
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int cno = 1; cno < 10000; cno++) {
|
||||||
|
String cname = prefs.getProperty(String.format("chapter.%04d.name", cno));
|
||||||
|
if (cname == null) break;
|
||||||
|
|
||||||
|
c = new Chapter(String.format("%04d", cno), cname);
|
||||||
|
c.setPostGap(s2i(prefs.getProperty(String.format("chapter.%04d.post-gap", cno))));
|
||||||
|
c.setPreGap(s2i(prefs.getProperty(String.format("chapter.%04d.pre-gap", cno))));
|
||||||
|
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000000; i++) {
|
||||||
|
String id = prefs.getProperty(String.format("chapter.%04d.sentence.%08d.id", cno, i));
|
||||||
|
String text = prefs.getProperty(String.format("chapter.%04d.sentence.%08d.text", cno, i));
|
||||||
|
int gap = s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.post-gap", cno, i)));
|
||||||
|
if (id == null) break;
|
||||||
|
Sentence s = new Sentence(id, text);
|
||||||
|
s.setPostGap(gap);
|
||||||
|
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.start-offset", cno, i))));
|
||||||
|
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.end-offset", cno, i))));
|
||||||
|
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c = new Chapter("close", prefs.getProperty("chapter.close.name"));
|
||||||
|
c.setPostGap(s2i(prefs.getProperty("chapter.close.post-gap")));
|
||||||
|
c.setPreGap(s2i(prefs.getProperty("chapter.close.pre-gap")));
|
||||||
|
bookTreeModel.insertNodeInto(c, book, book.getChildCount());
|
||||||
|
|
||||||
|
for (int i = 0; i < 100000000; i++) {
|
||||||
|
String id = prefs.getProperty(String.format("chapter.close.sentence.%08d.id", i));
|
||||||
|
String text = prefs.getProperty(String.format("chapter.close.sentence.%08d.text", i));
|
||||||
|
int gap = s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.post-gap", i)));
|
||||||
|
if (id == null) break;
|
||||||
|
Sentence s = new Sentence(id, text);
|
||||||
|
s.setPostGap(gap);
|
||||||
|
s.setStartOffset(s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.start-offset", i))));
|
||||||
|
s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.end-offset", i))));
|
||||||
|
bookTreeModel.insertNodeInto(s, c, c.getChildCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
bookTree.expandPath(new TreePath(book.getPath()));
|
||||||
|
|
||||||
|
toolBar.enableBook();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void openBook() {
|
||||||
|
|
||||||
|
|
||||||
|
OpenBookPanel info = new OpenBookPanel();
|
||||||
|
int r = JOptionPane.showConfirmDialog(this, info, "Open Book", JOptionPane.OK_CANCEL_OPTION);
|
||||||
|
if (r == JOptionPane.OK_OPTION) {
|
||||||
|
File f = info.getSelectedFile();
|
||||||
|
if (!f.exists()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "File not found.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.isDirectory()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "File is directory.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!f.getName().endsWith(".abk")) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Not a .abk file.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBookStructure(f);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int s2i(String s) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getBookFolder() {
|
||||||
|
File bf = new File(Options.get("path.storage"), book.getName());
|
||||||
|
if (!bf.exists()) {
|
||||||
|
bf.mkdirs();
|
||||||
|
}
|
||||||
|
return bf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNoiseFloor() {
|
||||||
|
int[] samples = roomNoise.getAudioData();
|
||||||
|
if (samples == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int ms = 0;
|
||||||
|
for (int i = 0; i < samples.length; i++) {
|
||||||
|
if (Math.abs(samples[i]) > ms) {
|
||||||
|
ms = Math.abs(samples[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ms *= 10;
|
||||||
|
ms /= 9;
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recordRoomNoise() {
|
||||||
|
if (roomNoise.startRecording()) {
|
||||||
|
|
||||||
|
centralPanel.setFlash(true);
|
||||||
|
java.util.Timer ticker = new java.util.Timer(true);
|
||||||
|
ticker.schedule(new TimerTask() {
|
||||||
|
public void run() {
|
||||||
|
roomNoise.stopRecording();
|
||||||
|
centralPanel.setFlash(false);
|
||||||
|
}
|
||||||
|
}, 5000); // 5 seconds of recording
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void playSelectedSentence() {
|
||||||
|
if (selectedSentence == null) return;
|
||||||
|
|
||||||
|
if (playing != null) return;
|
||||||
|
|
||||||
|
playing = selectedSentence;
|
||||||
|
|
||||||
|
|
||||||
|
playingThread = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
playing.play();
|
||||||
|
playing = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
playingThread.setDaemon(true);
|
||||||
|
playingThread.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/uk/co/majenko/audiobookrecorder/Book.java
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
|
||||||
|
public class Book extends DefaultMutableTreeNode {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String author;
|
||||||
|
String genre;
|
||||||
|
String comment;
|
||||||
|
|
||||||
|
public Book(String bookname) {
|
||||||
|
super(bookname);
|
||||||
|
name = bookname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String a) {
|
||||||
|
author = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGenre(String g) {
|
||||||
|
genre = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String c) {
|
||||||
|
comment = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGenre() {
|
||||||
|
return genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter getClosingCredits() {
|
||||||
|
return getChapterById("close");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter getOpeningCredits() {
|
||||||
|
return getChapterById("open");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Chapter getChapterById(String id) {
|
||||||
|
for (Enumeration<Object>o = children(); o.hasMoreElements();) {
|
||||||
|
Object ob = o.nextElement();
|
||||||
|
if (ob instanceof Chapter) {
|
||||||
|
Chapter c = (Chapter)ob;
|
||||||
|
if (c.getId().equals(id)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter getLastChapter() {
|
||||||
|
Chapter cc = getClosingCredits();
|
||||||
|
if (cc == null) return null;
|
||||||
|
Chapter c = (Chapter)getChildBefore(cc);
|
||||||
|
if (c == null) return null;
|
||||||
|
if (c.getId().equals("open")) return null;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter getChapter(int n) {
|
||||||
|
if (n == 0) return null;
|
||||||
|
return (Chapter)getChildAt(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chapter addChapter() {
|
||||||
|
Chapter lc = getLastChapter();
|
||||||
|
if (lc == null) return new Chapter("1", "Chapter 1");
|
||||||
|
try {
|
||||||
|
int lcid = Integer.parseInt(lc.getId());
|
||||||
|
lcid++;
|
||||||
|
|
||||||
|
Chapter nc = new Chapter(String.format("%04d", lcid), "Chapter " + lcid);
|
||||||
|
return nc;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/uk/co/majenko/audiobookrecorder/BookInfoPanel.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
public class BookInfoPanel extends JPanel {
|
||||||
|
|
||||||
|
JTextField title;
|
||||||
|
JTextField author;
|
||||||
|
JTextField genre;
|
||||||
|
JTextField comment;
|
||||||
|
|
||||||
|
public BookInfoPanel(String t, String a, String g, String c) {
|
||||||
|
super();
|
||||||
|
setLayout(new GridBagLayout());
|
||||||
|
GridBagConstraints con = new GridBagConstraints();
|
||||||
|
|
||||||
|
con.gridx = 0;
|
||||||
|
con.gridy = 0;
|
||||||
|
|
||||||
|
add(new JLabel("Title:"), con);
|
||||||
|
con.gridx = 1;
|
||||||
|
title = new JTextField(t);
|
||||||
|
title.setPreferredSize(new Dimension(200, 20));
|
||||||
|
add(title, con);
|
||||||
|
|
||||||
|
con.gridx = 0;
|
||||||
|
con.gridy++;
|
||||||
|
|
||||||
|
add(new JLabel("Author:"), con);
|
||||||
|
con.gridx = 1;
|
||||||
|
author = new JTextField(a);
|
||||||
|
author.setPreferredSize(new Dimension(200, 20));
|
||||||
|
add(author, con);
|
||||||
|
|
||||||
|
con.gridx = 0;
|
||||||
|
con.gridy++;
|
||||||
|
|
||||||
|
add(new JLabel("Genre:"), con);
|
||||||
|
con.gridx = 1;
|
||||||
|
genre = new JTextField(g);
|
||||||
|
genre.setPreferredSize(new Dimension(200, 20));
|
||||||
|
add(genre, con);
|
||||||
|
|
||||||
|
con.gridx = 0;
|
||||||
|
con.gridy++;
|
||||||
|
|
||||||
|
add(new JLabel("Comment:"), con);
|
||||||
|
con.gridx = 1;
|
||||||
|
comment = new JTextField(c);
|
||||||
|
comment.setPreferredSize(new Dimension(200, 20));
|
||||||
|
add(comment, con);
|
||||||
|
|
||||||
|
con.gridx = 0;
|
||||||
|
con.gridy++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGenre() {
|
||||||
|
return genre.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment.getText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTitle(String t) {
|
||||||
|
title.setText(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String a) {
|
||||||
|
author.setText(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGenre(String g) {
|
||||||
|
genre.setText(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComment(String c) {
|
||||||
|
comment.setText(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/uk/co/majenko/audiobookrecorder/Chapter.java
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
|
||||||
|
public class Chapter extends DefaultMutableTreeNode {
|
||||||
|
|
||||||
|
String name;
|
||||||
|
String id;
|
||||||
|
|
||||||
|
int preGap;
|
||||||
|
int postGap;
|
||||||
|
|
||||||
|
public Chapter(String i, String chaptername) {
|
||||||
|
super(chaptername);
|
||||||
|
|
||||||
|
id = i;
|
||||||
|
name = chaptername;
|
||||||
|
preGap = Options.getInteger("catenation.pre-chapter");
|
||||||
|
postGap = Options.getInteger("catenation.post-chapter");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sentence getLastSentence() {
|
||||||
|
DefaultMutableTreeNode ls = getLastLeaf();
|
||||||
|
if (ls instanceof Sentence) return (Sentence)ls;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renameChapter() {
|
||||||
|
String n = JOptionPane.showInputDialog(null, "Rename Chapter", name);
|
||||||
|
if (n != null) {
|
||||||
|
name = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserObject(Object o) {
|
||||||
|
if (o instanceof String) {
|
||||||
|
String so = (String)o;
|
||||||
|
name = so;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String n) {
|
||||||
|
name = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPreGap(int g) {
|
||||||
|
preGap = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPreGap() {
|
||||||
|
return preGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostGap(int g) {
|
||||||
|
postGap = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPostGap() {
|
||||||
|
return postGap;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/uk/co/majenko/audiobookrecorder/CustomTreeUI.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
import javax.swing.plaf.*;
|
||||||
|
import javax.swing.plaf.basic.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public class CustomTreeUI extends BasicTreeUI {
|
||||||
|
|
||||||
|
JScrollPane pane;
|
||||||
|
|
||||||
|
public CustomTreeUI(JScrollPane p) {
|
||||||
|
super();
|
||||||
|
pane = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AbstractLayoutCache.NodeDimensions createNodeDimensions() {
|
||||||
|
return new NodeDimensionsHandler() {
|
||||||
|
@Override
|
||||||
|
public Rectangle getNodeDimensions(
|
||||||
|
Object value, int row, int depth, boolean expanded,
|
||||||
|
Rectangle size) {
|
||||||
|
Rectangle dimensions = super.getNodeDimensions(value, row,
|
||||||
|
depth, expanded, size);
|
||||||
|
dimensions.width =
|
||||||
|
pane.getWidth() - getRowX(row, depth);
|
||||||
|
return dimensions;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintHorizontalLine(Graphics g, JComponent c,
|
||||||
|
int y, int left, int right) {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds,
|
||||||
|
Insets insets, TreePath path) {
|
||||||
|
// do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/uk/co/majenko/audiobookrecorder/FlashPanel.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class FlashPanel extends JPanel {
|
||||||
|
|
||||||
|
boolean flash = false;
|
||||||
|
boolean col = false;
|
||||||
|
|
||||||
|
java.util.Timer ticker;
|
||||||
|
|
||||||
|
public FlashPanel() {
|
||||||
|
super();
|
||||||
|
ticker = new java.util.Timer(true);
|
||||||
|
ticker.scheduleAtFixedRate(new TimerTask() {
|
||||||
|
public void run() {
|
||||||
|
if (flash) {
|
||||||
|
col = !col;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFlash(boolean f) {
|
||||||
|
flash = f;
|
||||||
|
|
||||||
|
for (Component o : getComponents()) {
|
||||||
|
((JComponent)o).setVisible(!f);
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
if (flash == false) {
|
||||||
|
super.paintComponent(g);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (col) {
|
||||||
|
g.setColor(Color.RED);
|
||||||
|
} else {
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
}
|
||||||
|
Dimension d = getSize();
|
||||||
|
g.fillRect(0, 0, d.width, d.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
38
src/uk/co/majenko/audiobookrecorder/Icons.java
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public class Icons {
|
||||||
|
static public ImageIcon book;
|
||||||
|
static public ImageIcon chapter;
|
||||||
|
static public ImageIcon sentence;
|
||||||
|
static public ImageIcon play;
|
||||||
|
static public ImageIcon playon;
|
||||||
|
static public ImageIcon stop;
|
||||||
|
static public ImageIcon record;
|
||||||
|
|
||||||
|
static public ImageIcon openBook;
|
||||||
|
static public ImageIcon newBook;
|
||||||
|
static public ImageIcon newChapter;
|
||||||
|
static public ImageIcon recordRoom;
|
||||||
|
static public ImageIcon save;
|
||||||
|
|
||||||
|
static void loadIcons() {
|
||||||
|
book = new ImageIcon(Icons.class.getResource("icons/book.png"));
|
||||||
|
chapter = new ImageIcon(Icons.class.getResource("icons/chapter.png"));
|
||||||
|
sentence = new ImageIcon(Icons.class.getResource("icons/sentence.png"));
|
||||||
|
play = new ImageIcon(Icons.class.getResource("icons/play.png"));
|
||||||
|
playon = new ImageIcon(Icons.class.getResource("icons/playon.png"));
|
||||||
|
stop = new ImageIcon(Icons.class.getResource("icons/stop.png"));
|
||||||
|
record = new ImageIcon(Icons.class.getResource("icons/record.png"));
|
||||||
|
|
||||||
|
openBook = new ImageIcon(Icons.class.getResource("icons/open.png"));
|
||||||
|
newBook = new ImageIcon(Icons.class.getResource("icons/new.png"));
|
||||||
|
newChapter = new ImageIcon(Icons.class.getResource("icons/new-chapter.png"));
|
||||||
|
recordRoom = new ImageIcon(Icons.class.getResource("icons/record-room.png"));
|
||||||
|
save = new ImageIcon(Icons.class.getResource("icons/save.png"));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
116
src/uk/co/majenko/audiobookrecorder/MainToolBar.java
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
public class MainToolBar extends JToolBar {
|
||||||
|
|
||||||
|
JButton newBook;
|
||||||
|
JButton openBook;
|
||||||
|
JButton saveBook;
|
||||||
|
JButton newChapter;
|
||||||
|
JButton recordRoomNoise;
|
||||||
|
JButton playSentence;
|
||||||
|
JButton playonSentence;
|
||||||
|
JButton recordSentence;
|
||||||
|
|
||||||
|
AudiobookRecorder root;
|
||||||
|
|
||||||
|
public MainToolBar(AudiobookRecorder r) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
root = r;
|
||||||
|
|
||||||
|
newBook = new JButton(Icons.newBook);
|
||||||
|
newBook.setToolTipText("New Book");
|
||||||
|
newBook.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
root.createNewBook();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(newBook);
|
||||||
|
|
||||||
|
openBook = new JButton(Icons.openBook);
|
||||||
|
openBook.setToolTipText("Open Book");
|
||||||
|
openBook.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
root.openBook();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(openBook);
|
||||||
|
|
||||||
|
saveBook = new JButton(Icons.save);
|
||||||
|
saveBook.setToolTipText("Save Book");
|
||||||
|
saveBook.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
root.saveBookStructure();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(saveBook);
|
||||||
|
|
||||||
|
addSeparator();
|
||||||
|
|
||||||
|
newChapter = new JButton(Icons.newChapter);
|
||||||
|
newChapter.setToolTipText("New Chapter");
|
||||||
|
newChapter.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
root.addChapter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(newChapter);
|
||||||
|
|
||||||
|
recordRoomNoise = new JButton(Icons.recordRoom);
|
||||||
|
recordRoomNoise.setToolTipText("Record Room Noise");
|
||||||
|
recordRoomNoise.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
root.recordRoomNoise();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(recordRoomNoise);
|
||||||
|
|
||||||
|
addSeparator();
|
||||||
|
|
||||||
|
playSentence = new JButton(Icons.play);
|
||||||
|
playSentence.setToolTipText("Play sentence");
|
||||||
|
playSentence.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
root.playSelectedSentence();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(playSentence);
|
||||||
|
|
||||||
|
playonSentence = new JButton(Icons.playon);
|
||||||
|
playonSentence.setToolTipText("Play from sentence");
|
||||||
|
add(playonSentence);
|
||||||
|
|
||||||
|
recordSentence = new JButton(Icons.record);
|
||||||
|
recordSentence.setToolTipText("Re-record sentence");
|
||||||
|
add(recordSentence);
|
||||||
|
|
||||||
|
setFloatable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableBook() {
|
||||||
|
newChapter.setEnabled(true);
|
||||||
|
recordRoomNoise.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableBook() {
|
||||||
|
newChapter.setEnabled(false);
|
||||||
|
recordRoomNoise.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableSentence() {
|
||||||
|
playSentence.setEnabled(true);
|
||||||
|
playonSentence.setEnabled(true);
|
||||||
|
recordSentence.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableSentence() {
|
||||||
|
playSentence.setEnabled(false);
|
||||||
|
playonSentence.setEnabled(false);
|
||||||
|
recordSentence.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
133
src/uk/co/majenko/audiobookrecorder/OpenBookPanel.java
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.table.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class OpenBookPanel extends JPanel {
|
||||||
|
|
||||||
|
JScrollPane scroll;
|
||||||
|
|
||||||
|
JTable table;
|
||||||
|
|
||||||
|
class BookInfo {
|
||||||
|
public String name;
|
||||||
|
public String author;
|
||||||
|
public String genre;
|
||||||
|
public String comment;
|
||||||
|
|
||||||
|
public BookInfo(String n, String a, String g, String c) {
|
||||||
|
name = n;
|
||||||
|
author = a;
|
||||||
|
genre = g;
|
||||||
|
comment = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
|
ArrayList<BookInfo> books;
|
||||||
|
|
||||||
|
public BookTableModel() {
|
||||||
|
super();
|
||||||
|
books = new ArrayList<BookInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRowCount() {
|
||||||
|
return books.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getColumnCount() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addBook(BookInfo b) {
|
||||||
|
books.add(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValueAt(int r, int c) {
|
||||||
|
if (c > 3) return null;
|
||||||
|
if (r > books.size()) return null;
|
||||||
|
BookInfo b = books.get(r);
|
||||||
|
switch (c) {
|
||||||
|
case 0: return b.name;
|
||||||
|
case 1: return b.author;
|
||||||
|
case 2: return b.genre;
|
||||||
|
case 4: return b.comment;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getColumnName(int i) {
|
||||||
|
switch(i) {
|
||||||
|
case 0: return "Name";
|
||||||
|
case 1: return "Author";
|
||||||
|
case 2: return "Genre";
|
||||||
|
case 3: return "Comment";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BookTableModel model;
|
||||||
|
|
||||||
|
public OpenBookPanel() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
model = new BookTableModel();
|
||||||
|
|
||||||
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
scroll = new JScrollPane();
|
||||||
|
add(scroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
File dir = new File(Options.get("path.storage"));
|
||||||
|
|
||||||
|
for (File b : dir.listFiles()) {
|
||||||
|
if (!b.isDirectory()) continue;
|
||||||
|
File xml = new File(b, "audiobook.abk");
|
||||||
|
if (xml.exists()) {
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.loadFromXML(new FileInputStream(xml));
|
||||||
|
|
||||||
|
BookInfo book = new BookInfo(
|
||||||
|
props.getProperty("book.name"),
|
||||||
|
props.getProperty("book.author"),
|
||||||
|
props.getProperty("book.genre"),
|
||||||
|
props.getProperty("book.comment")
|
||||||
|
);
|
||||||
|
|
||||||
|
model.addBook(book);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table = new JTable(model);
|
||||||
|
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
scroll.setViewportView(table);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getSelectedFile() {
|
||||||
|
int sel = table.getSelectedRow();
|
||||||
|
if (sel == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = (String)table.getValueAt(sel, 0);
|
||||||
|
File d = new File(Options.get("path.storage"), name);
|
||||||
|
File f = new File(d, "audiobook.abk");
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
359
src/uk/co/majenko/audiobookrecorder/Options.java
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.prefs.*;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public class Options extends JDialog {
|
||||||
|
|
||||||
|
GridBagConstraints constraint;
|
||||||
|
|
||||||
|
JComboBox<KVPair> mixerList;
|
||||||
|
JComboBox<KVPair> playbackList;
|
||||||
|
JComboBox<KVPair> channelList;
|
||||||
|
JComboBox<KVPair> rateList;
|
||||||
|
JTextField storageFolder;
|
||||||
|
JSpinner preChapterGap;
|
||||||
|
JSpinner postChapterGap;
|
||||||
|
JSpinner postSentenceGap;
|
||||||
|
|
||||||
|
|
||||||
|
static HashMap<String, String> defaultPrefs;
|
||||||
|
static Preferences prefs = null;
|
||||||
|
|
||||||
|
static class KVPair implements Comparable {
|
||||||
|
public String key;
|
||||||
|
public String value;
|
||||||
|
|
||||||
|
public KVPair(String k, String v) {
|
||||||
|
key = k;
|
||||||
|
value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(Object o) {
|
||||||
|
if (o instanceof KVPair) {
|
||||||
|
KVPair ko = (KVPair)o;
|
||||||
|
return key.compareTo(ko.key);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JComboBox<KVPair> addDropdown(String label, KVPair[] options, String def) {
|
||||||
|
JLabel l = new JLabel(label);
|
||||||
|
constraint.gridx = 0;
|
||||||
|
constraint.gridwidth = 1;
|
||||||
|
constraint.gridheight = 1;
|
||||||
|
constraint.anchor = GridBagConstraints.LINE_START;
|
||||||
|
add(l, constraint);
|
||||||
|
|
||||||
|
JComboBox<KVPair> o = new JComboBox<KVPair>(options);
|
||||||
|
constraint.gridx = 1;
|
||||||
|
add(o, constraint);
|
||||||
|
|
||||||
|
for (KVPair p : options) {
|
||||||
|
if (p.key.equals(def)) {
|
||||||
|
o.setSelectedItem(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constraint.gridy++;
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
JTextField addFilePath(String label, String path) {
|
||||||
|
JLabel l = new JLabel(label);
|
||||||
|
constraint.gridx = 0;
|
||||||
|
constraint.gridwidth = 1;
|
||||||
|
constraint.gridheight = 1;
|
||||||
|
constraint.anchor = GridBagConstraints.LINE_START;
|
||||||
|
add(l, constraint);
|
||||||
|
|
||||||
|
JPanel p = new JPanel();
|
||||||
|
p.setLayout(new BorderLayout());
|
||||||
|
JTextField a = new JTextField(path);
|
||||||
|
p.add(a, BorderLayout.CENTER);
|
||||||
|
JButton b = new JButton("...");
|
||||||
|
p.add(b, BorderLayout.EAST);
|
||||||
|
|
||||||
|
constraint.gridx = 1;
|
||||||
|
|
||||||
|
constraint.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
add(p, constraint);
|
||||||
|
|
||||||
|
constraint.fill = GridBagConstraints.NONE;
|
||||||
|
|
||||||
|
constraint.gridy++;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addSeparator() {
|
||||||
|
constraint.gridx = 0;
|
||||||
|
constraint.gridwidth = 2;
|
||||||
|
|
||||||
|
JPanel p = new JPanel();
|
||||||
|
p.setLayout(new BorderLayout());
|
||||||
|
|
||||||
|
JSeparator sep = new JSeparator(SwingConstants.HORIZONTAL);
|
||||||
|
sep.setPreferredSize(new Dimension(1, 1));
|
||||||
|
p.add(sep, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
constraint.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
constraint.insets = new Insets(10, 2, 10, 2);
|
||||||
|
add(p, constraint);
|
||||||
|
constraint.insets = new Insets(2, 2, 2, 2);
|
||||||
|
constraint.fill = GridBagConstraints.NONE;
|
||||||
|
constraint.gridwidth = 1;
|
||||||
|
constraint.gridy++;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSpinner addSpinner(String label, int min, int max, int step, int value, String suffix) {
|
||||||
|
JLabel l = new JLabel(label);
|
||||||
|
constraint.gridx = 0;
|
||||||
|
constraint.gridwidth = 1;
|
||||||
|
constraint.gridheight = 1;
|
||||||
|
constraint.anchor = GridBagConstraints.LINE_START;
|
||||||
|
add(l, constraint);
|
||||||
|
|
||||||
|
JPanel p = new JPanel();
|
||||||
|
p.setLayout(new BorderLayout());
|
||||||
|
JSpinner a = new JSpinner(new SteppedNumericSpinnerModel(min, max, step, value));
|
||||||
|
|
||||||
|
p.add(a, BorderLayout.CENTER);
|
||||||
|
JLabel b = new JLabel(suffix);
|
||||||
|
p.add(b, BorderLayout.EAST);
|
||||||
|
|
||||||
|
constraint.gridx = 1;
|
||||||
|
|
||||||
|
constraint.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
add(p, constraint);
|
||||||
|
|
||||||
|
constraint.fill = GridBagConstraints.NONE;
|
||||||
|
|
||||||
|
constraint.gridy++;
|
||||||
|
return a;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options(JFrame parent) {
|
||||||
|
loadPreferences(); // Just in case. It should do nothing.
|
||||||
|
|
||||||
|
setLayout(new GridBagLayout());
|
||||||
|
|
||||||
|
constraint = new GridBagConstraints();
|
||||||
|
|
||||||
|
constraint.gridx = 0;
|
||||||
|
constraint.gridy = 0;
|
||||||
|
constraint.gridwidth = 1;
|
||||||
|
constraint.gridheight = 1;
|
||||||
|
|
||||||
|
constraint.insets = new Insets(2, 2, 2, 2);
|
||||||
|
|
||||||
|
addSeparator();
|
||||||
|
|
||||||
|
mixerList = addDropdown("Recording device:", getMixerList(), get("audio.recording.device"));
|
||||||
|
channelList = addDropdown("Channels:", getChannelCountList(), get("audio.recording.channels"));
|
||||||
|
rateList = addDropdown("Sample rate:", getSampleRateList(), get("audio.recording.samplerate"));
|
||||||
|
|
||||||
|
addSeparator();
|
||||||
|
|
||||||
|
playbackList = addDropdown("Playback device:", getMixerList(), get("audio.playback.device"));
|
||||||
|
addSeparator();
|
||||||
|
storageFolder = addFilePath("Storage folder:", get("path.storage"));
|
||||||
|
|
||||||
|
addSeparator();
|
||||||
|
|
||||||
|
preChapterGap = addSpinner("Default pre-chapter gap:", 0, 5000, 100, getInteger("catenation.pre-chapter"), "ms");
|
||||||
|
postChapterGap = addSpinner("Default post-chapter gap:", 0, 5000, 100, getInteger("catenation.post-chapter"), "ms");
|
||||||
|
postSentenceGap = addSpinner("Default post-sentence gap:", 0, 5000, 100, getInteger("catenation.post-sentence"), "ms");
|
||||||
|
|
||||||
|
addSeparator();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setTitle("Options");
|
||||||
|
|
||||||
|
setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
|
||||||
|
|
||||||
|
constraint.gridx = 1;
|
||||||
|
constraint.gridwidth = 1;
|
||||||
|
constraint.gridheight = 1;
|
||||||
|
|
||||||
|
JPanel box = new JPanel();
|
||||||
|
|
||||||
|
JButton ok = new JButton("OK");
|
||||||
|
|
||||||
|
ok.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
storePreferences();
|
||||||
|
Options.this.dispatchEvent(new WindowEvent(Options.this, WindowEvent.WINDOW_CLOSING));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
box.add(ok);
|
||||||
|
|
||||||
|
JButton cancel = new JButton("Cancel");
|
||||||
|
cancel.addActionListener(new ActionListener() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
Options.this.dispatchEvent(new WindowEvent(Options.this, WindowEvent.WINDOW_CLOSING));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
box.add(cancel);
|
||||||
|
|
||||||
|
constraint.anchor = GridBagConstraints.LINE_END;
|
||||||
|
|
||||||
|
add(box, constraint);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static KVPair[] getMixerList() {
|
||||||
|
TreeSet<KVPair> list = new TreeSet<KVPair>();
|
||||||
|
|
||||||
|
Mixer.Info[] info = AudioSystem.getMixerInfo();
|
||||||
|
for (Mixer.Info i : info) {
|
||||||
|
KVPair p = new KVPair(i.getName(), i.getDescription());
|
||||||
|
list.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.toArray(new KVPair[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static KVPair[] getChannelCountList() {
|
||||||
|
KVPair[] l = new KVPair[2];
|
||||||
|
l[0] = new KVPair("1", "Mono");
|
||||||
|
l[1] = new KVPair("2", "Stereo");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
static KVPair[] getSampleRateList() {
|
||||||
|
KVPair[] l = new KVPair[2];
|
||||||
|
l[0] = new KVPair("44100", "44100");
|
||||||
|
l[1] = new KVPair("48000", "48000");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void loadPreferences() {
|
||||||
|
|
||||||
|
defaultPrefs = new HashMap<String, String>();
|
||||||
|
|
||||||
|
KVPair[] mixers = getMixerList();
|
||||||
|
|
||||||
|
defaultPrefs.put("audio.recording.device", mixers[0].key);
|
||||||
|
defaultPrefs.put("audio.recording.channels", "2");
|
||||||
|
defaultPrefs.put("audio.recording.samplerate", "48000");
|
||||||
|
defaultPrefs.put("audio.playback.device", mixers[0].key);
|
||||||
|
defaultPrefs.put("path.storage", (new File(System.getProperty("user.home"), "Recordings")).toString());
|
||||||
|
|
||||||
|
defaultPrefs.put("catenation.pre-chapter", "2000");
|
||||||
|
defaultPrefs.put("catenation.post-chapter", "2000");
|
||||||
|
defaultPrefs.put("catenation.post-sentence", "1000");
|
||||||
|
|
||||||
|
if (prefs == null) {
|
||||||
|
prefs = Preferences.userNodeForPackage(AudiobookRecorder.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void savePreferences() {
|
||||||
|
if (prefs != null) {
|
||||||
|
try {
|
||||||
|
prefs.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String get(String key) {
|
||||||
|
if (prefs == null) return null;
|
||||||
|
String def = defaultPrefs.get(key);
|
||||||
|
String v = prefs.get(key, def);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Integer getInteger(String key) {
|
||||||
|
try {
|
||||||
|
Integer i = Integer.parseInt(get(key));
|
||||||
|
return i;
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(String key, String value) {
|
||||||
|
if (prefs == null) return;
|
||||||
|
prefs.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void storePreferences() {
|
||||||
|
set("audio.recording.device", ((KVPair)mixerList.getSelectedItem()).key);
|
||||||
|
set("audio.recording.channels", ((KVPair)channelList.getSelectedItem()).key);
|
||||||
|
set("audio.recording.samplerate", ((KVPair)rateList.getSelectedItem()).key);
|
||||||
|
set("audio.playback.device", ((KVPair)playbackList.getSelectedItem()).key);
|
||||||
|
set("path.storage", storageFolder.getText());
|
||||||
|
set("catenation.pre-chapter", "" + preChapterGap.getValue());
|
||||||
|
set("catenation.post-chapter", "" + preChapterGap.getValue());
|
||||||
|
set("catenation.post-sentence", "" + preChapterGap.getValue());
|
||||||
|
|
||||||
|
savePreferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AudioFormat getAudioFormat() {
|
||||||
|
String sampleRate = get("audio.recording.samplerate");
|
||||||
|
String channels = get("audio.recording.channels");
|
||||||
|
|
||||||
|
float sr = 48000f;
|
||||||
|
int chans = 2;
|
||||||
|
|
||||||
|
try {
|
||||||
|
sr = Float.parseFloat(sampleRate);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sr = 48000f;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
chans = Integer.parseInt(channels);
|
||||||
|
} catch (Exception e) {
|
||||||
|
chans = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioFormat af = new AudioFormat(sr, 16, chans, true, false);
|
||||||
|
return af;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mixer.Info getMixerByName(String name) {
|
||||||
|
|
||||||
|
Mixer.Info[] mixers = AudioSystem.getMixerInfo();
|
||||||
|
for (Mixer.Info info : mixers) {
|
||||||
|
if (info.getName().equals(name)) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mixer.Info getRecordingMixer() {
|
||||||
|
return getMixerByName(get("audio.recording.device"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mixer.Info getPlaybackMixer() {
|
||||||
|
return getMixerByName(get("audio.playback.device"));
|
||||||
|
}
|
||||||
|
}
|
||||||
335
src/uk/co/majenko/audiobookrecorder/Sentence.java
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
public class Sentence extends DefaultMutableTreeNode {
|
||||||
|
|
||||||
|
String text;
|
||||||
|
String id;
|
||||||
|
int postGap;
|
||||||
|
int startOffset = 0;
|
||||||
|
int endOffset = 0;
|
||||||
|
|
||||||
|
int sampleSize = -1;
|
||||||
|
|
||||||
|
boolean isSilence = false;
|
||||||
|
|
||||||
|
boolean recording;
|
||||||
|
|
||||||
|
TargetDataLine line;
|
||||||
|
AudioInputStream inputStream;
|
||||||
|
|
||||||
|
Thread recordingThread = null;
|
||||||
|
|
||||||
|
|
||||||
|
public Sentence() {
|
||||||
|
super("");
|
||||||
|
id = UUID.randomUUID().toString();
|
||||||
|
text = id;
|
||||||
|
setUserObject(text);
|
||||||
|
postGap = Options.getInteger("catenation.post-sentence");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sentence(String i, String t) {
|
||||||
|
super("");
|
||||||
|
id = i;
|
||||||
|
text = t;
|
||||||
|
setUserObject(text);
|
||||||
|
postGap = Options.getInteger("catenation.post-sentence");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean startRecording() {
|
||||||
|
AudioFormat format = new AudioFormat(
|
||||||
|
Options.getInteger("audio.recording.samplerate"),
|
||||||
|
16,
|
||||||
|
Options.getInteger("audio.recording.channels"),
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (format == null) {
|
||||||
|
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mixer.Info mixer = Options.getRecordingMixer();
|
||||||
|
|
||||||
|
line = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
line = AudioSystem.getTargetDataLine(format, mixer);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == null) {
|
||||||
|
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Sample format not supported", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputStream = new AudioInputStream(line);
|
||||||
|
|
||||||
|
try {
|
||||||
|
line.open();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
line.start();
|
||||||
|
|
||||||
|
File audioFile = new File(AudiobookRecorder.window.getBookFolder(), id + ".wav");
|
||||||
|
|
||||||
|
recordingThread = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
AudioSystem.write(inputStream, AudioFileFormat.Type.WAVE, audioFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
recordingThread.setDaemon(true);
|
||||||
|
|
||||||
|
recordingThread.start();
|
||||||
|
|
||||||
|
recording = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopRecording() {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
line.stop();
|
||||||
|
line.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
recording = false;
|
||||||
|
|
||||||
|
if (!id.equals("room-noise")) {
|
||||||
|
autoTrimSample();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void autoTrimSample() {
|
||||||
|
int[] samples = getAudioData();
|
||||||
|
int noiseFloor = AudiobookRecorder.window.getNoiseFloor();
|
||||||
|
|
||||||
|
isSilence = false;
|
||||||
|
// Find start
|
||||||
|
for (int i = 0; i < samples.length; i++) {
|
||||||
|
startOffset = i;
|
||||||
|
if (Math.abs(samples[i]) > noiseFloor) {
|
||||||
|
startOffset --;
|
||||||
|
if (startOffset < 0) startOffset = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startOffset >= samples.length-1) { // Failed! Silence?
|
||||||
|
isSilence = true;
|
||||||
|
startOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = samples.length-1; i >= 0; i--) {
|
||||||
|
endOffset = i;
|
||||||
|
if (Math.abs(samples[i]) > noiseFloor) {
|
||||||
|
endOffset ++;
|
||||||
|
if (endOffset >= samples.length-1) endOffset = samples.length-1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (endOffset <= 0) {
|
||||||
|
isSilence = true;
|
||||||
|
endOffset = samples.length-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String t) {
|
||||||
|
text = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return new File(AudiobookRecorder.window.getBookFolder(), id + ".wav");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void editText() {
|
||||||
|
String t = JOptionPane.showInputDialog(null, "Edit Text", text);
|
||||||
|
|
||||||
|
if (t != null) {
|
||||||
|
text = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRecording() {
|
||||||
|
return recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserObject(Object o) {
|
||||||
|
if (o instanceof String) {
|
||||||
|
String so = (String)o;
|
||||||
|
text = so;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPostGap() {
|
||||||
|
return postGap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostGap(int g) {
|
||||||
|
postGap = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteFiles() {
|
||||||
|
File audioFile = new File(AudiobookRecorder.window.getBookFolder(), id + ".wav");
|
||||||
|
if (audioFile.exists()) {
|
||||||
|
audioFile.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getAudioData() {
|
||||||
|
File f = getFile();
|
||||||
|
try {
|
||||||
|
AudioInputStream s = AudioSystem.getAudioInputStream(f);
|
||||||
|
AudioFormat format = s.getFormat();
|
||||||
|
|
||||||
|
long len = s.getFrameLength();
|
||||||
|
int frameSize = format.getFrameSize();
|
||||||
|
int chans = format.getChannels();
|
||||||
|
int bytes = frameSize / chans;
|
||||||
|
|
||||||
|
byte[] frame = new byte[frameSize];
|
||||||
|
int[] samples = new int[(int)len];
|
||||||
|
|
||||||
|
if (bytes != 2) return null;
|
||||||
|
|
||||||
|
for (long fno = 0; fno < len; fno++) {
|
||||||
|
s.read(frame);
|
||||||
|
int sample = 0;
|
||||||
|
if (chans == 2) { // Stereo
|
||||||
|
int left = (frame[1] << 8) | frame[0];
|
||||||
|
int right = (frame[3] << 8) | frame[2];
|
||||||
|
sample = (left + right) / 2;
|
||||||
|
} else {
|
||||||
|
sample = (frame[1] << 8) | frame[0];
|
||||||
|
}
|
||||||
|
samples[(int)fno] = sample;
|
||||||
|
}
|
||||||
|
s.close();
|
||||||
|
sampleSize = samples.length;
|
||||||
|
return samples;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartOffset() {
|
||||||
|
return startOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStartOffset(int o) {
|
||||||
|
startOffset = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEndOffset() {
|
||||||
|
return endOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEndOffset(int o) {
|
||||||
|
endOffset = o;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSampleSize() {
|
||||||
|
if (sampleSize == -1) {
|
||||||
|
getAudioData();
|
||||||
|
}
|
||||||
|
return sampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the audio file as an AudioInputStream and automatically
|
||||||
|
// skip the first startOffset frames.
|
||||||
|
public AudioInputStream getAudioStream() {
|
||||||
|
File f = getFile();
|
||||||
|
try {
|
||||||
|
AudioInputStream s = AudioSystem.getAudioInputStream(f);
|
||||||
|
AudioFormat format = s.getFormat();
|
||||||
|
long len = s.getFrameLength();
|
||||||
|
int frameSize = format.getFrameSize();
|
||||||
|
|
||||||
|
s.skip(frameSize * startOffset);
|
||||||
|
|
||||||
|
return s;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AudioFormat getAudioFormat() {
|
||||||
|
File f = getFile();
|
||||||
|
try {
|
||||||
|
AudioInputStream s = AudioSystem.getAudioInputStream(f);
|
||||||
|
AudioFormat format = s.getFormat();
|
||||||
|
return format;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void play() {
|
||||||
|
File f = getFile();
|
||||||
|
try {
|
||||||
|
AudioInputStream s = AudioSystem.getAudioInputStream(f);
|
||||||
|
AudioFormat format = s.getFormat();
|
||||||
|
long len = s.getFrameLength();
|
||||||
|
int frameSize = format.getFrameSize();
|
||||||
|
|
||||||
|
int pos = startOffset * frameSize;
|
||||||
|
|
||||||
|
SourceDataLine play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer());
|
||||||
|
play.open(format);
|
||||||
|
|
||||||
|
play.start();
|
||||||
|
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
|
||||||
|
s.skip(pos);
|
||||||
|
|
||||||
|
while (pos < endOffset * frameSize) {
|
||||||
|
int nr = s.read(buffer);
|
||||||
|
pos += nr;
|
||||||
|
|
||||||
|
play.write(buffer, 0, nr);
|
||||||
|
};
|
||||||
|
play.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
|
||||||
|
public class SteppedNumericSpinnerModel implements SpinnerModel {
|
||||||
|
int min;
|
||||||
|
int max;
|
||||||
|
int step;
|
||||||
|
int value;
|
||||||
|
|
||||||
|
ArrayList<ChangeListener> listeners;
|
||||||
|
|
||||||
|
public SteppedNumericSpinnerModel(int amin, int amax, int astep, int avalue) {
|
||||||
|
min = amin;
|
||||||
|
max = amax;
|
||||||
|
step = astep;
|
||||||
|
value = avalue;
|
||||||
|
|
||||||
|
listeners = new ArrayList<ChangeListener>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getNextValue() {
|
||||||
|
Integer v = value;
|
||||||
|
v += step;
|
||||||
|
if (v > max) return null;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getPreviousValue() {
|
||||||
|
Integer v = value;
|
||||||
|
v -= step;
|
||||||
|
if (v < min) return null;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getValue() {
|
||||||
|
Integer v = value;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(Object v) {
|
||||||
|
if (v instanceof Integer) {
|
||||||
|
Integer i = (Integer)v;
|
||||||
|
value = i;
|
||||||
|
ChangeEvent e = new ChangeEvent(this);
|
||||||
|
for (ChangeListener l : listeners) {
|
||||||
|
l.stateChanged(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChangeListener(ChangeListener l) {
|
||||||
|
listeners.add(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeChangeListener(ChangeListener l) {
|
||||||
|
listeners.remove(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinimum(int i) {
|
||||||
|
min = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMaximum(int i) {
|
||||||
|
max = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaximum() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinimum() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
118
src/uk/co/majenko/audiobookrecorder/Waveform.java
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package uk.co.majenko.audiobookrecorder;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.io.*;
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
public class Waveform extends JPanel {
|
||||||
|
|
||||||
|
int[] samples = null;
|
||||||
|
int leftMarker = 0;
|
||||||
|
int rightMarker = 0;
|
||||||
|
|
||||||
|
public Waveform() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void paintComponent(Graphics g) {
|
||||||
|
|
||||||
|
Dimension size = getSize();
|
||||||
|
|
||||||
|
int w = size.width;
|
||||||
|
int h = size.height;
|
||||||
|
|
||||||
|
g.setColor(Color.BLACK);
|
||||||
|
g.fillRect(0, 0, w, h);
|
||||||
|
|
||||||
|
g.setColor(Color.GREEN);
|
||||||
|
g.drawRect(0, 0, w, h);
|
||||||
|
g.drawLine(0, h/2, w, h/2);
|
||||||
|
|
||||||
|
g.setColor(new Color(0, 150, 0));
|
||||||
|
for (int x = 0; x < w; x += w/10) {
|
||||||
|
g.drawLine(x, 0, x, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < w; x += 4) {
|
||||||
|
g.drawLine(x, h/4, x, h/4);
|
||||||
|
g.drawLine(x, h/4*3, x, h/4*3);
|
||||||
|
}
|
||||||
|
|
||||||
|
int scale = 32768/(h/2);
|
||||||
|
|
||||||
|
if (samples != null) {
|
||||||
|
|
||||||
|
int num = samples.length;
|
||||||
|
int step = num / w;
|
||||||
|
|
||||||
|
for (int n = 0; n < w; n++) {
|
||||||
|
int hcnt = 0;
|
||||||
|
long have = 0;
|
||||||
|
int hmax = 0;
|
||||||
|
|
||||||
|
int lcnt = 0;
|
||||||
|
int lave = 0;
|
||||||
|
int lmax = 0;
|
||||||
|
|
||||||
|
for (int o = 0; o < step; o++) {
|
||||||
|
int sample = samples[(n * step) + o];
|
||||||
|
if (sample >= 0) {
|
||||||
|
have += sample;
|
||||||
|
hcnt++;
|
||||||
|
if (sample > hmax) hmax = sample;
|
||||||
|
} else {
|
||||||
|
sample = Math.abs(sample);
|
||||||
|
lave += sample;
|
||||||
|
lcnt++;
|
||||||
|
if (sample > lmax) lmax = sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hcnt > 0) have /= hcnt;
|
||||||
|
if (lcnt > 0) lave /= lcnt;
|
||||||
|
|
||||||
|
hmax /= scale;
|
||||||
|
lmax /= scale;
|
||||||
|
have /= scale;
|
||||||
|
lave /= scale;
|
||||||
|
|
||||||
|
g.setColor(new Color(0, 0, 100));
|
||||||
|
g.drawLine(n, h/2 + lmax, n, h/2 - hmax);
|
||||||
|
g.setColor(new Color(0, 0, 200));
|
||||||
|
g.drawLine(n, h/2 + (int)lave, n, h/2 - (int)have);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor(new Color(255, 0, 0));
|
||||||
|
g.drawLine(leftMarker/step, 0, leftMarker/step, h);
|
||||||
|
g.drawLine(rightMarker/step, 0, rightMarker/step, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMarkers(int l, int r) {
|
||||||
|
leftMarker = l;
|
||||||
|
rightMarker = r;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLeftMarker(int l) {
|
||||||
|
leftMarker = l;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRightMarker(int r) {
|
||||||
|
rightMarker = r;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearData() {
|
||||||
|
samples = null;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(int[] s) {
|
||||||
|
samples = s;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||