diff --git a/.gitignore b/.gitignore index a37537e..ec94f61 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,6 @@ ._* bin AudiobookRecorder.jar -AudiobookRecorder.dmg -AudiobookRecorder.exe -AudiobookRecorder +AudiobookRecorder-linux +AudiobookRecorder-osx.dmg +AudiobookRecorder-win.exe diff --git a/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java b/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java index 7f1525e..9bd59d1 100644 --- a/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java +++ b/src/uk/co/majenko/audiobookrecorder/AudiobookRecorder.java @@ -798,6 +798,21 @@ public class AudiobookRecorder extends JFrame { JMenu mergeWith = new JMenu("Merge chapter with"); JMenuObject lockAll = new JMenuObject("Lock all sentences", c); JMenuObject unlockAll = new JMenuObject("Unlock all sentences", c); + JMenuObject exportChapter = new JMenuObject("Export chapter", c); + + exportChapter.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JMenuObject o = (JMenuObject)e.getSource(); + Chapter chap = (Chapter)o.getObject(); + + ExportDialog ed = new ExportDialog("Exporting " + chap.getName()); + + ExportThread t = new ExportThread(chap, ed); + Thread nt = new Thread(t); + nt.start(); + ed.setVisible(true); + } + }); for (Enumeration bc = book.children(); bc.hasMoreElements();) { Chapter chp = bc.nextElement(); @@ -824,7 +839,7 @@ public class AudiobookRecorder extends JFrame { - int idNumber = s2i(c.getId()); + int idNumber = Utils.s2i(c.getId()); moveUp.setEnabled(idNumber > 0); moveDown.setEnabled(idNumber > 0); @@ -836,10 +851,10 @@ public class AudiobookRecorder extends JFrame { int pos = bookTreeModel.getIndexOfChild(book, chap); if (pos > 0) pos--; - int id = s2i(chap.getId()); + int id = Utils.s2i(chap.getId()); if (id > 0) { Chapter prevChap = (Chapter)bookTreeModel.getChild(book, pos); - id = s2i(prevChap.getId()); + id = Utils.s2i(prevChap.getId()); if (id > 0) { bookTreeModel.removeNodeFromParent(chap); bookTreeModel.insertNodeInto(chap, book, pos); @@ -853,11 +868,11 @@ public class AudiobookRecorder extends JFrame { Chapter chap = (Chapter)o.getObject(); int pos = bookTreeModel.getIndexOfChild(book, chap); pos++; - int id = s2i(chap.getId()); + int id = Utils.s2i(chap.getId()); if (id > 0) { Chapter nextChap = (Chapter)bookTreeModel.getChild(book, pos); if (nextChap != null) { - id = s2i(nextChap.getId()); + id = Utils.s2i(nextChap.getId()); if (id > 0) { bookTreeModel.removeNodeFromParent(chap); bookTreeModel.insertNodeInto(chap, book, pos); @@ -913,9 +928,16 @@ public class AudiobookRecorder extends JFrame { menu.addSeparator(); menu.add(peak); + menu.addSeparator(); + menu.add(lockAll); menu.add(unlockAll); + + menu.addSeparator(); + + menu.add(exportChapter); + menu.show(bookTree, e.getX(), e.getY()); } @@ -1279,38 +1301,38 @@ public class AudiobookRecorder extends JFrame { Chapter c = new Chapter("audition", prefs.getProperty("chapter.audition.name")); - c.setPostGap(s2i(prefs.getProperty("chapter.audition.post-gap"))); - c.setPreGap(s2i(prefs.getProperty("chapter.audition.pre-gap"))); + c.setPostGap(Utils.s2i(prefs.getProperty("chapter.audition.post-gap"))); + c.setPreGap(Utils.s2i(prefs.getProperty("chapter.audition.pre-gap"))); bookTreeModel.insertNodeInto(c, book, 0); for (int i = 0; i < 100000000; i++) { String id = prefs.getProperty(String.format("chapter.audition.sentence.%08d.id", i)); String text = prefs.getProperty(String.format("chapter.audition.sentence.%08d.text", i)); - int gap = s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.post-gap", i))); + int gap = Utils.s2i(prefs.getProperty(String.format("chapter.audition.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.audition.sentence.%08d.start-offset", i)))); - s.setEndOffset(s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.end-offset", i)))); - s.setLocked(s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.locked", i)))); + s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.start-offset", i)))); + s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.audition.sentence.%08d.end-offset", i)))); + s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.audition.sentence.%08d.locked", i)))); bookTreeModel.insertNodeInto(s, c, c.getChildCount()); } 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"))); + c.setPostGap(Utils.s2i(prefs.getProperty("chapter.open.post-gap"))); + c.setPreGap(Utils.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))); + int gap = Utils.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)))); - s.setLocked(s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.locked", i)))); + s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.start-offset", i)))); + s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.open.sentence.%08d.end-offset", i)))); + s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.open.sentence.%08d.locked", i)))); bookTreeModel.insertNodeInto(s, c, c.getChildCount()); } @@ -1321,39 +1343,39 @@ public class AudiobookRecorder extends JFrame { 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)))); + c.setPostGap(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.post-gap", cno)))); + c.setPreGap(Utils.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))); + int gap = Utils.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)))); - s.setLocked(s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.locked", cno, i)))); + s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.start-offset", cno, i)))); + s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.end-offset", cno, i)))); + s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.%04d.sentence.%08d.locked", 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"))); + c.setPostGap(Utils.s2i(prefs.getProperty("chapter.close.post-gap"))); + c.setPreGap(Utils.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))); + int gap = Utils.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)))); - s.setLocked(s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.locked", i)))); + s.setStartOffset(Utils.s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.start-offset", i)))); + s.setEndOffset(Utils.s2i(prefs.getProperty(String.format("chapter.close.sentence.%08d.end-offset", i)))); + s.setLocked(Utils.s2b(prefs.getProperty(String.format("chapter.close.sentence.%08d.locked", i)))); bookTreeModel.insertNodeInto(s, c, c.getChildCount()); } @@ -1395,23 +1417,6 @@ public class AudiobookRecorder extends JFrame { } - boolean s2b(String s) { - if (s == null) return false; - if (s.equals("true")) return true; - if (s.equals("t")) return true; - if (s.equals("yes")) return true; - if (s.equals("y")) return true; - return false; - } - - 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()) { @@ -1480,141 +1485,19 @@ public class AudiobookRecorder extends JFrame { class ExportThread implements Runnable { ExportDialog exportDialog; + Chapter chapter; - public ExportThread(ExportDialog e) { + public ExportThread(Chapter c, ExportDialog e) { super(); exportDialog = e; + chapter = c; } + @SuppressWarnings("unchecked") public void run() { 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; - - String ffloc = Options.get("path.ffmpeg"); - if (ffloc != null && !ffloc.equals("")) { - encoder = new Encoder(new FFMPEGLocator() { - public String getFFMPEGExecutablePath() { - return Options.get("path.ffmpeg"); - } - }); - } else { - encoder = new Encoder(); - } - EncodingAttributes attributes = new EncodingAttributes(); - - AudioAttributes audioAttributes = new AudioAttributes(); - audioAttributes.setCodec("libmp3lame"); - audioAttributes.setBitRate(Options.getInteger("audio.export.bitrate")); - audioAttributes.setSamplingRate(Options.getInteger("audio.export.samplerate")); - 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(); - - if (c.getChildCount() == 0) continue; - int kids = c.getChildCount(); - if (kids == 0) continue; - String name = c.getName(); - exportDialog.setMessage("Exporting " + name); - exportDialog.setProgress(0); - - File exportFile = new File(export, name + ".wax"); - File wavFile = new File(export, name + ".wav"); - - FileOutputStream fos = new FileOutputStream(exportFile); - data = getRoomNoise(s2i(Options.get("catenation.pre-chapter"))); - fullLength += data.length; - fos.write(data); - - int kidno = 0; - - - for (Enumeration s = c.children(); s.hasMoreElements();) { - kidno++; - exportDialog.setProgress(kidno * 1000 / kids); - 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); - fos = new FileOutputStream(wavFile); - AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos); - fos.flush(); - fos.close(); - fis.close(); - exportFile.delete(); - } - - - - for (Enumeration o = book.children(); o.hasMoreElements();) { - - Chapter c = o.nextElement(); - if (c.getChildCount() == 0) continue; - String name = c.getName(); - - exportDialog.setMessage("Converting " + name); - - File wavFile = new File(export, name + ".wav"); - File mp3File = new File(export, name + "-untagged.mp3"); - File taggedFile = new File(export, name + ".mp3"); - - encoder.encode(wavFile, mp3File, attributes, exportDialog); - - 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.setAlbum(book.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(); - - } - - + chapter.exportChapter(exportDialog); } catch (Exception e) { e.printStackTrace(); } @@ -1626,12 +1509,18 @@ public class AudiobookRecorder extends JFrame { @SuppressWarnings("unchecked") public void exportAudio() { - ExportDialog ed = new ExportDialog("Exporting book..."); + + for (Enumeration o = book.children(); o.hasMoreElements();) { + Chapter c = o.nextElement(); + ExportDialog ed = new ExportDialog("Exporting " + c.getName()); - ExportThread t = new ExportThread(ed); - Thread nt = new Thread(t); - nt.start(); - ed.setVisible(true); + ExportThread t = new ExportThread(c, ed); + Thread nt = new Thread(t); + nt.start(); + ed.setVisible(true); + } + + JOptionPane.showMessageDialog(this, "Book export finished", "Export finished", JOptionPane.PLAIN_MESSAGE); } @@ -1668,7 +1557,7 @@ public class AudiobookRecorder extends JFrame { first = true; } if (first) { - data = getRoomNoise(s2i(Options.get("catenation.pre-chapter"))); + data = getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter"))); play.write(data, 0, data.length); } data = s.getRawAudioData(); @@ -1683,7 +1572,7 @@ public class AudiobookRecorder extends JFrame { } if (last) { - data = getRoomNoise(s2i(Options.get("catenation.post-chapter"))); + data = getRoomNoise(Utils.s2i(Options.get("catenation.post-chapter"))); play.write(data, 0, data.length); playing = null; } else { diff --git a/src/uk/co/majenko/audiobookrecorder/Chapter.java b/src/uk/co/majenko/audiobookrecorder/Chapter.java index 86741fb..7c06a4a 100644 --- a/src/uk/co/majenko/audiobookrecorder/Chapter.java +++ b/src/uk/co/majenko/audiobookrecorder/Chapter.java @@ -8,6 +8,10 @@ import java.util.*; import java.io.*; import java.nio.file.*; import javax.swing.tree.*; +import it.sauronsoftware.jave.*; +import com.mpatric.mp3agic.*; +import javax.sound.sampled.*; + public class Chapter extends DefaultMutableTreeNode { @@ -71,4 +75,125 @@ public class Chapter extends DefaultMutableTreeNode { public int getPostGap() { return postGap; } + + @SuppressWarnings("unchecked") + public void exportChapter(ExportDialog exportDialog) throws + FileNotFoundException, IOException, InputFormatException, NotSupportedException, + EncoderException, UnsupportedTagException, InvalidDataException { + + if (getChildCount() == 0) return; + + Book book = AudiobookRecorder.window.book; + + 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; + + String ffloc = Options.get("path.ffmpeg"); + if (ffloc != null && !ffloc.equals("")) { + encoder = new Encoder(new FFMPEGLocator() { + public String getFFMPEGExecutablePath() { + return Options.get("path.ffmpeg"); + } + }); + } else { + encoder = new Encoder(); + } + EncodingAttributes attributes = new EncodingAttributes(); + + AudioAttributes audioAttributes = new AudioAttributes(); + audioAttributes.setCodec("libmp3lame"); + audioAttributes.setBitRate(Options.getInteger("audio.export.bitrate")); + audioAttributes.setSamplingRate(Options.getInteger("audio.export.samplerate")); + audioAttributes.setChannels(new Integer(2)); + attributes.setFormat("mp3"); + attributes.setAudioAttributes(audioAttributes); + + + AudioFormat format = AudiobookRecorder.window.roomNoise.getAudioFormat(); + byte[] data; + + int fullLength = 0; + + int kids = getChildCount(); + + String name = getName(); + if (exportDialog != null) exportDialog.setMessage("Exporting " + name); + if (exportDialog != null) exportDialog.setProgress(0); + + 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 = AudiobookRecorder.window.getRoomNoise(Utils.s2i(Options.get("catenation.pre-chapter"))); + fullLength += data.length; + fos.write(data); + + int kidno = 0; + + + for (Enumeration s = children(); s.hasMoreElements();) { + kidno++; + if (exportDialog != null) exportDialog.setProgress(kidno * 1000 / kids); + Sentence snt = s.nextElement(); + data = snt.getRawAudioData(); + + fullLength += data.length; + fos.write(data); + + if (s.hasMoreElements()) { + data = AudiobookRecorder.window.getRoomNoise(snt.getPostGap()); + } else { + data = AudiobookRecorder.window.getRoomNoise(Utils.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); + fos = new FileOutputStream(wavFile); + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos); + fos.flush(); + fos.close(); + fis.close(); + exportFile.delete(); + + + + if (exportDialog != null) exportDialog.setMessage("Converting " + name); + + + if (exportDialog != null) { + encoder.encode(wavFile, mp3File, attributes, exportDialog); + } else { + encoder.encode(wavFile, mp3File, attributes); + } + + Mp3File id3 = new Mp3File(mp3File); + + ID3v2 tags = new ID3v24Tag(); + id3.setId3v2Tag(tags); + + tags.setTrack(Integer.toString(Utils.s2i(getId()) - 0)); + tags.setTitle(name); + tags.setAlbum(book.getName()); + tags.setArtist(book.getAuthor()); + + tags.setComment(book.getComment()); + + id3.save(taggedFile.getAbsolutePath()); + mp3File.delete(); + wavFile.delete(); + } + } diff --git a/src/uk/co/majenko/audiobookrecorder/ExportDialog.java b/src/uk/co/majenko/audiobookrecorder/ExportDialog.java index f284940..adfd2fe 100644 --- a/src/uk/co/majenko/audiobookrecorder/ExportDialog.java +++ b/src/uk/co/majenko/audiobookrecorder/ExportDialog.java @@ -68,11 +68,15 @@ public class ExportDialog extends JDialog implements EncoderProgressListener { } } - public void progress(int p) { setProgress(p); } + public void progress(int p) { + progress.setValue(500 + (p / 2)); + progress.setString((50 + p / 20) + "%"); + spin(); + } public void setProgress(int p) { - progress.setValue(p); - progress.setString((p / 10) + "%"); + progress.setValue(p / 2); + progress.setString((p / 20) + "%"); spin(); } diff --git a/src/uk/co/majenko/audiobookrecorder/Utils.java b/src/uk/co/majenko/audiobookrecorder/Utils.java index 1c1b050..3250fe6 100644 --- a/src/uk/co/majenko/audiobookrecorder/Utils.java +++ b/src/uk/co/majenko/audiobookrecorder/Utils.java @@ -20,4 +20,22 @@ public class Utils { return resizedImg; } + + public static boolean s2b(String s) { + if (s == null) return false; + if (s.equals("true")) return true; + if (s.equals("t")) return true; + if (s.equals("yes")) return true; + if (s.equals("y")) return true; + return false; + } + + public static int s2i(String s) { + try { + return Integer.parseInt(s); + } catch (Exception e) { + } + return 0; + } + }