diff --git a/deps/jave-1.0.2.jar b/deps/jave-1.0.2.jar new file mode 100644 index 0000000..a00a3db Binary files /dev/null and b/deps/jave-1.0.2.jar differ diff --git a/deps/mp3agic-0.9.1.jar b/deps/mp3agic-0.9.1.jar new file mode 100644 index 0000000..793a87b Binary files /dev/null and b/deps/mp3agic-0.9.1.jar differ diff --git a/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java b/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java index dad1da9..4014a21 100644 --- a/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java +++ b/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java @@ -11,6 +11,8 @@ import java.lang.reflect.*; import java.util.*; import java.util.prefs.*; import java.io.*; +import it.sauronsoftware.jave.*; +import com.mpatric.mp3agic.*; public class AudiobookRecorder extends JFrame { @@ -34,7 +36,7 @@ public class AudiobookRecorder extends JFrame { JPanel statusBar; - JLabel statusFormat; + JLabel statusLabel; JScrollPane mainScroll; @@ -57,6 +59,8 @@ public class AudiobookRecorder extends JFrame { Thread playingThread = null; + Random rng = new Random(); + public static AudiobookRecorder window; void buildToolbar(Container ob) { @@ -78,7 +82,18 @@ public class AudiobookRecorder extends JFrame { } }); fileOpenBook = new JMenuItem("Open Book..."); + fileOpenBook.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + openBook(); + } + }); fileExit = new JMenuItem("Exit"); + fileExit.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + saveBookStructure(); + System.exit(0); + } + }); fileMenu.add(fileNewBook); fileMenu.add(fileOpenBook); @@ -91,7 +106,18 @@ public class AudiobookRecorder extends JFrame { bookMenu = new JMenu("Book"); bookNewChapter = new JMenuItem("New Chapter"); - bookExportAudio = new JMenuItem("Export Audio..."); + bookNewChapter.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + addChapter(); + } + }); + + bookExportAudio = new JMenuItem("Export Audio"); + bookExportAudio.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + exportAudio(); + } + }); bookMenu.add(bookNewChapter); bookMenu.add(bookExportAudio); @@ -158,6 +184,7 @@ public class AudiobookRecorder extends JFrame { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { + saveBookStructure(); System.exit(0); } }); @@ -336,8 +363,8 @@ public class AudiobookRecorder extends JFrame { statusBar = new JPanel(); add(statusBar, BorderLayout.SOUTH); - statusFormat = new JLabel(Options.getAudioFormat().toString()); - statusBar.add(statusFormat); + statusLabel = new JLabel("Noise floor: " + getNoiseFloor()); + statusBar.add(statusLabel); buildToolbar(centralPanel); @@ -474,6 +501,19 @@ public class AudiobookRecorder extends JFrame { } }); + process.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JMenuObject o = (JMenuObject)e.getSource(); + Sentence s = (Sentence)o.getObject(); + s.autoTrimSample(); + sampleWaveform.setData(s.getAudioData()); + sampleWaveform.setMarkers(s.getStartOffset(), s.getEndOffset()); + startOffset.setValue(s.getStartOffset()); + endOffset.setValue(s.getEndOffset()); + postSentenceGap.setValue(s.getPostGap()); + } + }); + menu.add(del); menu.add(edit); menu.add(process); @@ -554,6 +594,7 @@ public class AudiobookRecorder extends JFrame { toolBar.enableBook(); toolBar.enableSentence(); + toolBar.disableStop(); centralPanel.setFlash(false); recording = null; @@ -594,7 +635,11 @@ public class AudiobookRecorder extends JFrame { if (s != null) { bookTree.setSelectionPath(new TreePath(s.getPath())); bookTree.scrollPathToVisible(new TreePath(s.getPath())); + } else { + sampleWaveform.clearData(); } + selectedSentence = s; + saveBookStructure(); } public void addChapter() { @@ -607,6 +652,8 @@ public class AudiobookRecorder extends JFrame { @SuppressWarnings("unchecked") public void saveBookStructure() { + if (book == null) return; + File bookRoot = new File(Options.get("path.storage"), book.getName()); if (!bookRoot.exists()) { bookRoot.mkdirs(); @@ -694,12 +741,14 @@ public class AudiobookRecorder extends JFrame { ((SteppedNumericSpinnerModel)endOffset.getModel()).setMaximum(samples); toolBar.enableSentence(); + toolBar.disableStop(); } else { selectedSentence = null; toolBar.disableSentence(); sampleWaveform.clearData(); startOffset.setValue(0); endOffset.setValue(0); + toolBar.disableStop(); postSentenceGap.setValue(0); } } @@ -783,11 +832,11 @@ public class AudiobookRecorder extends JFrame { bookTree.expandPath(new TreePath(book.getPath())); toolBar.enableBook(); + statusLabel.setText("Noise floor: " + getNoiseFloor()); } public void openBook() { - OpenBookPanel info = new OpenBookPanel(); int r = JOptionPane.showConfirmDialog(this, info, "Open Book", JOptionPane.OK_CANCEL_OPTION); if (r == JOptionPane.OK_OPTION) { @@ -831,6 +880,7 @@ public class AudiobookRecorder extends JFrame { } public int getNoiseFloor() { + if (roomNoise == null) return 0; int[] samples = roomNoise.getAudioData(); if (samples == null) { return 0; @@ -855,10 +905,10 @@ public class AudiobookRecorder extends JFrame { ticker.schedule(new TimerTask() { public void run() { roomNoise.stopRecording(); - centralPanel.setFlash(false); + centralPanel.setFlash(false); + statusLabel.setText("Noise floor: " + getNoiseFloor()); } }, 5000); // 5 seconds of recording - } } @@ -869,11 +919,16 @@ public class AudiobookRecorder extends JFrame { playing = selectedSentence; + toolBar.disableSentence(); + toolBar.enableStop(); + playingThread = new Thread(new Runnable() { public void run() { playing.play(); playing = null; + toolBar.enableSentence(); + toolBar.disableStop(); } }); @@ -881,4 +936,202 @@ public class AudiobookRecorder extends JFrame { playingThread.start(); } + + @SuppressWarnings("unchecked") + public void exportAudio() { + + try { + File bookRoot = new File(Options.get("path.storage"), book.getName()); + if (!bookRoot.exists()) { + bookRoot.mkdirs(); + } + + File export = new File(bookRoot, "export"); + if (!export.exists()) { + export.mkdirs(); + } + + Encoder encoder = new Encoder(); + EncodingAttributes attributes = new EncodingAttributes(); + + AudioAttributes audioAttributes = new AudioAttributes(); + audioAttributes.setCodec("libmp3lame"); + audioAttributes.setBitRate(new Integer(256000)); + audioAttributes.setSamplingRate(new Integer(44100)); + audioAttributes.setChannels(new Integer(2)); + + attributes.setFormat("mp3"); + attributes.setAudioAttributes(audioAttributes); + + + AudioFormat format = roomNoise.getAudioFormat(); + byte[] data; + + for (Enumeration o = book.children(); o.hasMoreElements();) { + + int fullLength = 0; + + Chapter c = o.nextElement(); + + String name = c.getName(); + + File exportFile = new File(export, name + ".wax"); + File wavFile = new File(export, name + ".wav"); + File mp3File = new File(export, name + "-untagged.mp3"); + File taggedFile = new File(export, name + ".mp3"); + + FileOutputStream fos = new FileOutputStream(exportFile); + + data = getRoomNoise(s2i(Options.get("catenation.pre-chapter"))); + fullLength += data.length; + fos.write(data); + + for (Enumeration s = c.children(); s.hasMoreElements();) { + Sentence snt = s.nextElement(); + data = snt.getRawAudioData(); + + fullLength += data.length; + fos.write(data); + + if (s.hasMoreElements()) { + data = getRoomNoise(snt.getPostGap()); + } else { + data = getRoomNoise(s2i(Options.get("catenation.post-chapter"))); + } + fullLength += data.length; + fos.write(data); + } + fos.close(); + + FileInputStream fis = new FileInputStream(exportFile); + AudioInputStream ais = new AudioInputStream(fis, format, fullLength); + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, wavFile); + fis.close(); + exportFile.delete(); + + encoder.encode(wavFile, mp3File, attributes); + Mp3File id3 = new Mp3File(mp3File); + + ID3v2 tags = new ID3v24Tag(); + id3.setId3v2Tag(tags); + + tags.setTrack(Integer.toString(s2i(c.getId()) - 0)); + tags.setTitle(c.getName()); + tags.setArtist(book.getAuthor()); + +// ID3v2TextFrameData g = new ID3v2TextFrameData(false, new EncodedText(book.getGenre())); +// tags.addFrame(tags.createFrame("TCON", g.toBytes(), true)); + + tags.setComment(book.getComment()); + + id3.save(taggedFile.getAbsolutePath()); + + mp3File.delete(); + wavFile.delete(); + + } + + JOptionPane.showMessageDialog(this, "Book exported.", "Done", JOptionPane.INFORMATION_MESSAGE); + + + } catch (Exception e) { + e.printStackTrace(); + } + } + + + public void playFromSelectedSentence() { + if (selectedSentence == null) return; + if (playing != null) return; + playing = selectedSentence; + toolBar.disableSentence(); + toolBar.enableStop(); + + playingThread = new Thread(new Runnable() { + public void run() { + Sentence s = playing; + byte[] data; + + try { + + AudioFormat format = s.getAudioFormat(); + SourceDataLine play = AudioSystem.getSourceDataLine(format, Options.getPlaybackMixer()); + play.open(format); + play.start(); + + while (playing != null) { + DefaultMutableTreeNode prev = s.getPreviousSibling(); + boolean first = false; + if (prev == null) { + first = true; + } else if (!(prev instanceof Sentence)) { + first = true; + } + if (first) { + data = getRoomNoise(s2i(Options.get("catenation.pre-chapter"))); + play.write(data, 0, data.length); + } + data = s.getRawAudioData(); + play.write(data, 0, data.length); + + DefaultMutableTreeNode next = s.getNextSibling(); + boolean last = false; + if (next == null) { + last = true; + } else if (!(next instanceof Sentence)) { + last = true; + } + + if (last) { + data = getRoomNoise(s2i(Options.get("catenation.post-chapter"))); + play.write(data, 0, data.length); + playing = null; + } else { + data = getRoomNoise(s.getPostGap()); + play.write(data, 0, data.length); + } + s = (Sentence)next; + } + } catch (Exception e) { + e.printStackTrace(); + playing = null; + } + toolBar.enableSentence(); + toolBar.disableStop(); + } + }); + + playingThread.setDaemon(true); + playingThread.start(); + } + + + public byte[] getRoomNoise(int ms) { + + if (roomNoise == null) return null; + + int len = roomNoise.getSampleSize(); + if (len == 0) return null; + + AudioFormat f = roomNoise.getAudioFormat(); + int frameSize = f.getFrameSize(); + + float sr = f.getSampleRate(); + + int samples = (int)(ms * (sr / 1000f)); + + int start = rng.nextInt(len - samples); + int end = start + samples; + + roomNoise.setStartOffset(start); + roomNoise.setEndOffset(end); + + byte[] data = roomNoise.getRawAudioData(); + + return data; + } + + public void stopPlaying() { + playing = null; + } } diff --git a/src/uk/co/majenko/audiobookrecorder/MainToolBar.java b/src/uk/co/majenko/audiobookrecorder/MainToolBar.java index 2f827e8..ffcd878 100644 --- a/src/uk/co/majenko/audiobookrecorder/MainToolBar.java +++ b/src/uk/co/majenko/audiobookrecorder/MainToolBar.java @@ -15,6 +15,7 @@ public class MainToolBar extends JToolBar { JButton playSentence; JButton playonSentence; JButton recordSentence; + JButton stopPlaying; AudiobookRecorder root; @@ -83,12 +84,26 @@ public class MainToolBar extends JToolBar { playonSentence = new JButton(Icons.playon); playonSentence.setToolTipText("Play from sentence"); + playonSentence.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + root.playFromSelectedSentence(); + } + }); add(playonSentence); recordSentence = new JButton(Icons.record); recordSentence.setToolTipText("Re-record sentence"); add(recordSentence); + stopPlaying = new JButton(Icons.stop); + stopPlaying.setToolTipText("Stop playing / recording"); + stopPlaying.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + root.stopPlaying(); + } + }); + add(stopPlaying); + setFloatable(false); } @@ -113,4 +128,12 @@ public class MainToolBar extends JToolBar { playonSentence.setEnabled(false); recordSentence.setEnabled(false); } + + public void enableStop() { + stopPlaying.setEnabled(true); + } + + public void disableStop() { + stopPlaying.setEnabled(false); + } } diff --git a/src/uk/co/majenko/audiobookrecorder/Options.java b/src/uk/co/majenko/audiobookrecorder/Options.java index 5e4cd01..a6c2b0d 100644 --- a/src/uk/co/majenko/audiobookrecorder/Options.java +++ b/src/uk/co/majenko/audiobookrecorder/Options.java @@ -308,8 +308,8 @@ public class Options extends JDialog { 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()); + set("catenation.post-chapter", "" + postChapterGap.getValue()); + set("catenation.post-sentence", "" + postSentenceGap.getValue()); savePreferences(); } diff --git a/src/uk/co/majenko/audiobookrecorder/Sentence.java b/src/uk/co/majenko/audiobookrecorder/Sentence.java index 8b773f8..34cb3b1 100644 --- a/src/uk/co/majenko/audiobookrecorder/Sentence.java +++ b/src/uk/co/majenko/audiobookrecorder/Sentence.java @@ -86,7 +86,7 @@ public class Sentence extends DefaultMutableTreeNode { line.start(); - File audioFile = new File(AudiobookRecorder.window.getBookFolder(), id + ".wav"); + File audioFile = getFile(); recordingThread = new Thread(new Runnable() { public void run() { @@ -169,7 +169,11 @@ public class Sentence extends DefaultMutableTreeNode { } public File getFile() { - return new File(AudiobookRecorder.window.getBookFolder(), id + ".wav"); + File b = new File(AudiobookRecorder.window.getBookFolder(), "files"); + if (!b.exists()) { + b.mkdirs(); + } + return new File(b, id + ".wav"); } public void editText() { @@ -205,7 +209,7 @@ public class Sentence extends DefaultMutableTreeNode { } public void deleteFiles() { - File audioFile = new File(AudiobookRecorder.window.getBookFolder(), id + ".wav"); + File audioFile = getFile(); if (audioFile.exists()) { audioFile.delete(); } @@ -243,7 +247,6 @@ public class Sentence extends DefaultMutableTreeNode { sampleSize = samples.length; return samples; } catch (Exception e) { - e.printStackTrace(); } return null; } @@ -332,4 +335,25 @@ public class Sentence extends DefaultMutableTreeNode { e.printStackTrace(); } } + + public byte[] getRawAudioData() { + File f = getFile(); + try { + AudioInputStream s = AudioSystem.getAudioInputStream(f); + AudioFormat format = s.getFormat(); + int frameSize = format.getFrameSize(); + int length = endOffset - startOffset; + byte[] data = new byte[length * frameSize]; + + s.skip(startOffset * frameSize); + + s.read(data); + + return data; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + }