Compare commits

...

19 Commits

Author SHA1 Message Date
6ea1bd22b0 Improved OSX building 2018-09-15 15:57:02 +01:00
f303872bf5 Added OS X bundling 2018-09-14 23:02:41 +01:00
a8b8def6ca Added rename book 2018-09-13 17:51:20 +01:00
aa9d7993d6 Removed open book window column heading 2018-09-13 15:46:52 +01:00
6157eea4da Improved open book panel and added cover art 2018-09-13 15:29:02 +01:00
8cc7614a68 Updated todo 2018-09-13 12:13:28 +01:00
4d928a9e26 Added chapter and sentence move up/down 2018-09-13 12:13:06 +01:00
ff0dc30375 Added archive todo 2018-09-13 01:41:58 +01:00
fab564e0e4 more todo items 2018-09-13 01:20:48 +01:00
27e72dc2d8 Added TODO 2018-09-13 01:17:00 +01:00
75a684d29f Added move sentence to chapter 2018-09-13 01:15:17 +01:00
b3f57c45af Added a crude Linux installer 2018-09-12 15:38:39 +01:00
fef1d19ddd Added auto-trim all and audition chapter 2018-09-12 15:23:09 +01:00
08408d2ae5 Added Oxygen acknowledgement 2018-09-12 12:04:25 +01:00
8380128bc8 Added app icon to readme for some reason 2018-09-12 12:03:08 +01:00
e01bc29ef0 Added new paragraph recording keymap 2018-09-12 11:51:40 +01:00
415b93931b Automatically open last used book 2018-09-11 23:29:22 +01:00
3f42a13afd Improved export system 2018-09-11 23:12:36 +01:00
04e4447b39 Added modified version of jave to source tree 2018-09-11 23:11:16 +01:00
43 changed files with 3163 additions and 59 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.DS_Store
._*
bin
AudiobookRecorder.jar
AudiobookRecorder.dmg
AudiobookRecorder.exe

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "universalJavaApplicationStub"]
path = universalJavaApplicationStub
url = ./universalJavaApplicationStub

View File

@@ -34,4 +34,4 @@ Third party software
* CMU Sphinx: https://github.com/mpatric/mp3agic * CMU Sphinx: https://github.com/mpatric/mp3agic
* JAVE ffmpeg wrapper: http://www.sauronsoftware.it/projects/jave/ * JAVE ffmpeg wrapper: http://www.sauronsoftware.it/projects/jave/
* JTattoo: http://www.sauronsoftware.it/projects/jave/ * JTattoo: http://www.sauronsoftware.it/projects/jave/
* Icons from, or based on, Oxygen: https://github.com/KDE/oxygen-icons

View File

