Compare commits
17 Commits
v0.0.0-69e
...
v0.0.3-a8b
| Author | SHA1 | Date | |
|---|---|---|---|
| a8b8def6ca | |||
| aa9d7993d6 | |||
| 6157eea4da | |||
| 8cc7614a68 | |||
| 4d928a9e26 | |||
| ff0dc30375 | |||
| fab564e0e4 | |||
| 27e72dc2d8 | |||
| 75a684d29f | |||
| b3f57c45af | |||
| fef1d19ddd | |||
| 08408d2ae5 | |||
| 8380128bc8 | |||
| e01bc29ef0 | |||
| 415b93931b | |||
| 3f42a13afd | |||
| 04e4447b39 |
@@ -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
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|

|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
7
TODO.md
Normal file
7
TODO.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
ToDo
|
||||||
|
====
|
||||||
|
|
||||||
|
* Merge chapters
|
||||||
|
* Identify and export 5 minute retail sample
|
||||||
|
* Identify and export 15 minute checkpoint
|
||||||
|
* Archive (Zip and delete) audiobooks
|
||||||
BIN
deps/jave-1.0.2.jar
vendored
BIN
deps/jave-1.0.2.jar
vendored
Binary file not shown.
26
installers/linux.sh
Executable file
26
installers/linux.sh
Executable 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"
|
||||||
BIN
resources/uk/co/majenko/audiobookrecorder/icons/appIcon.png
Normal file
BIN
resources/uk/co/majenko/audiobookrecorder/icons/appIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
182
src/it/sauronsoftware/jave/AudioAttributes.java
Normal file
182
src/it/sauronsoftware/jave/AudioAttributes.java
Normal 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 + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
137
src/it/sauronsoftware/jave/AudioInfo.java
Normal file
137
src/it/sauronsoftware/jave/AudioInfo.java
Normal 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 + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
141
src/it/sauronsoftware/jave/DefaultFFMPEGLocator.java
Normal file
141
src/it/sauronsoftware/jave/DefaultFFMPEGLocator.java
Normal 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) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
938
src/it/sauronsoftware/jave/Encoder.java
Normal file
938
src/it/sauronsoftware/jave/Encoder.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
46
src/it/sauronsoftware/jave/EncoderException.java
Normal file
46
src/it/sauronsoftware/jave/EncoderException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
55
src/it/sauronsoftware/jave/EncoderProgressListener.java
Normal file
55
src/it/sauronsoftware/jave/EncoderProgressListener.java
Normal 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);
|
||||||
|
|
||||||
|
}
|
||||||
181
src/it/sauronsoftware/jave/EncodingAttributes.java
Normal file
181
src/it/sauronsoftware/jave/EncodingAttributes.java
Normal 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
|
||||||
|
+ ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
178
src/it/sauronsoftware/jave/FFMPEGExecutor.java
Normal file
178
src/it/sauronsoftware/jave/FFMPEGExecutor.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
49
src/it/sauronsoftware/jave/FFMPEGLocator.java
Normal file
49
src/it/sauronsoftware/jave/FFMPEGLocator.java
Normal 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
38
src/it/sauronsoftware/jave/InputFormatException.java
Normal file
38
src/it/sauronsoftware/jave/InputFormatException.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
136
src/it/sauronsoftware/jave/MultimediaInfo.java
Normal file
136
src/it/sauronsoftware/jave/MultimediaInfo.java
Normal 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 + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
51
src/it/sauronsoftware/jave/ProcessKiller.java
Normal file
51
src/it/sauronsoftware/jave/ProcessKiller.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
73
src/it/sauronsoftware/jave/RBufferedReader.java
Normal file
73
src/it/sauronsoftware/jave/RBufferedReader.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
177
src/it/sauronsoftware/jave/VideoAttributes.java
Normal file
177
src/it/sauronsoftware/jave/VideoAttributes.java
Normal 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 + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
133
src/it/sauronsoftware/jave/VideoInfo.java
Normal file
133
src/it/sauronsoftware/jave/VideoInfo.java
Normal 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 + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
78
src/it/sauronsoftware/jave/VideoSize.java
Normal file
78
src/it/sauronsoftware/jave/VideoSize.java
Normal 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
|
||||||
|
+ ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
src/it/sauronsoftware/jave/ffmpeg
Normal file
BIN
src/it/sauronsoftware/jave/ffmpeg
Normal file
Binary file not shown.
BIN
src/it/sauronsoftware/jave/ffmpeg.exe
Normal file
BIN
src/it/sauronsoftware/jave/ffmpeg.exe
Normal file
Binary file not shown.
5
src/it/sauronsoftware/jave/package.html
Normal file
5
src/it/sauronsoftware/jave/package.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<p>JAVE - Java Audio Video Encoder</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
src/it/sauronsoftware/jave/pthreadGC2.dll
Normal file
BIN
src/it/sauronsoftware/jave/pthreadGC2.dll
Normal file
Binary file not shown.
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
96
src/uk/co/majenko/audiobookrecorder/BookPanel.java
Normal file
96
src/uk/co/majenko/audiobookrecorder/BookPanel.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
public Class getColumnClass(int i) {
|
||||||
case 3: return "Comment";
|
return BookPanel.class;
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
23
src/uk/co/majenko/audiobookrecorder/Utils.java
Normal file
23
src/uk/co/majenko/audiobookrecorder/Utils.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user