Compare commits

...

7 Commits

28 changed files with 2741 additions and 21 deletions

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.

BIN
deps/jave-1.0.2.jar vendored

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 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"));
@@ -641,18 +665,22 @@ 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);
ren.addActionListener(new ActionListener() { peak.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 c = (Chapter)o.getObject();
c.renameChapter(); for (Enumeration<Sentence> s = c.children(); s.hasMoreElements();) {
bookTreeModel.reload(c); Sentence snt = s.nextElement();
if (!snt.isLocked()) {
snt.autoTrimSamplePeak();
}
}
} }
}); });
menu.add(ren); menu.add(peak);
menu.show(bookTree, e.getX(), e.getY()); menu.show(bookTree, e.getX(), e.getY());
} }
@@ -687,6 +715,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;
@@ -950,7 +1025,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 +1061,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;
@@ -1037,6 +1132,9 @@ public class AudiobookRecorder extends JFrame {
} }
loadBookStructure(f); loadBookStructure(f);
Options.set("path.last-book", book.getName());
Options.savePreferences();
} }
@@ -1172,13 +1270,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 +1301,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 +1331,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 +1409,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

@@ -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

@@ -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() {