@@ -1,3 +1,5 @@
![Application Icon](https://github.com/MajenkoProjects/AudiobookRecorder/raw/master/resources/uk/co/majenko/audiobookrecorder/icons/appIcon.png)
Audiobook Recorder Audiobook Recorder
================== ==================
@@ -24,6 +26,7 @@ From here on much is controlled by key presses.
* Press and hold "R" to record a new phrase - the screen flashes red while it's recording. The phrase is * Press and hold "R" to record a new phrase - the screen flashes red while it's recording. The phrase is
appended to the currently selected chapter, or to the last chapter if none is selected. appended to the currently selected chapter, or to the last chapter if none is selected.
* Press and hold "T" to record a new phrase that is the start of a new paragraph. This adds the "post paragraph" gap to the previous sentence. Otherwise it does the same as "R".
* Press "D" to delete the last phrase you recorded. * Press "D" to delete the last phrase you recorded.
* Press "E" to re-record the currently selected phrase. * Press "E" to re-record the currently selected phrase.

8
TODO.md Normal file
View File

@@ -0,0 +1,8 @@
ToDo
====
* Merge chapters
* Identify and export 5 minute retail sample
* Identify and export 15 minute checkpoint
* Archive (Zip and delete) audiobooks
* File path browser buttons in options

Binary file not shown.

View File

@@ -1,6 +1,8 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project name="Audiobook Recorder" default="build"> <project name="Audiobook Recorder" default="build">
<taskdef name="jarbundler" classname="com.ultramixer.jarbundler.JarBundler" classpath="ant-libs/jarbundler-2.3.2.jar" />
<target name="clean" description="Clean out the build directories"> <target name="clean" description="Clean out the build directories">
<delete dir="bin" /> <delete dir="bin" />
<delete file="AudiobookRecorder.jar" /> <delete file="AudiobookRecorder.jar" />
@@ -43,4 +45,46 @@
<chmod perm="0755" file="uecide.jar" /> <chmod perm="0755" file="uecide.jar" />
</target> </target>
<target name="release" depends="macapp">
</target>
<target name="macapp" depends="build">
<mkdir dir="tmp"/>
<jarbundler
name="AudiobookRecorder"
shortname="AudiobookRecorder"
icon="dist/macosx/audiobookrecorder.icns"
stubfile="universalJavaApplicationStub/src/universalJavaApplicationStub"
dir="tmp"
jar="AudiobookRecorder.jar"
mainclass="uk.co.majenko.audiobookrecorder.AudiobookRecorder"
jvmversion="1.7+"
>
</jarbundler>
<symlink link="tmp/Applications" resource="/Applications" overwrite="true" />
<!--copy file="dist/macosx/dmg.icns" tofile="tmp/.VolumeIcon.icns" /-->
<exec executable="genisoimage">
<arg value="-D" />
<arg value="-V" />
<arg value="AudiobookRecorder" />
<arg value="-no-pad" />
<arg value="-r" />
<arg value="-apple" />
<arg value="-o" />
<arg value="AudiobookRecorder.dmg" />
<arg value="-dir-mode" />
<arg value="0755" />
<arg value="-file-mode" />
<arg value="0755" />
<arg value="tmp" />
</exec>
<delete dir="tmp" />
</target>
</project> </project>

BIN
deps/jave-1.0.2.jar vendored

Binary file not shown.

BIN
dist/macosx/audiobookrecorder.icns vendored Normal file

Binary file not shown.

BIN
dist/macosx/dmg.icns vendored Normal file

Binary file not shown.

26
installers/linux.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
NAME=AudiobookRecorder
BIN=/usr/bin
DESKTOP=/usr/share/applications
SHARE=/usr/share
ICON=/usr/share/icons
mkdir -p "$BIN"
mkdir -p "$SHARE/$NAME"
cp AudiobookRecorder.jar "$SHARE/$NAME/$NAME.jar"
echo "#!/bin/bash" > "$BIN/$NAME"
echo "java -jar \"$SHARE/$NAME/$NAME.jar\"" >> "$BIN/$NAME"
chmod 755 "$BIN/$NAME"
echo "[Desktop Entry]" > "$DESKTOP/$NAME.desktop"
echo "Version=1.0" >> "$DESKTOP/$NAME.desktop"
echo "Name=$NAME" >> "$DESKTOP/$NAME.desktop"
echo "Exec=$NAME" >> "$DESKTOP/$NAME.desktop"
echo "Icon=$NAME" >> "$DESKTOP/$NAME.desktop"
echo "Categories=Multimedia" >> "$DESKTOP/$NAME.desktop"
cp resources/uk/co/majenko/audiobookrecorder/icons/appIcon.png "$ICON/$NAME.png"

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,182 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.Serializable;
/**
* Attributes controlling the audio encoding process.
*
* @author Carlo Pelliccia
*/
public class AudioAttributes implements Serializable {
private static final long serialVersionUID = 1L;
/**
* This value can be setted in the codec field to perform a direct stream
* copy, without re-encoding of the audio stream.
*/
public static final String DIRECT_STREAM_COPY = "copy";
/**
* The codec name for the encoding process. If null or not specified the
* encoder will perform a direct stream copy.
*/
private String codec = null;
/**
* The bitrate value for the encoding process. If null or not specified a
* default value will be picked.
*/
private Integer bitRate = null;
/**
* The samplingRate value for the encoding process. If null or not specified
* a default value will be picked.
*/
private Integer samplingRate = null;
/**
* The channels value (1=mono, 2=stereo) for the encoding process. If null
* or not specified a default value will be picked.
*/
private Integer channels = null;
/**
* The volume value for the encoding process. If null or not specified a
* default value will be picked. If 256 no volume change will be performed.
*/
private Integer volume = null;
/**
* Returns the codec name for the encoding process.
*
* @return The codec name for the encoding process.
*/
String getCodec() {
return codec;
}
/**
* Sets the codec name for the encoding process. If null or not specified
* the encoder will perform a direct stream copy.
*
* Be sure the supplied codec name is in the list returned by
* {@link Encoder#getAudioEncoders()}.
*
* A special value can be picked from
* {@link AudioAttributes#DIRECT_STREAM_COPY}.
*
* @param codec
* The codec name for the encoding process.
*/
public void setCodec(String codec) {
this.codec = codec;
}
/**
* Returns the bitrate value for the encoding process.
*
* @return The bitrate value for the encoding process.
*/
Integer getBitRate() {
return bitRate;
}
/**
* Sets the bitrate value for the encoding process. If null or not specified
* a default value will be picked.
*
* @param bitRate
* The bitrate value for the encoding process.
*/
public void setBitRate(Integer bitRate) {
this.bitRate = bitRate;
}
/**
* Returns the samplingRate value for the encoding process.
*
* @return the samplingRate The samplingRate value for the encoding process.
*/
Integer getSamplingRate() {
return samplingRate;
}
/**
* Sets the samplingRate value for the encoding process. If null or not
* specified a default value will be picked.
*
* @param samplingRate
* The samplingRate value for the encoding process.
*/
public void setSamplingRate(Integer samplingRate) {
this.samplingRate = samplingRate;
}
/**
* Returns the channels value (1=mono, 2=stereo) for the encoding process.
*
* @return The channels value (1=mono, 2=stereo) for the encoding process.
*/
Integer getChannels() {
return channels;
}
/**
* Sets the channels value (1=mono, 2=stereo) for the encoding process. If
* null or not specified a default value will be picked.
*
* @param channels
* The channels value (1=mono, 2=stereo) for the encoding
* process.
*/
public void setChannels(Integer channels) {
this.channels = channels;
}
/**
* Returns the volume value for the encoding process.
*
* @return The volume value for the encoding process.
*/
Integer getVolume() {
return volume;
}
/**
* Sets the volume value for the encoding process. If null or not specified
* a default value will be picked. If 256 no volume change will be
* performed.
*
* @param volume
* The volume value for the encoding process.
*/
public void setVolume(Integer volume) {
this.volume = volume;
}
public String toString() {
return getClass().getName() + "(codec=" + codec + ", bitRate="
+ bitRate + ", samplingRate=" + samplingRate + ", channels="
+ channels + ", volume=" + volume + ")";
}
}

View File

@@ -0,0 +1,137 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* Instances of this class report informations about an audio stream that can be
* decoded.
*
* @author Carlo Pelliccia
*/
public class AudioInfo {
/**
* The audio stream decoder name.
*/
private String decoder;
/**
* The audio stream sampling rate. If less than 0, this information is not
* available.
*/
private int samplingRate = -1;
/**
* The audio stream channels number (1=mono, 2=stereo). If less than 0, this
* information is not available.
*/
private int channels = -1;
/**
* The audio stream (average) bit rate. If less than 0, this information is
* not available.
*/
private int bitRate = -1;
/**
* Returns the audio stream decoder name.
*
* @return The audio stream decoder name.
*/
public String getDecoder() {
return decoder;
}
/**
* Sets the audio stream decoder name.
*
* @param decoder
* The audio stream decoder name.
*/
void setDecoder(String format) {
this.decoder = format;
}
/**
* Returns the audio stream sampling rate. If less than 0, this information
* is not available.
*
* @return The audio stream sampling rate.
*/
public int getSamplingRate() {
return samplingRate;
}
/**
* Sets the audio stream sampling rate.
*
* @param samplingRate
* The audio stream sampling rate.
*/
void setSamplingRate(int samplingRate) {
this.samplingRate = samplingRate;
}
/**
* Returns the audio stream channels number (1=mono, 2=stereo). If less than
* 0, this information is not available.
*
* @return the channels The audio stream channels number (1=mono, 2=stereo).
*/
public int getChannels() {
return channels;
}
/**
* Sets the audio stream channels number (1=mono, 2=stereo).
*
* @param channels
* The audio stream channels number (1=mono, 2=stereo).
*/
void setChannels(int channels) {
this.channels = channels;
}
/**
* Returns the audio stream (average) bit rate. If less than 0, this
* information is not available.
*
* @return The audio stream (average) bit rate.
*/
public int getBitRate() {
return bitRate;
}
/**
* Sets the audio stream (average) bit rate.
*
* @param bitRate
* The audio stream (average) bit rate.
*/
void setBitRate(int bitRate) {
this.bitRate = bitRate;
}
public String toString() {
return getClass().getName() + " (decoder=" + decoder + ", samplingRate="
+ samplingRate + ", channels=" + channels + ", bitRate="
+ bitRate + ")";
}
}

View File

@@ -0,0 +1,141 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* The default ffmpeg executable locator, which exports on disk the ffmpeg
* executable bundled with the library distributions. It should work both for
* windows and many linux distributions. If it doesn't, try compiling your own
* ffmpeg executable and plug it in JAVE with a custom {@link FFMPEGLocator}.
*
* @author Carlo Pelliccia
*/
public class DefaultFFMPEGLocator extends FFMPEGLocator {
/**
* Trace the version of the bundled ffmpeg executable. It's a counter: every
* time the bundled ffmpeg change it is incremented by 1.
*/
private static final int myEXEversion = 1;
/**
* The ffmpeg executable file path.
*/
private String path;
/**
* It builds the default FFMPEGLocator, exporting the ffmpeg executable on a
* temp file.
*/
public DefaultFFMPEGLocator() {
// Windows?
boolean isWindows;
String os = System.getProperty("os.name").toLowerCase();
if (os.indexOf("windows") != -1) {
isWindows = true;
} else {
isWindows = false;
}
// Temp dir?
File temp = new File(System.getProperty("java.io.tmpdir"), "jave-"
+ myEXEversion);
if (!temp.exists()) {
temp.mkdirs();
temp.deleteOnExit();
}
// ffmpeg executable export on disk.
String suffix = isWindows ? ".exe" : "";
File exe = new File(temp, "ffmpeg" + suffix);
if (!exe.exists()) {
copyFile("ffmpeg" + suffix, exe);
}
// pthreadGC2.dll
if (isWindows) {
File dll = new File(temp, "pthreadGC2.dll");
if (!dll.exists()) {
copyFile("pthreadGC2.dll", dll);
}
}
// Need a chmod?
if (!isWindows) {
Runtime runtime = Runtime.getRuntime();
try {
runtime.exec(new String[] { "/bin/chmod", "755",
exe.getAbsolutePath() });
} catch (IOException e) {
e.printStackTrace();
}
}
// Ok.
this.path = exe.getAbsolutePath();
}
protected String getFFMPEGExecutablePath() {
return path;
}
/**
* Copies a file bundled in the package to the supplied destination.
*
* @param path
* The name of the bundled file.
* @param dest
* The destination.
* @throws RuntimeException
* If aun unexpected error occurs.
*/
private void copyFile(String path, File dest) throws RuntimeException {
InputStream input = null;
OutputStream output = null;
try {
input = getClass().getResourceAsStream(path);
output = new FileOutputStream(dest);
byte[] buffer = new byte[1024];
int l;
while ((l = input.read(buffer)) != -1) {
output.write(buffer, 0, l);
}
} catch (IOException e) {
throw new RuntimeException("Cannot write file "
+ dest.getAbsolutePath());
} finally {
if (output != null) {
try {
output.close();
} catch (Throwable t) {
;
}
}
if (input != null) {
try {
input.close();
} catch (Throwable t) {
;
}
}
}
}
}

View File

@@ -0,0 +1,938 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Main class of the package. Instances can encode audio and video streams.
*
* @author Carlo Pelliccia
*/
public class Encoder {
/**
* This regexp is used to parse the ffmpeg output about the supported
* formats.
*/
private static final Pattern FORMAT_PATTERN = Pattern
.compile("^\\s*([D ])([E ])\\s+([\\w,]+)\\s+.+$");
/**
* This regexp is used to parse the ffmpeg output about the included
* encoders/decoders.
*/
private static final Pattern ENCODER_DECODER_PATTERN = Pattern.compile(
"^\\s*([D ])([E ])([AVS]).{3}\\s+(.+)$", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the ongoing encoding
* process.
*/
private static final Pattern PROGRESS_INFO_PATTERN = Pattern.compile(
"\\s*(\\w+)\\s*=\\s*(\\S+)\\s*", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the size of a video
* stream.
*/
private static final Pattern SIZE_PATTERN = Pattern.compile(
"(\\d+)x(\\d+)", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the frame rate value
* of a video stream.
*/
private static final Pattern FRAME_RATE_PATTERN = Pattern.compile(
"([\\d.]+)\\s+(?:fps|tb\\(r\\))", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the bit rate value
* of a stream.
*/
private static final Pattern BIT_RATE_PATTERN = Pattern.compile(
"(\\d+)\\s+kb/s", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the sampling rate of
* an audio stream.
*/
private static final Pattern SAMPLING_RATE_PATTERN = Pattern.compile(
"(\\d+)\\s+Hz", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the channels number
* of an audio stream.
*/
private static final Pattern CHANNELS_PATTERN = Pattern.compile(
"(mono|stereo)", Pattern.CASE_INSENSITIVE);
/**
* This regexp is used to parse the ffmpeg output about the success of an
* encoding operation.
*/
private static final Pattern SUCCESS_PATTERN = Pattern.compile(
"^\\s*video\\:\\S+\\s+audio\\:\\S+\\s+global headers\\:\\S+.*$",
Pattern.CASE_INSENSITIVE);
/**
* The locator of the ffmpeg executable used by this encoder.
*/
private FFMPEGLocator locator;
/**
* It builds an encoder using a {@link DefaultFFMPEGLocator} instance to
* locate the ffmpeg executable to use.
*/
public Encoder() {
this.locator = new DefaultFFMPEGLocator();
}
/**
* It builds an encoder with a custom {@link FFMPEGLocator}.
*
* @param locator
* The locator picking up the ffmpeg executable used by the
* encoder.
*/
public Encoder(FFMPEGLocator locator) {
this.locator = locator;
}
/**
* Returns a list with the names of all the audio decoders bundled with the
* ffmpeg distribution in use. An audio stream can be decoded only if a
* decoder for its format is available.
*
* @return A list with the names of all the included audio decoders.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public String[] getAudioDecoders() throws EncoderException {
ArrayList<String> res = new ArrayList<String>();
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-formats");
try {
ffmpeg.execute();
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getInputStream()));
String line;
boolean evaluate = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (evaluate) {
Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
if (matcher.matches()) {
String decoderFlag = matcher.group(1);
String audioVideoFlag = matcher.group(3);
if ("D".equals(decoderFlag)
&& "A".equals(audioVideoFlag)) {
String name = matcher.group(4);
res.add(name);
}
} else {
break;
}
} else if (line.trim().equals("Codecs:")) {
evaluate = true;
}
}
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
int size = res.size();
String[] ret = new String[size];
for (int i = 0; i < size; i++) {
ret[i] = (String) res.get(i);
}
return ret;
}
/**
* Returns a list with the names of all the audio encoders bundled with the
* ffmpeg distribution in use. An audio stream can be encoded using one of
* these encoders.
*
* @return A list with the names of all the included audio encoders.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public String[] getAudioEncoders() throws EncoderException {
ArrayList<String> res = new ArrayList<String>();
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-formats");
try {
ffmpeg.execute();
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getInputStream()));
String line;
boolean evaluate = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (evaluate) {
Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
if (matcher.matches()) {
String encoderFlag = matcher.group(2);
String audioVideoFlag = matcher.group(3);
if ("E".equals(encoderFlag)
&& "A".equals(audioVideoFlag)) {
String name = matcher.group(4);
res.add(name);
}
} else {
break;
}
} else if (line.trim().equals("Codecs:")) {
evaluate = true;
}
}
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
int size = res.size();
String[] ret = new String[size];
for (int i = 0; i < size; i++) {
ret[i] = (String) res.get(i);
}
return ret;
}
/**
* Returns a list with the names of all the video decoders bundled with the
* ffmpeg distribution in use. A video stream can be decoded only if a
* decoder for its format is available.
*
* @return A list with the names of all the included video decoders.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public String[] getVideoDecoders() throws EncoderException {
ArrayList<String> res = new ArrayList<String>();
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-formats");
try {
ffmpeg.execute();
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getInputStream()));
String line;
boolean evaluate = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (evaluate) {
Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
if (matcher.matches()) {
String decoderFlag = matcher.group(1);
String audioVideoFlag = matcher.group(3);
if ("D".equals(decoderFlag)
&& "V".equals(audioVideoFlag)) {
String name = matcher.group(4);
res.add(name);
}
} else {
break;
}
} else if (line.trim().equals("Codecs:")) {
evaluate = true;
}
}
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
int size = res.size();
String[] ret = new String[size];
for (int i = 0; i < size; i++) {
ret[i] = (String) res.get(i);
}
return ret;
}
/**
* Returns a list with the names of all the video encoders bundled with the
* ffmpeg distribution in use. A video stream can be encoded using one of
* these encoders.
*
* @return A list with the names of all the included video encoders.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public String[] getVideoEncoders() throws EncoderException {
ArrayList<String> res = new ArrayList<String>();
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-formats");
try {
ffmpeg.execute();
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getInputStream()));
String line;
boolean evaluate = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (evaluate) {
Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line);
if (matcher.matches()) {
String encoderFlag = matcher.group(2);
String audioVideoFlag = matcher.group(3);
if ("E".equals(encoderFlag)
&& "V".equals(audioVideoFlag)) {
String name = matcher.group(4);
res.add(name);
}
} else {
break;
}
} else if (line.trim().equals("Codecs:")) {
evaluate = true;
}
}
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
int size = res.size();
String[] ret = new String[size];
for (int i = 0; i < size; i++) {
ret[i] = (String) res.get(i);
}
return ret;
}
/**
* Returns a list with the names of all the file formats supported at
* encoding time by the underlying ffmpeg distribution. A multimedia file
* could be encoded and generated only if the specified format is in this
* list.
*
* @return A list with the names of all the supported file formats at
* encoding time.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public String[] getSupportedEncodingFormats() throws EncoderException {
ArrayList<String> res = new ArrayList<String>();
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-formats");
try {
ffmpeg.execute();
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getInputStream()));
String line;
boolean evaluate = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (evaluate) {
Matcher matcher = FORMAT_PATTERN.matcher(line);
if (matcher.matches()) {
String encoderFlag = matcher.group(2);
if ("E".equals(encoderFlag)) {
String aux = matcher.group(3);
StringTokenizer st = new StringTokenizer(aux, ",");
while (st.hasMoreTokens()) {
String token = st.nextToken().trim();
if (!res.contains(token)) {
res.add(token);
}
}
}
} else {
break;
}
} else if (line.trim().equals("File formats:")) {
evaluate = true;
}
}
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
int size = res.size();
String[] ret = new String[size];
for (int i = 0; i < size; i++) {
ret[i] = (String) res.get(i);
}
return ret;
}
/**
* Returns a list with the names of all the file formats supported at
* decoding time by the underlying ffmpeg distribution. A multimedia file
* could be open and decoded only if its format is in this list.
*
* @return A list with the names of all the supported file formats at
* decoding time.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public String[] getSupportedDecodingFormats() throws EncoderException {
ArrayList<String> res = new ArrayList<String>();
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-formats");
try {
ffmpeg.execute();
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getInputStream()));
String line;
boolean evaluate = false;
while ((line = reader.readLine()) != null) {
if (line.trim().length() == 0) {
continue;
}
if (evaluate) {
Matcher matcher = FORMAT_PATTERN.matcher(line);
if (matcher.matches()) {
String decoderFlag = matcher.group(1);
if ("D".equals(decoderFlag)) {
String aux = matcher.group(3);
StringTokenizer st = new StringTokenizer(aux, ",");
while (st.hasMoreTokens()) {
String token = st.nextToken().trim();
if (!res.contains(token)) {
res.add(token);
}
}
}
} else {
break;
}
} else if (line.trim().equals("File formats:")) {
evaluate = true;
}
}
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
int size = res.size();
String[] ret = new String[size];
for (int i = 0; i < size; i++) {
ret[i] = (String) res.get(i);
}
return ret;
}
/**
* Returns a set informations about a multimedia file, if its format is
* supported for decoding.
*
* @param source
* The source multimedia file.
* @return A set of informations about the file and its contents.
* @throws InputFormatException
* If the format of the source file cannot be recognized and
* decoded.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
public MultimediaInfo getInfo(File source) throws InputFormatException,
EncoderException {
FFMPEGExecutor ffmpeg = locator.createExecutor();
ffmpeg.addArgument("-i");
ffmpeg.addArgument(source.getAbsolutePath());
try {
ffmpeg.execute();
} catch (IOException e) {
throw new EncoderException(e);
}
try {
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getErrorStream()));
return parseMultimediaInfo(source, reader);
} finally {
ffmpeg.destroy();
}
}
/**
* Private utility. It parses the ffmpeg output, extracting informations
* about a source multimedia file.
*
* @param source
* The source multimedia file.
* @param reader
* The ffmpeg output channel.
* @return A set of informations about the source multimedia file and its
* contents.
* @throws InputFormatException
* If the format of the source file cannot be recognized and
* decoded.
* @throws EncoderException
* If a problem occurs calling the underlying ffmpeg executable.
*/
private MultimediaInfo parseMultimediaInfo(File source,
RBufferedReader reader) throws InputFormatException,
EncoderException {
Pattern p1 = Pattern.compile("^\\s*Input #0, (\\w+).+$\\s*",
Pattern.CASE_INSENSITIVE);
Pattern p2 = Pattern.compile(
"^\\s*Duration: (\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d).*$",
Pattern.CASE_INSENSITIVE);
Pattern p3 = Pattern.compile(
"^\\s*Stream #\\S+: ((?:Audio)|(?:Video)|(?:Data)): (.*)\\s*$",
Pattern.CASE_INSENSITIVE);
MultimediaInfo info = null;
try {
int step = 0;
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
if (step == 0) {
String token = source.getAbsolutePath() + ": ";
if (line.startsWith(token)) {
String message = line.substring(token.length());
throw new InputFormatException(message);
}
Matcher m = p1.matcher(line);
if (m.matches()) {
String format = m.group(1);
info = new MultimediaInfo();
info.setFormat(format);
step++;
}
} else if (step == 1) {
Matcher m = p2.matcher(line);
if (m.matches()) {
long hours = Integer.parseInt(m.group(1));
long minutes = Integer.parseInt(m.group(2));
long seconds = Integer.parseInt(m.group(3));
long dec = Integer.parseInt(m.group(4));
long duration = (dec * 100L) + (seconds * 1000L)
+ (minutes * 60L * 1000L)
+ (hours * 60L * 60L * 1000L);
info.setDuration(duration);
step++;
} else {
step = 3;
}
} else if (step == 2) {
Matcher m = p3.matcher(line);
if (m.matches()) {
String type = m.group(1);
String specs = m.group(2);
if ("Video".equalsIgnoreCase(type)) {
VideoInfo video = new VideoInfo();
StringTokenizer st = new StringTokenizer(specs, ",");
for (int i = 0; st.hasMoreTokens(); i++) {
String token = st.nextToken().trim();
if (i == 0) {
video.setDecoder(token);
} else {
boolean parsed = false;
// Video size.
Matcher m2 = SIZE_PATTERN.matcher(token);
if (!parsed && m2.find()) {
int width = Integer.parseInt(m2
.group(1));
int height = Integer.parseInt(m2
.group(2));
video.setSize(new VideoSize(width,
height));
parsed = true;
}
// Frame rate.
m2 = FRAME_RATE_PATTERN.matcher(token);
if (!parsed && m2.find()) {
try {
float frameRate = Float
.parseFloat(m2.group(1));
video.setFrameRate(frameRate);
} catch (NumberFormatException e) {
;
}
parsed = true;
}
// Bit rate.
m2 = BIT_RATE_PATTERN.matcher(token);
if (!parsed && m2.find()) {
int bitRate = Integer.parseInt(m2
.group(1));
video.setBitRate(bitRate);
parsed = true;
}
}
}
info.setVideo(video);
} else if ("Audio".equalsIgnoreCase(type)) {
AudioInfo audio = new AudioInfo();
StringTokenizer st = new StringTokenizer(specs, ",");
for (int i = 0; st.hasMoreTokens(); i++) {
String token = st.nextToken().trim();
if (i == 0) {
audio.setDecoder(token);
} else {
boolean parsed = false;
// Sampling rate.
Matcher m2 = SAMPLING_RATE_PATTERN
.matcher(token);
if (!parsed && m2.find()) {
int samplingRate = Integer.parseInt(m2
.group(1));
audio.setSamplingRate(samplingRate);
parsed = true;
}
// Channels.
m2 = CHANNELS_PATTERN.matcher(token);
if (!parsed && m2.find()) {
String ms = m2.group(1);
if ("mono".equalsIgnoreCase(ms)) {
audio.setChannels(1);
} else if ("stereo"
.equalsIgnoreCase(ms)) {
audio.setChannels(2);
}
parsed = true;
}
// Bit rate.
m2 = BIT_RATE_PATTERN.matcher(token);
if (!parsed && m2.find()) {
int bitRate = Integer.parseInt(m2
.group(1));
audio.setBitRate(bitRate);
parsed = true;
}
}
}
info.setAudio(audio);
}
} else {
step = 3;
}
}
if (step == 3) {
reader.reinsertLine(line);
break;
}
}
} catch (IOException e) {
throw new EncoderException(e);
}
if (info == null) {
throw new InputFormatException();
}
return info;
}
/**
* Private utility. Parse a line and try to match its contents against the
* {@link Encoder#PROGRESS_INFO_PATTERN} pattern. It the line can be parsed,
* it returns a hashtable with progress informations, otherwise it returns
* null.
*
* @param line
* The line from the ffmpeg output.
* @return A hashtable with the value reported in the line, or null if the
* given line can not be parsed.
*/
private Hashtable parseProgressInfoLine(String line) {
Hashtable<String, String> table = null;
Matcher m = PROGRESS_INFO_PATTERN.matcher(line);
while (m.find()) {
if (table == null) {
table = new Hashtable<String,String>();
}
String key = m.group(1);
String value = m.group(2);
table.put(key, value);
}
return table;
}
/**
* Re-encode a multimedia file.
*
* @param source
* The source multimedia file. It cannot be null. Be sure this
* file can be decoded (see
* {@link Encoder#getSupportedDecodingFormats()},
* {@link Encoder#getAudioDecoders()} and
* {@link Encoder#getVideoDecoders()}).
* @param target
* The target multimedia re-encoded file. It cannot be null. If
* this file already exists, it will be overwrited.
* @param attributes
* A set of attributes for the encoding process.
* @throws IllegalArgumentException
* If both audio and video parameters are null.
* @throws InputFormatException
* If the source multimedia file cannot be decoded.
* @throws EncoderException
* If a problems occurs during the encoding process.
*/
public void encode(File source, File target, EncodingAttributes attributes)
throws IllegalArgumentException, InputFormatException,
EncoderException {
encode(source, target, attributes, null);
}
/**
* Re-encode a multimedia file.
*
* @param source
* The source multimedia file. It cannot be null. Be sure this
* file can be decoded (see
* {@link Encoder#getSupportedDecodingFormats()},
* {@link Encoder#getAudioDecoders()} and
* {@link Encoder#getVideoDecoders()}).
* @param target
* The target multimedia re-encoded file. It cannot be null. If
* this file already exists, it will be overwrited.
* @param attributes
* A set of attributes for the encoding process.
* @param listener
* An optional progress listener for the encoding process. It can
* be null.
* @throws IllegalArgumentException
* If both audio and video parameters are null.
* @throws InputFormatException
* If the source multimedia file cannot be decoded.
* @throws EncoderException
* If a problems occurs during the encoding process.
*/
public void encode(File source, File target, EncodingAttributes attributes,
EncoderProgressListener listener) throws IllegalArgumentException,
InputFormatException, EncoderException {
String formatAttribute = attributes.getFormat();
Float offsetAttribute = attributes.getOffset();
Float durationAttribute = attributes.getDuration();
AudioAttributes audioAttributes = attributes.getAudioAttributes();
VideoAttributes videoAttributes = attributes.getVideoAttributes();
if (audioAttributes == null && videoAttributes == null) {
throw new IllegalArgumentException(
"Both audio and video attributes are null");
}
target = target.getAbsoluteFile();
target.getParentFile().mkdirs();
FFMPEGExecutor ffmpeg = locator.createExecutor();
if (offsetAttribute != null) {
ffmpeg.addArgument("-ss");
ffmpeg.addArgument(String.valueOf(offsetAttribute.floatValue()));
}
ffmpeg.addArgument("-i");
ffmpeg.addArgument(source.getAbsolutePath());
if (durationAttribute != null) {
ffmpeg.addArgument("-t");
ffmpeg.addArgument(String.valueOf(durationAttribute.floatValue()));
}
if (videoAttributes == null) {
ffmpeg.addArgument("-vn");
} else {
String codec = videoAttributes.getCodec();
if (codec != null) {
ffmpeg.addArgument("-vcodec");
ffmpeg.addArgument(codec);
}
String tag = videoAttributes.getTag();
if (tag != null) {
ffmpeg.addArgument("-vtag");
ffmpeg.addArgument(tag);
}
Integer bitRate = videoAttributes.getBitRate();
if (bitRate != null) {
ffmpeg.addArgument("-b");
ffmpeg.addArgument(String.valueOf(bitRate.intValue()));
}
Integer frameRate = videoAttributes.getFrameRate();
if (frameRate != null) {
ffmpeg.addArgument("-r");
ffmpeg.addArgument(String.valueOf(frameRate.intValue()));
}
VideoSize size = videoAttributes.getSize();
if (size != null) {
ffmpeg.addArgument("-s");
ffmpeg.addArgument(String.valueOf(size.getWidth()) + "x"
+ String.valueOf(size.getHeight()));
}
}
if (audioAttributes == null) {
ffmpeg.addArgument("-an");
} else {
String codec = audioAttributes.getCodec();
if (codec != null) {
ffmpeg.addArgument("-acodec");
ffmpeg.addArgument(codec);
}
Integer bitRate = audioAttributes.getBitRate();
if (bitRate != null) {
ffmpeg.addArgument("-ab");
ffmpeg.addArgument(String.valueOf(bitRate.intValue()));
}
Integer channels = audioAttributes.getChannels();
if (channels != null) {
ffmpeg.addArgument("-ac");
ffmpeg.addArgument(String.valueOf(channels.intValue()));
}
Integer samplingRate = audioAttributes.getSamplingRate();
if (samplingRate != null) {
ffmpeg.addArgument("-ar");
ffmpeg.addArgument(String.valueOf(samplingRate.intValue()));
}
Integer volume = audioAttributes.getVolume();
if (volume != null) {
ffmpeg.addArgument("-vol");
ffmpeg.addArgument(String.valueOf(volume.intValue()));
}
}
ffmpeg.addArgument("-f");
ffmpeg.addArgument(formatAttribute);
ffmpeg.addArgument("-y");
ffmpeg.addArgument(target.getAbsolutePath());
try {
ffmpeg.execute();
} catch (IOException e) {
throw new EncoderException(e);
}
try {
String lastWarning = null;
long duration;
long progress = 0;
RBufferedReader reader = null;
reader = new RBufferedReader(new InputStreamReader(ffmpeg
.getErrorStream()));
MultimediaInfo info = parseMultimediaInfo(source, reader);
if (durationAttribute != null) {
duration = (long) Math
.round((durationAttribute.floatValue() * 1000L));
} else {
duration = info.getDuration();
if (offsetAttribute != null) {
duration -= (long) Math
.round((offsetAttribute.floatValue() * 1000L));
}
}
if (listener != null) {
listener.sourceInfo(info);
}
int step = 0;
String line;
while ((line = reader.readLine()) != null) {
/*
if (step == 0) {
if (line.startsWith("WARNING: ")) {
if (listener != null) {
listener.message(line);
}
} else if (!line.startsWith("Output #0")) {
throw new EncoderException(line);
} else {
step++;
}
} else if (step == 1) {
if (!line.startsWith(" ")) {
step++;
}
}
if (step == 2) {
if (!line.startsWith("Stream mapping:")) {
throw new EncoderException(line);
} else {
step++;
}
} else if (step == 3) {
if (!line.startsWith(" ")) {
step++;
}
}
if (step == 4) {
*/
line = line.trim();
if (line.length() > 0) {
Hashtable table = parseProgressInfoLine(line);
if (table == null) {
if (listener != null) {
listener.message(line);
}
lastWarning = line;
} else {
if (listener != null) {
String time = (String) table.get("time");
if (time != null) {
int dot = time.indexOf('.');
if (dot > 0 && dot == time.length() - 2
&& duration > 0) {
String p1 = time.substring(0, dot);
String p2 = time.substring(dot + 1);
try {
long i1 = Long.parseLong(p1);
long i2 = Long.parseLong(p2);
progress = (i1 * 1000L)
+ (i2 * 100L);
int perm = (int) Math
.round((double) (progress * 1000L)
/ (double) duration);
if (perm > 1000) {
perm = 1000;
}
listener.progress(perm);
} catch (NumberFormatException e) {
;
}
}
}
}
lastWarning = null;
}
}
// }
}
// if (lastWarning != null) {
// if (!SUCCESS_PATTERN.matcher(lastWarning).matches()) {
// throw new EncoderException(lastWarning);
// }
// }
} catch (IOException e) {
throw new EncoderException(e);
} finally {
ffmpeg.destroy();
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* Encoding expection.
*
* @author Carlo Pelliccia
*/
public class EncoderException extends Exception {
private static final long serialVersionUID = 1L;
EncoderException() {
super();
}
EncoderException(String message) {
super(message);
}
EncoderException(Throwable cause) {
super(cause);
}
EncoderException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,55 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* Encoding progress listener interface. Instances of implementing classes could
* be used to listen an encoding process.
*
* @author Carlo Pelliccia
*/
public interface EncoderProgressListener {
/**
* This method is called before the encoding process starts, reporting
* information about the source stream that will be decoded and re-encoded.
*
* @param info
* Informations about the source multimedia stream.
*/
public void sourceInfo(MultimediaInfo info);
/**
* This method is called to notify a progress in the encoding process.
*
* @param permil
* A permil value representing the encoding process progress.
*/
public void progress(int permil);
/**
* This method is called every time the encoder need to send a message
* (usually, a warning).
*
* @param message
* The message sent by the encoder.
*/
public void message(String message);
}

View File

@@ -0,0 +1,181 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.Serializable;
/**
* Attributes controlling the encoding process.
*
* @author Carlo Pelliccia
*/
public class EncodingAttributes implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The format name for the encoded target multimedia file. Be sure this
* format is supported (see {@link Encoder#getSupportedEncodingFormats()}.
*/
private String format = null;
/**
* The start offset time (seconds). If null or not specified no start offset
* will be applied.
*/
private Float offset = null;
/**
* The duration (seconds) of the re-encoded stream. If null or not specified
* the source stream, starting from the offset, will be completely
* re-encoded in the target stream.
*/
private Float duration = null;
/**
* The attributes for the encoding of the audio stream in the target
* multimedia file. If null of not specified no audio stream will be
* encoded. It cannot be null if also the video field is null.
*/
private AudioAttributes audioAttributes = null;
/**
* The attributes for the encoding of the video stream in the target
* multimedia file. If null of not specified no video stream will be
* encoded. It cannot be null if also the audio field is null.
*/
private VideoAttributes videoAttributes = null;
/**
* Returns the format name for the encoded target multimedia file.
*
* @return The format name for the encoded target multimedia file.
*/
String getFormat() {
return format;
}
/**
* Sets the format name for the encoded target multimedia file. Be sure this
* format is supported (see {@link Encoder#getSupportedEncodingFormats()}.
*
* @param format
* The format name for the encoded target multimedia file.
*/
public void setFormat(String format) {
this.format = format;
}
/**
* Returns the start offset time (seconds).
*
* @return The start offset time (seconds).
*/
Float getOffset() {
return offset;
}
/**
* Sets the start offset time (seconds). If null or not specified no start
* offset will be applied.
*
* @param offset
* The start offset time (seconds).
*/
public void setOffset(Float offset) {
this.offset = offset;
}
/**
* Returns the duration (seconds) of the re-encoded stream.
*
* @return The duration (seconds) of the re-encoded stream.
*/
Float getDuration() {
return duration;
}
/**
* Sets the duration (seconds) of the re-encoded stream. If null or not
* specified the source stream, starting from the offset, will be completely
* re-encoded in the target stream.
*
* @param duration
* The duration (seconds) of the re-encoded stream.
*/
public void setDuration(Float duration) {
this.duration = duration;
}
/**
* Returns the attributes for the encoding of the audio stream in the target
* multimedia file.
*
* @return The attributes for the encoding of the audio stream in the target
* multimedia file.
*/
AudioAttributes getAudioAttributes() {
return audioAttributes;
}
/**
* Sets the attributes for the encoding of the audio stream in the target
* multimedia file. If null of not specified no audio stream will be
* encoded. It cannot be null if also the video field is null.
*
* @param audioAttributes
* The attributes for the encoding of the audio stream in the
* target multimedia file.
*/
public void setAudioAttributes(AudioAttributes audioAttributes) {
this.audioAttributes = audioAttributes;
}
/**
* Returns the attributes for the encoding of the video stream in the target
* multimedia file.
*
* @return The attributes for the encoding of the video stream in the target
* multimedia file.
*/
VideoAttributes getVideoAttributes() {
return videoAttributes;
}
/**
* Sets the attributes for the encoding of the video stream in the target
* multimedia file. If null of not specified no video stream will be
* encoded. It cannot be null if also the audio field is null.
*
* @param videoAttributes
* The attributes for the encoding of the video stream in the
* target multimedia file.
*/
public void setVideoAttributes(VideoAttributes videoAttributes) {
this.videoAttributes = videoAttributes;
}
public String toString() {
return getClass().getName() + "(format=" + format + ", offset="
+ offset + ", duration=" + duration + ", audioAttributes="
+ audioAttributes + ", videoAttributes=" + videoAttributes
+ ")";
}
}

View File

@@ -0,0 +1,178 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
/**
* A ffmpeg process wrapper.
*
* @author Carlo Pelliccia
*/
class FFMPEGExecutor {
/**
* The path of the ffmpeg executable.
*/
private String ffmpegExecutablePath;
/**
* Arguments for the executable.
*/
private ArrayList<String> args = new ArrayList<String>();
/**
* The process representing the ffmpeg execution.
*/
private Process ffmpeg = null;
/**
* A process killer to kill the ffmpeg process with a shutdown hook, useful
* if the jvm execution is shutted down during an ongoing encoding process.
*/
private ProcessKiller ffmpegKiller = null;
/**
* A stream reading from the ffmpeg process standard output channel.
*/
private InputStream inputStream = null;
/**
* A stream writing in the ffmpeg process standard input channel.
*/
private OutputStream outputStream = null;
/**
* A stream reading from the ffmpeg process standard error channel.
*/
private InputStream errorStream = null;
/**
* It build the executor.
*
* @param ffmpegExecutablePath
* The path of the ffmpeg executable.
*/
public FFMPEGExecutor(String ffmpegExecutablePath) {
this.ffmpegExecutablePath = ffmpegExecutablePath;
}
/**
* Adds an argument to the ffmpeg executable call.
*
* @param arg
* The argument.
*/
public void addArgument(String arg) {
args.add(arg);
}
/**
* Executes the ffmpeg process with the previous given arguments.
*
* @throws IOException
* If the process call fails.
*/
public void execute() throws IOException {
int argsSize = args.size();
String[] cmd = new String[argsSize + 1];
cmd[0] = ffmpegExecutablePath;
for (int i = 0; i < argsSize; i++) {
cmd[i + 1] = (String) args.get(i);
}
Runtime runtime = Runtime.getRuntime();
ffmpeg = runtime.exec(cmd);
ffmpegKiller = new ProcessKiller(ffmpeg);
runtime.addShutdownHook(ffmpegKiller);
inputStream = ffmpeg.getInputStream();
outputStream = ffmpeg.getOutputStream();
errorStream = ffmpeg.getErrorStream();
}
/**
* Returns a stream reading from the ffmpeg process standard output channel.
*
* @return A stream reading from the ffmpeg process standard output channel.
*/
public InputStream getInputStream() {
return inputStream;
}
/**
* Returns a stream writing in the ffmpeg process standard input channel.
*
* @return A stream writing in the ffmpeg process standard input channel.
*/
public OutputStream getOutputStream() {
return outputStream;
}
/**
* Returns a stream reading from the ffmpeg process standard error channel.
*
* @return A stream reading from the ffmpeg process standard error channel.
*/
public InputStream getErrorStream() {
return errorStream;
}
/**
* If there's a ffmpeg execution in progress, it kills it.
*/
public void destroy() {
if (inputStream != null) {
try {
inputStream.close();
} catch (Throwable t) {
;
}
inputStream = null;
}
if (outputStream != null) {
try {
outputStream.close();
} catch (Throwable t) {
;
}
outputStream = null;
}
if (errorStream != null) {
try {
errorStream.close();
} catch (Throwable t) {
;
}
errorStream = null;
}
if (ffmpeg != null) {
ffmpeg.destroy();
ffmpeg = null;
}
if (ffmpegKiller != null) {
Runtime runtime = Runtime.getRuntime();
runtime.removeShutdownHook(ffmpegKiller);
ffmpegKiller = null;
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* Abstract class whose derived concrete instances are used by {@link Encoder}
* to locate the ffmpeg executable path.
*
* @author Carlo Pelliccia
* @see Encoder
*/
public abstract class FFMPEGLocator {
/**
* This method should return the path of a ffmpeg executable suitable for
* the current machine.
*
* @return The path of the ffmpeg executable.
*/
protected abstract String getFFMPEGExecutablePath();
/**
* It returns a brand new {@link FFMPEGExecutor}, ready to be used in a
* ffmpeg call.
*
* @return A newly instanced {@link FFMPEGExecutor}, using this locator to
* call the ffmpeg executable.
*/
FFMPEGExecutor createExecutor() {
return new FFMPEGExecutor(getFFMPEGExecutablePath());
}
}

View File

@@ -0,0 +1,38 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* This expection is thrown if a source file format is not recognized.
*
* @author Carlo Pelliccia
*/
public class InputFormatException extends EncoderException {
private static final long serialVersionUID = 1L;
InputFormatException() {
super();
}
InputFormatException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,136 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* Instances of this class report informations about a decoded multimedia file.
*
* @author Carlo Pelliccia
*/
public class MultimediaInfo {
/**
* The multimedia file format name.
*/
private String format = null;
/**
* The stream duration in millis. If less than 0 this information is not
* available.
*/
private long duration = -1;
/**
* A set of audio-specific informations. If null, there's no audio stream in
* the multimedia file.
*/
private AudioInfo audio = null;
/**
* A set of video-specific informations. If null, there's no video stream in
* the multimedia file.
*/
private VideoInfo video = null;
/**
* Returns the multimedia file format name.
*
* @return The multimedia file format name.
*/
public String getFormat() {
return format;
}
/**
* Sets the multimedia file format name.
*
* @param format
* The multimedia file format name.
*/
void setFormat(String format) {
this.format = format;
}
/**
* Returns the stream duration in millis. If less than 0 this information is
* not available.
*
* @return The stream duration in millis. If less than 0 this information is
* not available.
*/
public long getDuration() {
return duration;
}
/**
* Sets the stream duration in millis.
*
* @param duration
* The stream duration in millis.
*/
void setDuration(long duration) {
this.duration = duration;
}
/**
* Returns a set of audio-specific informations. If null, there's no audio
* stream in the multimedia file.
*
* @return A set of audio-specific informations.
*/
public AudioInfo getAudio() {
return audio;
}
/**
* Sets a set of audio-specific informations.
*
* @param audio
* A set of audio-specific informations.
*/
void setAudio(AudioInfo audio) {
this.audio = audio;
}
/**
* Returns a set of video-specific informations. If null, there's no video
* stream in the multimedia file.
*
* @return A set of audio-specific informations.
*/
public VideoInfo getVideo() {
return video;
}
/**
* Sets a set of video-specific informations.
*
* @param video
* A set of video-specific informations.
*/
void setVideo(VideoInfo video) {
this.video = video;
}
public String toString() {
return getClass().getName() + " (format=" + format + ", duration="
+ duration + ", video=" + video + ", audio=" + audio + ")";
}
}

View File

@@ -0,0 +1,51 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* A package-private utility to add a shutdown hook to kill ongoing encoding
* processes at the jvm shutdown.
*
* @author Carlo Pelliccia
*/
class ProcessKiller extends Thread {
/**
* The process to kill.
*/
private Process process;
/**
* Builds the killer.
*
* @param process
* The process to kill.
*/
public ProcessKiller(Process process) {
this.process = process;
}
/**
* It kills the supplied process.
*/
public void run() {
process.destroy();
}
}

View File

@@ -0,0 +1,73 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
/**
* A package-private utility extending java.io.BufferedReader. If a line read
* with {@link RBufferedReader#readLine()} is not useful for the calling code,
* it can be re-inserted in the stream. The same line will be returned again at
* the next readLine() call.
*
* @author Carlo Pelliccia
*/
class RBufferedReader extends BufferedReader {
/**
* Re-inserted lines buffer.
*/
private ArrayList<String> lines = new ArrayList<String>();
/**
* It builds the reader.
*
* @param in
* The underlying reader.
*/
public RBufferedReader(Reader in) {
super(in);
}
/**
* It returns the next line in the stream.
*/
public String readLine() throws IOException {
if (lines.size() > 0) {
return (String) lines.remove(0);
} else {
return super.readLine();
}
}
/**
* Reinserts a line in the stream. The line will be returned at the next
* {@link RBufferedReader#readLine()} call.
*
* @param line
* The line.
*/
public void reinsertLine(String line) {
lines.add(0, line);
}
}

View File

@@ -0,0 +1,177 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.Serializable;
/**
* Attributes controlling the video encoding process.
*
* @author Carlo Pelliccia
*/
public class VideoAttributes implements Serializable {
private static final long serialVersionUID = 1L;
/**
* This value can be setted in the codec field to perform a direct stream
* copy, without re-encoding of the audio stream.
*/
public static final String DIRECT_STREAM_COPY = "copy";
/**
* The codec name for the encoding process. If null or not specified the
* encoder will perform a direct stream copy.
*/
private String codec = null;
/**
* The the forced tag/fourcc value for the video stream.
*/
private String tag = null;
/**
* The bitrate value for the encoding process. If null or not specified a
* default value will be picked.
*/
private Integer bitRate = null;
/**
* The frame rate value for the encoding process. If null or not specified a
* default value will be picked.
*/
private Integer frameRate = null;
/**
* The video size for the encoding process. If null or not specified the
* source video size will not be modified.
*/
private VideoSize size = null;
/**
* Returns the codec name for the encoding process.
*
* @return The codec name for the encoding process.
*/
String getCodec() {
return codec;
}
/**
* Sets the codec name for the encoding process. If null or not specified
* the encoder will perform a direct stream copy.
*
* Be sure the supplied codec name is in the list returned by
* {@link Encoder#getVideoEncoders()}.
*
* A special value can be picked from
* {@link VideoAttributes#DIRECT_STREAM_COPY}.
*
* @param codec
* The codec name for the encoding process.
*/
public void setCodec(String codec) {
this.codec = codec;
}
/**
* Returns the the forced tag/fourcc value for the video stream.
*
* @return The the forced tag/fourcc value for the video stream.
*/
String getTag() {
return tag;
}
/**
* Sets the forced tag/fourcc value for the video stream.
*
* @param tag
* The the forced tag/fourcc value for the video stream.
*/
public void setTag(String tag) {
this.tag = tag;
}
/**
* Returns the bitrate value for the encoding process.
*
* @return The bitrate value for the encoding process.
*/
Integer getBitRate() {
return bitRate;
}
/**
* Sets the bitrate value for the encoding process. If null or not specified
* a default value will be picked.
*
* @param bitRate
* The bitrate value for the encoding process.
*/
public void setBitRate(Integer bitRate) {
this.bitRate = bitRate;
}
/**
* Returns the frame rate value for the encoding process.
*
* @return The frame rate value for the encoding process.
*/
Integer getFrameRate() {
return frameRate;
}
/**
* Sets the frame rate value for the encoding process. If null or not
* specified a default value will be picked.
*
* @param frameRate
* The frame rate value for the encoding process.
*/
public void setFrameRate(Integer frameRate) {
this.frameRate = frameRate;
}
/**
* Returns the video size for the encoding process.
*
* @return The video size for the encoding process.
*/
VideoSize getSize() {
return size;
}
/**
* Sets the video size for the encoding process. If null or not specified
* the source video size will not be modified.
*
* @param size
* he video size for the encoding process.
*/
public void setSize(VideoSize size) {
this.size = size;
}
public String toString() {
return getClass().getName() + "(codec=" + codec + ", bitRate="
+ bitRate + ", frameRate=" + frameRate + ", size=" + size + ")";
}
}

View File

@@ -0,0 +1,133 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
/**
* Instances of this class report informations about a video stream that can be
* decoded.
*
* @author Carlo Pelliccia
*/
public class VideoInfo {
/**
* The video stream decoder name.
*/
private String decoder;
/**
* The video size. If null this information is not available.
*/
private VideoSize size = null;
/**
* The video stream (average) bit rate. If less than 0, this information is
* not available.
*/
private int bitRate = -1;
/**
* The video frame rate. If less than 0 this information is not available.
*/
private float frameRate = -1;
/**
* Returns the video stream decoder name.
*
* @return The video stream decoder name.
*/
public String getDecoder() {
return decoder;
}
/**
* Sets the video stream decoder name.
*
* @param decoder
* The video stream decoder name.
*/
void setDecoder(String codec) {
this.decoder = codec;
}
/**
* Returns the video size. If null this information is not available.
*
* @return the size The video size.
*/
public VideoSize getSize() {
return size;
}
/**
* Sets the video size.
*
* @param size
* The video size.
*/
void setSize(VideoSize size) {
this.size = size;
}
/**
* Returns the video frame rate. If less than 0 this information is not
* available.
*
* @return The video frame rate.
*/
public float getFrameRate() {
return frameRate;
}
/**
* Sets the video frame rate.
*
* @param frameRate
* The video frame rate.
*/
void setFrameRate(float frameRate) {
this.frameRate = frameRate;
}
/**
* Returns the video stream (average) bit rate. If less than 0, this
* information is not available.
*
* @return The video stream (average) bit rate.
*/
public int getBitRate() {
return bitRate;
}
/**
* Sets the video stream (average) bit rate.
*
* @param bitRate
* The video stream (average) bit rate.
*/
void setBitRate(int bitRate) {
this.bitRate = bitRate;
}
public String toString() {
return getClass().getName() + " (decoder=" + decoder + ", size=" + size
+ ", bitRate=" + bitRate + ", frameRate=" + frameRate + ")";
}
}

View File

@@ -0,0 +1,78 @@
/*
* JAVE - A Java Audio/Video Encoder (based on FFMPEG)
*
* Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.sauronsoftware.jave;
import java.io.Serializable;
/**
* Instances of this class report informations about videos size.
*
* @author Carlo Pelliccia
*/
public class VideoSize implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The video width.
*/
private int width;
/**
* The video height.
*/
private int height;
/**
* It builds the bean.
*
* @param width
* The video width.
* @param height
* The video height.
*/
public VideoSize(int width, int height) {
this.width = width;
this.height = height;
}
/**
* Returns the video width.
*
* @return The video width.
*/
public int getWidth() {
return width;
}
/**
* Returns the video height.
*
* @return The video height.
*/
public int getHeight() {
return height;
}
public String toString() {
return getClass().getName() + " (width=" + width + ", height=" + height
+ ")";
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,5 @@
<html>
<body>
<p>JAVE - Java Audio Video Encoder</p>
</body>
</html>

Binary file not shown.

View File

@@ -464,6 +464,9 @@ public class AudiobookRecorder extends JFrame {
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("R"), "startRecord"); 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 R"), "stopRecord");
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("T"), "startRecordNewPara");
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released T"), "stopRecord");
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released D"), "deleteLast"); centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released D"), "deleteLast");
centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("SPACE"), "startPlayback"); centralPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("SPACE"), "startPlayback");
@@ -477,6 +480,12 @@ public class AudiobookRecorder extends JFrame {
startRecording(); startRecording();
} }
}); });
centralPanel.getActionMap().put("startRecordNewPara", new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return;
startRecordingNewParagraph();
}
});
centralPanel.getActionMap().put("startRerecord", new AbstractAction() { centralPanel.getActionMap().put("startRerecord", new AbstractAction() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (bookTree.isEditing()) return; if (bookTree.isEditing()) return;
@@ -512,6 +521,18 @@ public class AudiobookRecorder extends JFrame {
pack(); pack();
setVisible(true); setVisible(true);
String lastBook = Options.get("path.last-book");
if (lastBook != null && !lastBook.equals("")) {
File f = new File(Options.get("path.storage"), lastBook);
if (f.exists() && f.isDirectory()) {
File x = new File(f, "audiobook.abk");
if (x.exists()) {
loadBookStructure(x);
}
}
}
} }
public static void main(String args[]) { public static void main(String args[]) {
@@ -538,6 +559,9 @@ public class AudiobookRecorder extends JFrame {
prefs.setProperty("book.author", info.getAuthor()); prefs.setProperty("book.author", info.getAuthor());
prefs.setProperty("book.genre", info.getGenre()); prefs.setProperty("book.genre", info.getGenre());
prefs.setProperty("book.comment", info.getComment()); prefs.setProperty("book.comment", info.getComment());
prefs.setProperty("chapter.audition.name", "Audition");
prefs.setProperty("chapter.audition.pre-gap", Options.get("catenation.pre-chapter"));
prefs.setProperty("chapter.audition.post-gap", Options.get("catenation.post-chapter"));
prefs.setProperty("chapter.open.name", "Opening Credits"); prefs.setProperty("chapter.open.name", "Opening Credits");
prefs.setProperty("chapter.open.pre-gap", Options.get("catenation.pre-chapter")); 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.open.post-gap", Options.get("catenation.post-chapter"));
@@ -573,6 +597,40 @@ public class AudiobookRecorder extends JFrame {
} }
} }
class JMenuObject2 extends JMenuItem {
Object ob1;
Object ob2;
public JMenuObject2(String p) {
super(p);
ob1 = null;
ob2 = null;
}
public JMenuObject2(String p, Object o1, Object o2) {
super(p);
ob1 = o1;
ob2 = o2;
}
public void setObject1(Object o) {
ob1 = o;
}
public void setObject2(Object o) {
ob2 = o;
}
public Object getObject1() {
return ob1;
}
public Object getObject2() {
return ob2;
}
}
@SuppressWarnings("unchecked")
void treePopup(MouseEvent e) { void treePopup(MouseEvent e) {
int selRow = bookTree.getRowForLocation(e.getX(), e.getY()); int selRow = bookTree.getRowForLocation(e.getX(), e.getY());
@@ -588,6 +646,52 @@ public class AudiobookRecorder extends JFrame {
JPopupMenu menu = new JPopupMenu(); JPopupMenu menu = new JPopupMenu();
JMenuObject rec = new JMenuObject("Recognise text from audio", s); JMenuObject rec = new JMenuObject("Recognise text from audio", s);
JMenu moveMenu = new JMenu("Move sentence to...");
for (Enumeration<Chapter> c = book.children(); c.hasMoreElements();) {
Chapter chp = c.nextElement();
JMenuObject2 m = new JMenuObject2(chp.getName(), s, chp);
m.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject2 ob = (JMenuObject2)e.getSource();
Sentence sentence = (Sentence)ob.getObject1();
Chapter target = (Chapter)ob.getObject2();
bookTreeModel.removeNodeFromParent(sentence);
bookTreeModel.insertNodeInto(sentence, target, target.getChildCount());
}
});
moveMenu.add(m);
}
JMenuObject moveUp = new JMenuObject("Move Up", s);
JMenuObject moveDown = new JMenuObject("Move Down", s);
moveUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Sentence sent = (Sentence)o.getObject();
Chapter chap = (Chapter)sent.getParent();
int pos = bookTreeModel.getIndexOfChild(chap, sent);
if (pos > 0) pos--;
bookTreeModel.removeNodeFromParent(sent);
bookTreeModel.insertNodeInto(sent, chap, pos);
}
});
moveDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Sentence sent = (Sentence)o.getObject();
Chapter chap = (Chapter)sent.getParent();
int pos = bookTreeModel.getIndexOfChild(chap, sent);
if (pos < chap.getChildCount() - 1) pos++;
bookTreeModel.removeNodeFromParent(sent);
bookTreeModel.insertNodeInto(sent, chap, pos);
}
});
JMenuObject ins = new JMenuObject("Insert sentence above", s); JMenuObject ins = new JMenuObject("Insert sentence above", s);
JMenuObject del = new JMenuObject("Delete sentence", s); JMenuObject del = new JMenuObject("Delete sentence", s);
@@ -632,6 +736,10 @@ public class AudiobookRecorder extends JFrame {
menu.add(rec); menu.add(rec);
menu.addSeparator(); menu.addSeparator();
menu.add(moveUp);
menu.add(moveDown);
menu.add(moveMenu);
menu.addSeparator();
menu.add(ins); menu.add(ins);
menu.add(del); menu.add(del);
menu.show(bookTree, e.getX(), e.getY()); menu.show(bookTree, e.getX(), e.getY());
@@ -641,18 +749,72 @@ public class AudiobookRecorder extends JFrame {
bookTree.setSelectionPath(new TreePath(c.getPath())); bookTree.setSelectionPath(new TreePath(c.getPath()));
JPopupMenu menu = new JPopupMenu(); JPopupMenu menu = new JPopupMenu();
JMenuObject ren = new JMenuObject("Rename chapter", c); JMenuObject peak = new JMenuObject("Auto-trim all (Peak)", c);
JMenuObject moveUp = new JMenuObject("Move Up", c);
JMenuObject moveDown = new JMenuObject("Move Down", c);
ren.addActionListener(new ActionListener() { int idNumber = s2i(c.getId());
moveUp.setEnabled(idNumber > 0);
moveDown.setEnabled(idNumber > 0);
moveUp.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource(); JMenuObject o = (JMenuObject)e.getSource();
Chapter c = (Chapter)o.getObject(); Chapter chap = (Chapter)o.getObject();
c.renameChapter(); int pos = bookTreeModel.getIndexOfChild(book, chap);
bookTreeModel.reload(c); if (pos > 0) pos--;
int id = s2i(chap.getId());
if (id > 0) {
Chapter prevChap = (Chapter)bookTreeModel.getChild(book, pos);
id = s2i(prevChap.getId());
if (id > 0) {
bookTreeModel.removeNodeFromParent(chap);
bookTreeModel.insertNodeInto(chap, book, pos);
}
}
}
});
moveDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter chap = (Chapter)o.getObject();
int pos = bookTreeModel.getIndexOfChild(book, chap);
pos++;
int id = s2i(chap.getId());
if (id > 0) {
Chapter nextChap = (Chapter)bookTreeModel.getChild(book, pos);
if (nextChap != null) {
id = s2i(nextChap.getId());
if (id > 0) {
bookTreeModel.removeNodeFromParent(chap);
bookTreeModel.insertNodeInto(chap, book, pos);
}
}
}
} }
}); });
menu.add(ren); peak.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMenuObject o = (JMenuObject)e.getSource();
Chapter c = (Chapter)o.getObject();
for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
Sentence snt = s.nextElement();
if (!snt.isLocked()) {
snt.autoTrimSamplePeak();
}
}
}
});
menu.add(moveUp);
menu.add(moveDown);
menu.addSeparator();
menu.add(peak);
menu.show(bookTree, e.getX(), e.getY()); menu.show(bookTree, e.getX(), e.getY());
} }
@@ -687,6 +849,53 @@ public class AudiobookRecorder extends JFrame {
} }
} }
public void startRecordingNewParagraph() {
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;
DefaultMutableTreeNode lastLeaf = c.getLastLeaf();
if (lastLeaf instanceof Sentence) {
Sentence lastSentence = (Sentence)lastLeaf;
lastSentence.setPostGap(Options.getInteger("catenation.post-paragraph"));
}
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 startRecording() { public void startRecording() {
if (recording != null) return; if (recording != null) return;
@@ -851,6 +1060,15 @@ public class AudiobookRecorder extends JFrame {
prefs.loadFromXML(fis); prefs.loadFromXML(fis);
buildBook(prefs); buildBook(prefs);
File r = f.getParentFile();
File cf = new File(r, "coverart.png");
if (cf.exists()) {
ImageIcon i = new ImageIcon(cf.getAbsolutePath());
Image ri = Utils.getScaledImage(i.getImage(), 22, 22);
book.setIcon(new ImageIcon(ri));
bookTreeModel.reload(book);
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@@ -950,7 +1168,25 @@ public class AudiobookRecorder extends JFrame {
mainScroll.setViewportView(bookTree); mainScroll.setViewportView(bookTree);
Chapter c = new Chapter("open", prefs.getProperty("chapter.open.name")); 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")));
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)));
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))));
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.setPostGap(s2i(prefs.getProperty("chapter.open.post-gap")));
c.setPreGap(s2i(prefs.getProperty("chapter.open.pre-gap"))); c.setPreGap(s2i(prefs.getProperty("chapter.open.pre-gap")));
bookTreeModel.insertNodeInto(c, book, 0); bookTreeModel.insertNodeInto(c, book, 0);
@@ -968,6 +1204,8 @@ public class AudiobookRecorder extends JFrame {
bookTreeModel.insertNodeInto(s, c, c.getChildCount()); bookTreeModel.insertNodeInto(s, c, c.getChildCount());
} }
for (int cno = 1; cno < 10000; cno++) { for (int cno = 1; cno < 10000; cno++) {
String cname = prefs.getProperty(String.format("chapter.%04d.name", cno)); String cname = prefs.getProperty(String.format("chapter.%04d.name", cno));
if (cname == null) break; if (cname == null) break;
@@ -1013,6 +1251,7 @@ public class AudiobookRecorder extends JFrame {
toolBar.enableBook(); toolBar.enableBook();
statusLabel.setText("Noise floor: " + getNoiseFloor()); statusLabel.setText("Noise floor: " + getNoiseFloor());
book.setIcon(Icons.book);
} }
public void openBook() { public void openBook() {
@@ -1038,6 +1277,9 @@ public class AudiobookRecorder extends JFrame {
loadBookStructure(f); loadBookStructure(f);
Options.set("path.last-book", book.getName());
Options.savePreferences();
} }
@@ -1172,13 +1414,11 @@ public class AudiobookRecorder extends JFrame {
int fullLength = 0; int fullLength = 0;
Chapter c = o.nextElement(); Chapter c = o.nextElement();
if (c.getChildCount() == 0) continue;
String name = c.getName(); String name = c.getName();
File exportFile = new File(export, name + ".wax"); File exportFile = new File(export, name + ".wax");
File wavFile = new File(export, name + ".wav"); 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); FileOutputStream fos = new FileOutputStream(exportFile);
@@ -1205,11 +1445,29 @@ public class AudiobookRecorder extends JFrame {
FileInputStream fis = new FileInputStream(exportFile); FileInputStream fis = new FileInputStream(exportFile);
AudioInputStream ais = new AudioInputStream(fis, format, fullLength); AudioInputStream ais = new AudioInputStream(fis, format, fullLength);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, wavFile); fos = new FileOutputStream(wavFile);
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, fos);
fos.flush();
fos.close();
fis.close(); fis.close();
exportFile.delete(); exportFile.delete();
}
for (Enumeration<Chapter> o = book.children(); o.hasMoreElements();) {
Chapter c = o.nextElement();
if (c.getChildCount() == 0) continue;
String name = c.getName();
File wavFile = new File(export, name + ".wav");
File mp3File = new File(export, name + "-untagged.mp3");
File taggedFile = new File(export, name + ".mp3");
System.err.println(attributes);
encoder.encode(wavFile, mp3File, attributes); encoder.encode(wavFile, mp3File, attributes);
Mp3File id3 = new Mp3File(mp3File); Mp3File id3 = new Mp3File(mp3File);
ID3v2 tags = new ID3v24Tag(); ID3v2 tags = new ID3v24Tag();
@@ -1217,6 +1475,7 @@ public class AudiobookRecorder extends JFrame {
tags.setTrack(Integer.toString(s2i(c.getId()) - 0)); tags.setTrack(Integer.toString(s2i(c.getId()) - 0));
tags.setTitle(c.getName()); tags.setTitle(c.getName());
tags.setAlbum(book.getName());
tags.setArtist(book.getAuthor()); tags.setArtist(book.getAuthor());
// ID3v2TextFrameData g = new ID3v2TextFrameData(false, new EncodedText(book.getGenre())); // ID3v2TextFrameData g = new ID3v2TextFrameData(false, new EncodedText(book.getGenre()));
@@ -1294,7 +1553,6 @@ public class AudiobookRecorder extends JFrame {
bookTree.setSelectionPath(new TreePath(s.getPath())); bookTree.setSelectionPath(new TreePath(s.getPath()));
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
playing = null; playing = null;
} }
toolBar.enableSentence(); toolBar.enableSentence();

View File

@@ -16,6 +16,8 @@ public class Book extends DefaultMutableTreeNode {
String genre; String genre;
String comment; String comment;
ImageIcon icon;
public Book(String bookname) { public Book(String bookname) {
super(bookname); super(bookname);
name = bookname; name = bookname;
@@ -99,4 +101,39 @@ public class Book extends DefaultMutableTreeNode {
public String getName() { public String getName() {
return name; return name;
} }
public ImageIcon getIcon() {
return icon;
}
public void setIcon(ImageIcon i) {
icon = i;
}
public void setUserObject(Object o) {
if (o instanceof String) {
String newName = (String)o;
File oldDir = new File(Options.get("path.storage"), name);
File newDir = new File(Options.get("path.storage"), newName);
if (newDir.exists()) {
JOptionPane.showMessageDialog(AudiobookRecorder.window, "Book already exists", "Error", JOptionPane.ERROR_MESSAGE);
return;
}
if (oldDir.exists() && oldDir.isDirectory()) {
oldDir.renameTo(newDir);
name = newName;
AudiobookRecorder.window.saveBookStructure();
AudiobookRecorder.window.bookTreeModel.reload(this);
Options.set("path.last-book", name);
Options.savePreferences();
}
}
}
public String toString() {
return name;
}
} }

View File

@@ -0,0 +1,96 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.border.*;
import java.util.*;
import java.io.*;
public class BookPanel extends JPanel {
String name;
String author;
String genre;
String comment;
ImageIcon cover;
Image resizedCover;
JLabel iconLabel;
JLabel titleLabel;
JLabel authorLabel;
JLabel otherLabel;
JPanel panel;
File root;
boolean highlight = false;
public BookPanel(File r) {
try {
root = r;
Properties props = new Properties();
props.loadFromXML(new FileInputStream(new File(root, "audiobook.abk")));
name = props.getProperty("book.name");
author = props.getProperty("book.author");
genre = props.getProperty("book.genre");
comment = props.getProperty("book.comment");
File icon = new File(root, "coverart.png");
if (icon.exists()) {
cover = new ImageIcon(icon.getAbsolutePath());
resizedCover = Utils.getScaledImage(cover.getImage(), 75, 75);
iconLabel = new JLabel(new ImageIcon(resizedCover));
} else {
cover = null;
resizedCover = null;
iconLabel = new JLabel("");
}
iconLabel.setSize(new Dimension(75, 75));
iconLabel.setPreferredSize(new Dimension(75, 75));
titleLabel = new JLabel(name);
authorLabel = new JLabel(author);
otherLabel = new JLabel(genre + " :: " + comment);
authorLabel.setForeground(new Color(0x80, 0x80, 0x80));
otherLabel.setForeground(new Color(0x80, 0x80, 0x80));
setLayout(new BorderLayout());
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
panel.add(titleLabel);
panel.add(authorLabel);
panel.add(otherLabel);
add(iconLabel, BorderLayout.WEST);
add(panel, BorderLayout.CENTER);
panel.setBackground(new Color(0x20, 0x20, 0x20));
panel.setOpaque(true);
setBackground(new Color(0x20, 0x20, 0x20));
setOpaque(true);
} catch (Exception e) {
}
}
public File getConfigFile() {
return new File(root, "audiobook.abk");
}
public void highlight() {
setBackground(new Color(0x00, 0x20, 0x40));
panel.setBackground(new Color(0x00, 0x20, 0x40));
}
public void lowlight() {
setBackground(new Color(0x20, 0x20, 0x20));
panel.setBackground(new Color(0x20, 0x20, 0x20));
}
}

View File

@@ -19,7 +19,7 @@ public class BookTreeRenderer extends DefaultTreeCellRenderer {
} else if (value instanceof Chapter) { } else if (value instanceof Chapter) {
ret.setIcon(Icons.chapter); ret.setIcon(Icons.chapter);
} else if (value instanceof Book) { } else if (value instanceof Book) {
ret.setIcon(Icons.book); ret.setIcon(((Book)value).getIcon());
} }
return ret; return ret;
} }

