package model;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import config.Config;
import logging.LogUtil;
import model.audio.GainAudioFile;
import model.audio.MP3Gain;
import model.progressbar.interfaces.MP3GainThread;
import model.table.GainTableModel;
import model.util.Commons;
import model.util.FileUtil;
public class MP3GainModel {
/**
* the logger
*/
private final Logger logger = Logger.getLogger(this.getClass().getName());
/**
* currently loaded audio files
*/
private List<GainAudioFile> audioFiles;
/**
* all paths of loaded files
*/
private Set<String> readPaths;
/**
* the stop flag, if set to true all operations should stop
*/
private boolean stopFlag;
/**
* Constructor
*/
public MP3GainModel() {
this.audioFiles = new ArrayList<GainAudioFile>();
this.readPaths = new HashSet<String>();
this.stopFlag = false;
}
/**
* true if the stop flag is set, else false
*
* @return true or false
*/
public boolean isStopFlagSet() {
return this.stopFlag;
}
/**
* sets the stop flag and stops all operations
*
* @param set
* ture if stop, else false
*/
public void setStopFlag(boolean set) {
logger.log(Level.FINER, "set stop flag: " + set);
this.stopFlag = set;
MP3Gain.killCurrentProcess();
}
/**
* adds the given audio file
*
* @param path
* path to audio file
* @param sort
* sort audio files after adding
*
* @return true if added successfully, else false
*/
public boolean addAudioFile(String path, boolean sort) {
logger.log(Level.FINER, "adding audio file: " + path + " already read: " + readPaths.contains(path));
if (this.stopFlag)
return false;
if (readPaths.contains(path))
return false;
if (!Commons.isValidExtension(FileUtil.getFileExtension(path)))
return false;
audioFiles.add(new GainAudioFile(path));
readPaths.add(path);
if (sort)
sortAudioFiles();
return true;
}
/**
* reads the audio files in the given folder
*
* @param path
* path to the folder
* @param recursive
* read all subfolders recursively
*
* @return true if read successfully, else false
*/
public boolean addAudioFiles(String path, boolean recursive) {
logger.log(Level.FINER, "reading all audio files from: " + path + " recursive: " + recursive);
if (this.stopFlag)
return false;
List<String> files = FileUtil.getFilesFromFolder(path, true);
for (String file : files) {
if (this.stopFlag)
return false;
logger.log(Level.FINER, "analyse file: " + file);
if (Commons.isValidExtension(FileUtil.getFileExtension(file))) {
addAudioFile(file, false);
}
// check if it is a folder, if so
else if (recursive && new File(file).isDirectory()) {
addAudioFiles(file, recursive);
}
}
sortAudioFiles();
return true;
}
/**
* sorts the loaded audio files depending on the filename, not case
* sensitive
*/
private void sortAudioFiles() {
Collections.sort(audioFiles, new Comparator<GainAudioFile>() {
@Override
public int compare(GainAudioFile arg0, GainAudioFile arg1) {
return arg0.getPath().compareToIgnoreCase(arg1.getPath());
}
});
}
/**
* deletes the audio file at the given index
*
* @param index
* given index
*
* @return true if deleted successfully, else false
*/
private boolean deleteAudioFile(int index) {
logger.log(Level.FINER, "delete audio file with index " + index + " exists: " + (index < this.audioFiles.size()));
if (index >= this.audioFiles.size())
return false;
String path = this.audioFiles.get(index).getPath();
this.audioFiles.remove(index);
boolean delP = this.readPaths.remove(path);
logger.log(Level.FINER, "audio file deleted: " + delP);
return true;
}
/**
* deletes the audio files at the given indices
*
* @param indices
* given indices
*
* @return true if deleted successfully, else false
*/
public boolean deleteAudioFiles(int[] indices) {
Integer[] tmp = new Integer[indices.length];
for (int i = 0; i < indices.length; i++)
tmp[i] = indices[i];
List<Integer> ints = Arrays.asList(tmp);
Collections.sort(ints);
int offset = 0;
for (Integer i : ints) {
deleteAudioFile(i - offset);
offset++;
}
return true;
}
/**
* deletes all audio files
*
* @return true if deleted successfully, else false
*/
public boolean clearAudioFiles() {
this.audioFiles.clear();
this.readPaths.clear();
return true;
}
/**
* gets the audio file at the given index
*
* @param index
* given index
*
* @return the audio file
*/
public GainAudioFile getAudioFile(int index) {
return this.audioFiles.get(index);
}
/**
* gets the tablemodel depending on the currently loaded audio files
*
* @return the table model
*/
public GainTableModel getTableModel() {
return new GainTableModel(audioFiles);
}
/**
* sets the given indices as invalid, so that they need to be recalculated
*
* @param indices
* given indices
*/
public void clearAudioFileData(int[] indices) {
for (int i = 0; i < indices.length; i++)
audioFiles.get(indices[i]).setValid(false);
}
/**
* sets all loaded files as invalid, so that they need to be recalculated
*/
public void clearAllAudioFileData() {
for (GainAudioFile file : this.audioFiles)
file.setValid(false);
}
/**
* analyses the Track Gain for the audio file at the given index
*
* @param index
* given index
* @param targetVol
* the given target volume. The gain will be calculated depending
* on the given volume
* @param forceRecalc
* true if the track should be recalculated, false to just read
* the infos, the gain will only be calculated if no data is
* saved.
* @param pb
* the given progressbar which should be updated
*
* @return true if analysed successfully
*
* @throws IOException
* thrown if something went wrong while analysing
*/
public boolean analyseTrackGain(int index, int targetVol, boolean forceRecalc, MP3GainThread pb) throws IOException {
logger.log(Level.FINER, "analyse Track: " + index + " with targetVol: " + targetVol + " force Recalc: " + forceRecalc);
if (this.stopFlag)
return false;
if (index >= this.audioFiles.size())
return false;
GainAudioFile audioFile = this.audioFiles.get(index);
MP3Gain g = new MP3Gain(audioFile.getPath(), targetVol);
g.calculateGain(forceRecalc, pb);
audioFile.setValid(true);
audioFile.setTrackvolume(g.getGainData(0).getTrackVolume());
audioFile.setTrackGain(g.getGainData(0).getTrackGainChange());
audioFile.setAlbumVolume(g.getGainData(0).getAlbumVolume());
audioFile.setAlbumGain(g.getGainData(0).getAlbumGainChange());
logger.log(Level.FINER, "analyse Track: " + this.audioFiles.get(index));
return true;
}
/**
* gets the number of loaded files
*
* @return number of files
*/
public int getNumOfAudioFiles() {
return this.audioFiles.size();
}
/**
* analyses the album gain of the audio files at the given indices
*
* @param indices
* indices of the audio files
* @param targetVol
* the given target volume. The gain will be calculated depending
* on the given volume
* @param forceRecalc
* true if the track should be recalculated, false to just read
* the infos, the gain will only be calculated if no data is
* saved.
* @param pb
* the given progressbar which should be updated
*
* @return true if analysed successfully
*
* @throws IOException
* thrown if something went wrong while calculating
*/
public boolean analyseAlbumGain(int[] indices, int targetVol, boolean forceRecalc, MP3GainThread pb) throws IOException {
logger.log(Level.FINER, "analyse Album gain for " + indices.length + " Tracks with targetVol: " + targetVol + " force Recalc: " + forceRecalc);
if (indices.length < 1)
return false;
List<String> audioFiles = new ArrayList<String>();
for (int i = 0; i < indices.length; i++)
audioFiles.add(this.audioFiles.get(indices[i]).getPath());
MP3Gain g = new MP3Gain(audioFiles, targetVol);
g.calculateGain(forceRecalc, pb);
for (int i = 0; i < indices.length; i++) {
this.audioFiles.get(i).setValid(true);
this.audioFiles.get(i).setTrackvolume(g.getGainData(i).getTrackVolume());
this.audioFiles.get(i).setTrackGain(g.getGainData(i).getTrackGainChange());
this.audioFiles.get(i).setAlbumVolume(g.getGainData(i).getAlbumVolume());
this.audioFiles.get(i).setAlbumGain(g.getGainData(i).getAlbumGainChange());
logger.log(Level.FINER, "analyse album: " + this.audioFiles.get(i));
}
return true;
}
/**
* changes the track gain of the audio file at the given index
*
* @param index
* index of the audio file
* @param targetVol
* the given target volume. The gain will be calculated depending
* on the given volume
* @param forceRecalc
* true if the track should be recalculated, false to just read
* the infos, the gain will only be calculated if no data is
* saved.
* @param pb
* the given progressbar which should be updated
*
* @return true if changed successfully
*
* @throws IOException
* thrown if something went wrong while changing
*/
public boolean changeGain(int index, int targetVol, boolean forceRecalc, MP3GainThread pb) throws IOException {
logger.log(Level.FINER, "change track gain for index " + index);
if (index >= this.audioFiles.size()) {
logger.log(Level.FINER, "audio file not found");
return false;
}
GainAudioFile audioFile = this.audioFiles.get(index);
logger.log(Level.FINER, "change gain: " + audioFile);
if (!audioFile.isValid()) {
logger.log(Level.FINER, "audio file is not valid. No Gaininformation saved.");
return false;
}
MP3Gain.changeTrackGain(audioFile.getPath(), audioFile.getRelativeAlbumGain(targetVol), pb);
analyseTrackGain(index, targetVol, false, null);
return true;
}
/**
* changes the album gain of the audio file at the given index
*
* @param indices
* index of the audio file
* @param targetVol
* the given target volume. The gain will be calculated depending
* on the given volume
* @param forceRecalc
* true if the track should be recalculated, false to just read
* the infos, the gain will only be calculated if no data is
* saved.
* @param pb
* the given progressbar which should be updated
*
* @return true if changed successfully
*
* @throws IOException
* thrown if something went wrong while changing
*/
public boolean changeAlbumGain(int[] indices, int targetVol, boolean forceRecalc, MP3GainThread pb) throws IOException {
logger.log(Level.FINER, "change album gain for " + indices.length + " audio files.");
if (indices.length < 1)
return false;
List<String> audioFiles = new ArrayList<String>();
for (int i = 0; i < indices.length; i++) {
logger.log(Level.FINER, "change album: " + this.audioFiles.get(indices[i]));
if (!this.audioFiles.get(indices[i]).isValid()) {
logger.log(Level.FINER, "file " + this.audioFiles.get(indices[i]).getPath() + " is not valid.");
return false;
}
audioFiles.add(this.audioFiles.get(indices[i]).getPath());
}
MP3Gain.changeAlbumGain(audioFiles, this.audioFiles.get(0).getRelativeAlbumGain(targetVol), pb);
analyseAlbumGain(indices, targetVol, false, null);
return true;
}
/**
* checks if mp3gain is available
*
* @return true if it is available, else false
*/
public boolean checkMP3Gain() {
logger.log(Level.FINER, "check if mp3gain is available.");
try {
logger.log(Level.FINER, "call command: " + Config.getInstance().getMP3GainPath() + " -v");
Process p = Runtime.getRuntime().exec(Config.getInstance().getMP3GainPath() + " -v");
BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String line = br.readLine();
logger.log(Level.FINER, "line: " + line);
br.close();
if (line.toLowerCase().contains("version") && line.toLowerCase().contains("mp3gain"))
return true;
if (p.waitFor() == 0)
return true;
} catch (IOException | InterruptedException e) {
logger.log(Level.SEVERE, "Error while starting mp3gain test process:\n" + LogUtil.getStackTrace(e), e);
return false;
}
return false;
}
}