package model.audio; 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.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import config.Config; import logging.LogUtil; import model.progressbar.interfaces.MP3GainThread; import model.regex.Regex; public class MP3Gain { /** * the logger */ private final Logger logger = Logger.getLogger(this.getClass().getName()); /** * list of given files */ private List<GainMetaData> audioFiles; /** * the default gain */ private int defaultGain; /** * the regex to get the important blocks from the mp3gain result */ private static Regex blockRegex = new Regex("(.*?Recommended \"Track\" dB change:.*?Min mp3 global gain field:[^\\n]*)", Pattern.DOTALL); /** * gets the track gain change out of the block regex */ private static Regex changeRegex = new Regex("(.*?)Recommended \"Track\" dB change:(.*?)Recommended \"Track\" mp3 gain change: (-?\\d*).*", Pattern.DOTALL); /** * gets the album gain change out of the block regex */ private static Regex albumRegex = new Regex(".*Recommended \"Album\" dB change for all files:(.*?)Recommended \"Album\" mp3 gain change for all files: (-?\\d*).*", Pattern.DOTALL); /** * the current gain process */ private static Process currentProcess; /** * kills the current process if there is one * * @return true if killed, else false */ public static boolean killCurrentProcess() { if (currentProcess == null) return true; currentProcess.destroy(); return true; } /** * Constructor. default gain is 89. * * @param filePath * given audio file path */ public MP3Gain(String filePath) { this(Arrays.asList(new String[] { filePath }), 89); } /** * Constructor * * @param filePath * given file path * @param defaultGain * a default gain which should be applied */ public MP3Gain(String filePath, int defaultGain) { this(Arrays.asList(new String[] { filePath }), defaultGain); } /** * Constructor * * @param audioFiles * given List with file paths */ public MP3Gain(List<String> audioFiles) { this(audioFiles, 89); } /** * Constructor * * @param audioFiles * given List with file paths * @param defaultGain * a default gain which should be applied */ public MP3Gain(List<String> audioFiles, int defaultGain) { logger.log(Level.FINER, "init MP3Gain for " + audioFiles.size() + " files. defaulGain: " + defaultGain); this.audioFiles = new ArrayList<GainMetaData>(); this.defaultGain = defaultGain; for (String audioFile : audioFiles) this.audioFiles.add(new GainMetaData(audioFile, defaultGain)); String gainPath = Config.getInstance().getMP3GainPath(); logger.log(Level.FINER, "gain path is: " + gainPath); } /** * calculates the gain of the given file/files (if album gain should be * calculated more than one file should be given) * * @param forceRecalc * true if the gain should be recalculated * @param thread * the given thread for the gui where the in and outpthread * should be given to * * @return true if calculated successful, else false * * @throws IOException * thrown if something went wrong during calculation */ public boolean calculateGain(boolean forceRecalc, MP3GainThread thread) throws IOException { if (this.audioFiles.size() < 1) return false; String param = makeFilesToParam(); String gainPath = Config.getInstance().getMP3GainPath(); String cmd; if (forceRecalc) { cmd = gainPath + " -c -s r " + param; logger.log(Level.FINER, "calculate gain cmd: " + cmd); currentProcess = Runtime.getRuntime().exec(cmd, null, new File(".")); } else { cmd = gainPath + " -c " + param; logger.log(Level.FINER, "calculate gain cmd: " + cmd); currentProcess = Runtime.getRuntime().exec(cmd, null, new File(".")); } BufferedReader inputStream = new BufferedReader(new InputStreamReader(currentProcess.getInputStream())); String line; String output = ""; if (thread != null) { thread.setInputStream(currentProcess.getInputStream()); thread.setErrorStream(currentProcess.getErrorStream()); if (!thread.isRunning()) thread.start(); else thread.reset(); } try { while ((line = inputStream.readLine()) != null) { output += line; } } catch (IOException e) { logger.log(Level.SEVERE, "Error while reading input stream:\n" + LogUtil.getStackTrace(e), e); } logger.log(Level.FINER, "result: " + output); try { currentProcess.waitFor(); } catch (InterruptedException e) { Logger.getLogger(MP3Gain.class.getName()).log(Level.SEVERE, "Error while changing gain:\n" + LogUtil.getStackTrace(e), e); } int index = 0; // get track db and track gain for (String str : blockRegex.find(output)) { if (changeRegex.matches(str)) { audioFiles.get(index).setRecommendedTrackVolumeChange(Double.parseDouble(changeRegex.getGroup(2).trim())); audioFiles.get(index).setRecommendedTrackGainChange(Double.parseDouble(changeRegex.getGroup(3).trim())); } index++; } double albumDB = 0.0; double albumGain = 0.0; if (albumRegex.matches(output)) { albumDB = Double.parseDouble(albumRegex.getGroup(1).trim()); albumGain = Double.parseDouble(albumRegex.getGroup(2).trim()); } for (GainMetaData audioFile : audioFiles) { audioFile.setRecommendedAlbumVolumeChange(albumDB); audioFile.setRecommendedAlbumGainChange(albumGain); logger.log( Level.FINER, "calculated for: " + audioFile.getFilePath() + " Trackvolume: " + audioFile.getTrackVolume() + " TrackVolChange: " + audioFile.getRecommendedTrackVolumeChange() + " AlbumVolume: " + audioFile.getAlbumVolume() + " AlbumVolChange: " + audioFile.getRecommendedAlbumVolumeChange()); } return true; } /** * joins all audio files and wraps them into " " * * @return the joined list as a string which can be used as a parameter for * shell commands */ private String makeFilesToParam() { StringBuffer result = new StringBuffer(); for (GainMetaData audioFile : this.audioFiles) result.append("\"").append(audioFile.getFilePath()).append("\" "); return result.toString().trim(); } /** * changes the gain of the given file for the given gain * * @param path * the path of the file * @param gain * the gain value it should be changed for e.g. -3 would change the gain to 89-3 * @param thread * the given thread for the gui where the in and outpthread * should be given to * * @return true if gain changed, else false * * @throws IOException * thrown if something went wrong during change */ public static boolean changeTrackGain(String path, double gain, MP3GainThread thread) throws IOException { Logger.getLogger(MP3Gain.class.getName()).log(Level.FINER, "change gain for mp3: " + path + " change: " + gain); String gainPath = Config.getInstance().getMP3GainPath(); String cmd = gainPath + " -c -r -d " + gain + " \"" + path + "\""; Logger.getLogger(MP3Gain.class.getName()).log(Level.FINER, "change Gain cmd: " + cmd); Process p = Runtime.getRuntime().exec(cmd, null, new File(".")); if (thread != null) { thread.setInputStream(p.getInputStream()); thread.setErrorStream(p.getErrorStream()); if (thread.isRunning()) thread.nextStep(); else thread.start(); } try { return p.waitFor() == 0; } catch (InterruptedException e) { Logger.getLogger(MP3Gain.class.getName()).log(Level.SEVERE, "Error while changing gain:\n" + LogUtil.getStackTrace(e), e); return false; } } /** * changes the gain of the given audio files for the given gain value * * @param audioFiles * the paths of the files * @param gain * the gain it should be changed to * @param thread * the given thread for the gui where the in and outpthread * should be given to * * @return true if gain changed, else false * * @throws IOException * thrown if something went wrong during change */ public static boolean changeAlbumGain(List<String> audioFiles, double gain, MP3GainThread thread) throws IOException { Logger.getLogger(MP3Gain.class.getName()).log(Level.FINER, "change gain for " + audioFiles.size() + " mp3s. change: " + gain); String param = ""; for (String audioFile : audioFiles) param += "\"" + audioFile + "\" "; param = param.trim(); String gainPath = Config.getInstance().getMP3GainPath(); String cmd = gainPath + " -c -a -d " + gain + " " + param; Logger.getLogger(MP3Gain.class.getName()).log(Level.FINER, "change Gain cmd: " + cmd); Process p = Runtime.getRuntime().exec(cmd, null, new File(".")); if (thread != null) { thread.setInputStream(p.getInputStream()); thread.setErrorStream(p.getErrorStream()); thread.start(); } try { return p.waitFor() == 0; } catch (InterruptedException e) { Logger.getLogger(MP3Gain.class.getName()).log(Level.SEVERE, "Error while changing gain:\n" + LogUtil.getStackTrace(e), e); return false; } } /** * gets the default gain * * @return the default gain */ public int getDefaultGain() { return this.defaultGain; } /** * gets the number of audio files * * @return number of files */ public int getNumOfAudioFiles() { return this.audioFiles.size(); } /** * gets the gain data at the given index * * @param index * the given index * * @return the gain data */ public GainMetaData getGainData(int index) { return this.audioFiles.get(index); } }