View File

@@ -37,13 +37,6 @@ public class Chapter extends DefaultMutableTreeNode {
return null; return null;
} }
public void renameChapter() {
String n = JOptionPane.showInputDialog(null, "Rename Chapter", name);
if (n != null) {
name = n;
}
}
public String toString() { public String toString() {
return name; return name;
} }

View File

@@ -28,13 +28,28 @@ public class OpenBookPanel extends JPanel {
} }
} }
public class BookCellRenderer implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value == null) return null;
BookPanel p = (BookPanel)value;
if (isSelected) {
p.highlight();
} else {
p.lowlight();
}
return p;
}
};
class BookTableModel extends AbstractTableModel { class BookTableModel extends AbstractTableModel {
ArrayList<BookInfo> books; ArrayList<BookPanel> books;
public BookTableModel() { public BookTableModel() {
super(); super();
books = new ArrayList<BookInfo>(); books = new ArrayList<BookPanel>();
} }
public int getRowCount() { public int getRowCount() {
@@ -42,38 +57,27 @@ public class OpenBookPanel extends JPanel {
} }
public int getColumnCount() { public int getColumnCount() {
return 4; return 1;
} }
public boolean isCellEditable(int row, int column) { public boolean isCellEditable(int row, int column) {
return false; return false;
} }
public void addBook(BookInfo b) { public void addBook(BookPanel b) {
books.add(b); books.add(b);
} }
public Object getValueAt(int r, int c) { public Object getValueAt(int r, int c) {
if (c > 3) return null; return books.get(r);
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) { public String getColumnName(int i) {
switch(i) { return "Book";
case 0: return "Name";
case 1: return "Author";
case 2: return "Genre";
case 3: return "Comment";
} }
return null;
public Class getColumnClass(int i) {
return BookPanel.class;
} }
} }
@@ -85,7 +89,6 @@ public class OpenBookPanel extends JPanel {
model = new BookTableModel(); model = new BookTableModel();
setLayout(new BorderLayout()); setLayout(new BorderLayout());
scroll = new JScrollPane(); scroll = new JScrollPane();
add(scroll, BorderLayout.CENTER); add(scroll, BorderLayout.CENTER);
@@ -97,22 +100,17 @@ public class OpenBookPanel extends JPanel {
if (!b.isDirectory()) continue; if (!b.isDirectory()) continue;
File xml = new File(b, "audiobook.abk"); File xml = new File(b, "audiobook.abk");
if (xml.exists()) { if (xml.exists()) {
Properties props = new Properties(); BookPanel book = new BookPanel(b);
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); model.addBook(book);
} }
} }
table = new JTable(model); table = new JTable(model);
table.setDefaultRenderer(BookPanel.class, new BookCellRenderer());
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowHeight(80);
table.getTableHeader().setUI(null);
scroll.setViewportView(table); scroll.setViewportView(table);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@@ -126,9 +124,7 @@ public class OpenBookPanel extends JPanel {
return null; return null;
} }
String name = (String)table.getValueAt(sel, 0); BookPanel b = (BookPanel)table.getValueAt(sel, 0);
File d = new File(Options.get("path.storage"), name); return b.getConfigFile();
File f = new File(d, "audiobook.abk");
return f;
} }
} }

