/*
* Copyright (c) 2008, 2009, 2010, 2011 Denis Tulskiy
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3 along with this work. If not, see <http://www.gnu.org/licenses/>.
*/
package com.tulskiy.musique.audio;
import com.tulskiy.musique.playlist.Track;
import com.tulskiy.musique.playlist.formatting.Parser;
import com.tulskiy.musique.playlist.formatting.tokens.Expression;
import com.tulskiy.musique.system.Application;
import com.tulskiy.musique.system.Codecs;
import com.tulskiy.musique.system.TrackIO;
import com.tulskiy.musique.system.configuration.Configuration;
import com.tulskiy.musique.util.AudioMath;
import javax.swing.*;
import java.io.File;
import java.util.List;
import java.util.logging.Logger;
/**
* Author: Denis Tulskiy
* Date: Jul 27, 2010
*/
public class Converter {
private Logger logger = Logger.getLogger(getClass().getName());
private Configuration config = Application.getInstance().getConfiguration();
private Expression fileNameFormat;
private Encoder encoder;
private Decoder decoder;
private Track track;
private boolean stop;
private long cueTotalBytes;
private long currentByte;
private long currentSample;
private long totalSamples;
private long startTime;
private long elapsed;
private double speed;
private double estimated;
private File output;
public Converter() {
String fileName = config.getString("converter.fileNameFormat", "%fileName%");
fileNameFormat = Parser.parse(fileName);
}
public void convert(List<Track> tracks) {
stop = false;
totalSamples = 0;
currentSample = 0;
startTime = System.currentTimeMillis();
boolean merge = config.getBoolean("converter.merge", false);
for (Track track : tracks) {
totalSamples += track.getTrackData().getTotalSamples();
}
byte[] buf = new byte[65536];
for (Track track : tracks) {
if (stop)
break;
this.track = track;
if (!openDecoder()) {
currentSample += track.getTrackData().getTotalSamples();
continue;
}
if (!merge || encoder == null) {
if (!openEncoder()) {
currentSample += track.getTrackData().getTotalSamples();
continue;
}
}
int len;
boolean cueFinished = false;
while (!stop && !cueFinished) {
len = decoder.decode(buf);
if (len == -1)
break;
if (track.getTrackData().isCue()) {
if (cueTotalBytes <= currentByte + len) {
len = (int) (cueTotalBytes - currentByte);
cueFinished = true;
}
}
currentByte += len;
currentSample += AudioMath.bytesToSamples(len, decoder.getAudioFormat().getFrameSize());
updateStats();
if (len != 0)
encoder.encode(buf, len);
}
if (!merge) {
Track newTrack = track.copy();
newTrack.getTrackData().setLocation(output.toURI().toString());
encoder.close();
encoder = null;
TrackIO.write(newTrack);
}
decoder.close();
decoder = null;
}
if (merge) {
encoder.close();
encoder = null;
}
}
private void updateStats() {
elapsed = System.currentTimeMillis() - startTime;
speed = AudioMath.samplesToMillis(currentSample,
(int) decoder.getAudioFormat().getSampleRate()) /
elapsed;
estimated = totalSamples / speed;
}
public double getEstimated() {
return estimated;
}
public long getElapsed() {
return elapsed;
}
public double getSpeed() {
return speed;
}
public Track getTrack() {
return track;
}
public long getCurrentSample() {
return currentSample;
}
public long getTotalSamples() {
return totalSamples;
}
public void stop() {
stop = true;
}
public boolean openDecoder() {
decoder = Codecs.getNewDecoder(track);
if (decoder == null || !decoder.open(track)) {
logger.info("Couldn't initialize decoder for track: " + track.getTrackData().getLocation());
return false;
}
cueTotalBytes = 0;
currentByte = 0;
if (track.getTrackData().isCue()) {
decoder.seekSample(track.getTrackData().getStartPosition());
cueTotalBytes = AudioMath.samplesToBytes(track.getTrackData().getTotalSamples(), decoder.getAudioFormat().getFrameSize());
}
return true;
}
public boolean openEncoder() {
if (decoder == null) {
logger.severe("Need to initialize decoder before encoder!");
}
logger.info("Converting track: " + track.getTrackData().getLocation());
File parent = null;
if (config.getBoolean("converter.saveToSourceFolder", true)) {
if (track.getTrackData().isFile()) {
parent = track.getTrackData().getFile().getParentFile();
}
} else {
String path = config.getString("converter.path", "");
parent = new File(path);
}
if (parent == null || !parent.isDirectory()) {
logger.warning("Don't know where to save track: " + track.getTrackData().getLocation());
return false;
}
if (!parent.canWrite()) {
logger.warning("Cannot write to folder: " + parent);
return false;
}
//noinspection ResultOfMethodCallIgnored
parent.mkdirs();
String format = config.getString("converter.encoder", "wav");
String fileName = String.valueOf(fileNameFormat.eval(track)) +
"." + format;
output = new File(parent, fileName);
if (output.exists()) {
String action = config.getString("converter.actionWhenExists", "Ask");
if (action.equals("Ask")) {
int ret = JOptionPane.showConfirmDialog(null,
"File " + output.getAbsolutePath() + " exists, overwrite?",
"File exists, overwrite",
JOptionPane.YES_NO_CANCEL_OPTION);
if (ret == JOptionPane.YES_OPTION) {
//noinspection ResultOfMethodCallIgnored
output.delete();
} else if (ret == JOptionPane.NO_OPTION) {
return false;
} else if (ret == JOptionPane.CANCEL_OPTION) {
stop();
return false;
}
} else if (action.equals("Overwrite")) {
//noinspection ResultOfMethodCallIgnored
output.delete();
} else if (action.equals("Skip")) {
return false;
}
}
logger.info("Saving track to file: " + output.getAbsolutePath());
encoder = Codecs.getEncoder(format);
if (!encoder.open(output, decoder.getAudioFormat(), config)) {
logger.warning("Couldn't initialize encoder for track: " + track.getTrackData().getLocation());
return false;
}
return true;
}
public File getOutput() {
return output;
}
}