Initial import
This commit is contained in:
884
src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java
Normal file
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
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
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
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
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
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
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
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
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
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
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
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user