View File

@@ -21,6 +21,7 @@ public class Options extends JDialog {
JSpinner preChapterGap; JSpinner preChapterGap;
JSpinner postChapterGap; JSpinner postChapterGap;
JSpinner postSentenceGap; JSpinner postSentenceGap;
JSpinner postParagraphGap;
JTextField ffmpegLocation; JTextField ffmpegLocation;
JComboBox<KVPair> bitRate; JComboBox<KVPair> bitRate;
JComboBox<KVPair> exportRate; JComboBox<KVPair> exportRate;
@@ -190,6 +191,7 @@ public class Options extends JDialog {
preChapterGap = addSpinner("Default pre-chapter gap:", 0, 5000, 100, getInteger("catenation.pre-chapter"), "ms"); 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"); 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"); postSentenceGap = addSpinner("Default post-sentence gap:", 0, 5000, 100, getInteger("catenation.post-sentence"), "ms");
postParagraphGap = addSpinner("Default post-paragraph gap:", 0, 5000, 100, getInteger("catenation.post-paragraph"), "ms");
addSeparator(); addSeparator();
@@ -277,7 +279,7 @@ public class Options extends JDialog {
} }
if (supported) { if (supported) {
KVPair p = new KVPair(i.getName(), i.getDescription()); KVPair p = new KVPair(i.getName(), i.getName()); //i.getDescription());
list.add(p); list.add(p);
} }
} }
@@ -316,7 +318,7 @@ public class Options extends JDialog {
if (supported) { if (supported) {
KVPair p = new KVPair(i.getName(), i.getDescription()); KVPair p = new KVPair(i.getName(), i.getName()); //i.getDescription());
list.add(p); list.add(p);
} }
} }
@@ -361,6 +363,7 @@ public class Options extends JDialog {
defaultPrefs.put("catenation.pre-chapter", "2000"); defaultPrefs.put("catenation.pre-chapter", "2000");
defaultPrefs.put("catenation.post-chapter", "2000"); defaultPrefs.put("catenation.post-chapter", "2000");
defaultPrefs.put("catenation.post-sentence", "1000"); defaultPrefs.put("catenation.post-sentence", "1000");
defaultPrefs.put("catenation.post-paragraph", "2000");
defaultPrefs.put("path.storage", (new File(System.getProperty("user.home"), "Recordings")).toString()); defaultPrefs.put("path.storage", (new File(System.getProperty("user.home"), "Recordings")).toString());
defaultPrefs.put("path.ffmpeg", ""); defaultPrefs.put("path.ffmpeg", "");
@@ -426,6 +429,7 @@ public class Options extends JDialog {
set("catenation.pre-chapter", "" + preChapterGap.getValue()); set("catenation.pre-chapter", "" + preChapterGap.getValue());
set("catenation.post-chapter", "" + postChapterGap.getValue()); set("catenation.post-chapter", "" + postChapterGap.getValue());
set("catenation.post-sentence", "" + postSentenceGap.getValue()); set("catenation.post-sentence", "" + postSentenceGap.getValue());
set("catenation.post-paragraph", "" + postParagraphGap.getValue());
set("audio.export.bitrate", ((KVPair)bitRate.getSelectedItem()).key); set("audio.export.bitrate", ((KVPair)bitRate.getSelectedItem()).key);
set("audio.export.samplerate", ((KVPair)exportRate.getSelectedItem()).key); set("audio.export.samplerate", ((KVPair)exportRate.getSelectedItem()).key);
set("process.sphinx", enableParsing.isSelected() ? "true" : "false"); set("process.sphinx", enableParsing.isSelected() ? "true" : "false");

View File

@@ -222,6 +222,8 @@ public class Sentence extends DefaultMutableTreeNode {
startOffset = 0; startOffset = 0;
} }
startOffset -= 4096;
for (int i = samples.length-1; i >= 0; i--) { for (int i = samples.length-1; i >= 0; i--) {
endOffset = i; endOffset = i;
if (Math.abs(samples[i]) > noiseFloor) { if (Math.abs(samples[i]) > noiseFloor) {
@@ -230,10 +232,16 @@ public class Sentence extends DefaultMutableTreeNode {
break; break;
} }
} }
endOffset += 4096;
if (endOffset <= startOffset) endOffset = startOffset + 4096; if (endOffset <= startOffset) endOffset = startOffset + 4096;
if (endOffset <= 0) { if (endOffset <= 0) {
endOffset = samples.length-1; endOffset = samples.length-1;
} }
if (startOffset < 0) startOffset = 0;
if (endOffset >= samples.length) endOffset = samples.length-1;
} }
public String getId() { public String getId() {

View File

@@ -0,0 +1,23 @@
package uk.co.majenko.audiobookrecorder;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.border.*;
import java.util.*;
import java.io.*;
public class Utils {
public static Image getScaledImage(Image srcImg, int w, int h){
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
}
}