package be.tarsos.transcoder;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import be.tarsos.transcoder.ffmpeg.Encoder;
import be.tarsos.transcoder.ffmpeg.EncoderException;
import be.tarsos.transcoder.ffmpeg.InputFormatException;
import be.tarsos.transcoder.ffmpeg.LinuxFFMPEGLocator;
import be.tarsos.transcoder.ffmpeg.MacFFMPEGLocator;
import be.tarsos.transcoder.ffmpeg.PathFFMPEGLocator;
import be.tarsos.transcoder.ffmpeg.WindowsFFMPEGLocator;
/**
* The main interface to transcode audio.
*
* @author Joren Six
*/
public class Transcoder {
private static final Logger LOG = Logger.getLogger(Transcoder.class.getName());
/**
* Adds default locators to encoder.
*/
private static void initialize() {
if(!Encoder.hasLocators()) {
Encoder.addFFMPEGLocator(new WindowsFFMPEGLocator());
Encoder.addFFMPEGLocator(new MacFFMPEGLocator());
Encoder.addFFMPEGLocator(new LinuxFFMPEGLocator());
Encoder.addFFMPEGLocator(new PathFFMPEGLocator());
}
}
/**
* A private constructor to hide the default.
*/
private Transcoder() {
}
/**
* Transcodes audio. It converts source to target with the defined
* attributes.
*
* @param source
* The path to the source audio file.
* @param target
* The path to the target audio file.
* @param targetEncoding
* A description of the encoding parameters.
* @throws EncoderException
* If something goes wrong in the encoding process.
*/
public static void transcode(final String source, final String target, final Attributes targetEncoding)
throws EncoderException {
transcode(new File(source), new File(target), targetEncoding);
}
/**
* Transcodes audio. It converts source to target with the defined
* attributes.
*
* @param source
* The path to the source audio file.
* @param target
* The path to the target audio file.
* @param targetEncoding
* A description of the encoding parameters.
* @throws EncoderException
* If something goes wrong in the encoding process.
*/
public static void transcode(final String source, final String target,
final DefaultAttributes targetEncoding) throws EncoderException {
transcode(source, target, targetEncoding.getAttributes());
}
/**
* Transcodes audio. It converts source to target with the defined
* attributes.
*
* @param source
* The path to the source audio file.
* @param target
* The path to the target audio file.
* @param targetEncoding
* A description of the encoding parameters.
* @throws EncoderException
* If something goes wrong in the encoding process.
*/
public static void transcode(final File source, final File target, final DefaultAttributes targetEncoding)
throws EncoderException {
transcode(source, target, targetEncoding.getAttributes());
}
/**
* Transcodes audio. It converts source to target with the defined
* attributes.
*
* @param source
* The path to the source audio file.
* @param target
* The path to the target audio file.
* @param targetEncoding
* A description of the encoding parameters.
* @throws EncoderException
* If something goes wrong in the encoding process.
*/
public static void transcode(final File source, final File target, final Attributes targetEncoding)
throws EncoderException {
// sanity checks
if (!source.exists()) {
throw new IllegalArgumentException(source + " does not exist. It should"
+ " be a readable audiofile.");
}
if (source.isDirectory()) {
throw new IllegalArgumentException(source + " is a directory. It should "
+ "be a readable audiofile.");
}
if (!source.canRead()) {
throw new IllegalArgumentException(source
+ " can not be read, check file permissions. It should be a readable audiofile.");
}
initialize();
// encode the source to directory
final Encoder e = new Encoder();
LOG.info("Try to transcode " + source + " to " + target);
e.encode(source, target, targetEncoding);
LOG.info("Successfully transcoded " + source + " to " + target);
}
/**
* Checks if transcoding is required: it fetches information about the file
* 'target' and checks if the file has the expected format, number of
* channels and sampling rate. If not then transcoding is required. If
* target is not found then transcoding is also required.
*
* @param target
* the path to the transcoded file or file to transcode
* @param targetEncoding
* The target encoding attributes. It defines the number of
* channels, format, sampling rate,... the transcoded file should
* have.
* @return false if the file is already transcoded as per the requested
* parameters, false otherwise.
*/
public static boolean transcodingRequired(final String target, final Attributes targetEncoding) {
initialize();
final File targetFile = new File(target);
// if the file does not exist transcoding is always required
boolean transcodingRequired = !targetFile.exists();
// if the file exists, check the format
if (targetFile.exists()) {
// no need to transcode if attributes match.
transcodingRequired = !matchesEncoding(target, targetEncoding);
}
return transcodingRequired;
}
/**
* Check if target matches encoding attributes defined by attributes.
*
* @param target
* The path to the target.
* @param targetEncoding
* The encoding attributes.
* @return True if target matches the encoding, false otherwise.
*/
private static boolean matchesEncoding(final String target, final Attributes targetEncoding) {
final Attributes info = getInfo(target);
final int currentSamplingRate = info.getSamplingRate();
final int currentNumberOfChannels = info.getChannels();
final String currentDecoder = info.getFormat();
final boolean samplingRateMatches = currentSamplingRate == targetEncoding.getSamplingRate();
final boolean numberOfChannelsMatches = currentNumberOfChannels == targetEncoding.getChannels();
final boolean codecMatches = currentDecoder.equalsIgnoreCase(targetEncoding.getCodec());
return samplingRateMatches && numberOfChannelsMatches && codecMatches;
}
/**
* Returns information about an audio file: the sampling rate, the number of
* channels, the decoder, ...
*
* @param file
* the file to get the info for
* @return the info for the file.
*/
public static Attributes getInfo(final String file) {
initialize();
Attributes info = null;
try {
final Encoder e = new Encoder();
info = e.getInfo(new File(file));
} catch (final InputFormatException e1) {
LOG.severe("Unknown input file format: " + file);
} catch (final EncoderException e1) {
LOG.warning("Could not get information about:" + file);
}
return info;
}
public static void play(String source) throws EncoderException, LineUnavailableException, IOException{
SourceDataLine line;
DataLine.Info info;
//Set the transcoding to WAV PCM, 16bits LE
Attributes attributes = DefaultAttributes.WAV_PCM_S16LE_STEREO_44KHZ.getAttributes();
//Stream the same file with on the fly decoding:
AudioInputStream streamedAudioInputStream = Streamer.stream(source, attributes);
AudioFormat audioFormat = Streamer.streamAudioFormat(attributes);
byte[] streamBuffer = new byte[1024];
info = new DataLine.Info(SourceDataLine.class, audioFormat);
line = (SourceDataLine) AudioSystem.getLine(info);
line.open(audioFormat);
line.start();
while (streamedAudioInputStream.available() > streamBuffer.length){
int bytesRead = streamedAudioInputStream.read(streamBuffer);
int bytesWrote = line.write(streamBuffer, 0, streamBuffer.length);
assert bytesRead == bytesWrote;
}
line.close();
streamedAudioInputStream.close();
}
public static void main(String args[]) {
if( args.length ==2 && args[0].equalsIgnoreCase("play")){
try {
play(args[1]);
} catch (Exception e) {
System.out.println("Error while playing the stream:" + e.getMessage());
}
}else if (args.length != 3) {
printHelp();
} else {
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
DefaultAttributes encodingAttributes = null;
try {
encodingAttributes = DefaultAttributes.valueOf(args[2]);
} catch (IllegalArgumentException e) {
System.err.println("Please make sure " + args[2]
+ " is a correct value\n");
printHelp();
}
if (inputFile.exists() && inputFile.canRead()
&& encodingAttributes != null) {
try {
transcode(inputFile, outputFile, encodingAttributes);
} catch (EncoderException e) {
System.err.println("Transcoding error: make sure input "
+ inputFile + " is an audio file.");
}
} else {
System.err.println("Make sure input " + inputFile
+ " is readable and output " + outputFile
+ " writable. Also check encoding attributes: "
+ args[2]);
System.out.println();
printHelp();
}
}
}
private static void printHelp() {
System.out.println("USAGE: java -jar transcoder.jar inputfile outputfile encoding");
System.out.println(" with encoding one of:");
for (DefaultAttributes attribute : DefaultAttributes.values()) {
System.out.println(" " + attribute.name());
}
}
}