1   /**
2    *    Copyright 2009 Webster Smalley
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package com.webstersmalley.iplayer.mp3;
18  
19  import java.io.BufferedReader;
20  import java.io.File;
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.InputStreamReader;
24  import java.util.Set;
25  
26  import org.apache.log4j.Logger;
27  import org.blinkenlights.jid3.ID3Exception;
28  import org.blinkenlights.jid3.ID3Tag;
29  import org.blinkenlights.jid3.MP3File;
30  import org.blinkenlights.jid3.MediaFile;
31  import org.blinkenlights.jid3.v2.ID3V2Tag;
32  import org.blinkenlights.jid3.v2.ID3V2_3_0Tag;
33  
34  import com.webstersmalley.iplayer.informer.ProgramInformation;
35  import com.webstersmalley.iplayer.organiser.RadioProgramBean;
36  import com.webstersmalley.musiclibrary.model.FileSystemMusicFileFactory;
37  import com.webstersmalley.musiclibrary.model.MusicFile;
38  
39  /**
40   * @author Matthew Smalley
41   * 
42   */
43  public class MP3Utils {
44  	private static final String COMMAND_LINE_TEMPLATE = "%1$s --nohist --preset \"%2$s\" \"%3$s\" \"%4$s\"";
45  	private String mp3Encoder;
46  	private FileSystemMusicFileFactory musicFileFactory = new FileSystemMusicFileFactory();
47  	private FileSystemUtils fileSystemUtils = new FileSystemUtils();
48  
49  	private Logger logger = Logger.getLogger(getClass());
50  
51  	public void setMp3Encoder(String mp3Encoder) {
52  		this.mp3Encoder = mp3Encoder;
53  	}
54  
55  	public ID3V2Tag getID3V2Tags(String filename) {
56  		try {
57  			MediaFile mediaFile = new MP3File(new File(filename));
58  			ID3Tag[] tags = mediaFile.getTags();
59  			for (ID3Tag tag : tags) {
60  				if (tag instanceof ID3V2Tag) {
61  					return (ID3V2Tag) tag;
62  				}
63  			}
64  		} catch (ID3Exception e) {
65  			throw new RuntimeException("Error reading tags", e);
66  		}
67  		return null;
68  	}
69  
70  	public String dumpAllTags(String filename) {
71  		StringBuffer sb = new StringBuffer();
72  		try {
73  			MediaFile mediaFile = new MP3File(new File(filename));
74  
75  			ID3Tag[] tags = mediaFile.getTags();
76  
77  			for (ID3Tag tag : tags) {
78  				sb.append(tag.getClass());
79  				sb.append("\n");
80  				sb.append(tag);
81  
82  				sb.append("\n");
83  			}
84  
85  		} catch (ID3Exception e) {
86  			throw new RuntimeException("Error reading tags", e);
87  		}
88  
89  		return sb.toString();
90  	}
91  
92  	public void reencodeFolder(String inputFolder, String outputFolder, String preset) {
93  		logger.info("Re-encoding folder: " + inputFolder + " into " + outputFolder + ", using preset " + preset);
94  		if (inputFolder == null || outputFolder == null || preset == null) {
95  			throw new RuntimeException("Null parameters not allowed");
96  		}
97  		if (!inputFolder.endsWith(File.separator)) {
98  			inputFolder += File.separator;
99  		}
100 		if (!outputFolder.endsWith(File.separator)) {
101 			outputFolder += File.separator;
102 		}
103 
104 		Set<MusicFile> inputFiles = musicFileFactory.getMusicFiles(inputFolder);
105 		for (MusicFile musicFile : inputFiles) {
106 			String inputFilename = inputFolder + musicFile.getFilename();
107 			String outputFilename = outputFolder + musicFile.getFilename();
108 			logger.info("Processing file: " + inputFilename);
109 			fileSystemUtils.checkFolder(new File(outputFilename).getParentFile().getAbsolutePath());
110 			if (fileSystemUtils.destinationFileNewer(inputFilename, outputFilename)) {
111 				logger.info("Destination file exists + is newer, skipping");
112 			} else {
113 				logger.info("Running: " + inputFilename + ", " + outputFilename);
114 				reencodeMP3(inputFilename, outputFilename, preset);
115 			}
116 		}
117 	}
118 
119 	public void reencodeMP3(String inputFilename, String outputFilename, String preset) {
120 		reencodeMP3(inputFilename, outputFilename, preset, true);
121 	}
122 
123 	public void reencodeMP3(String inputFilename, String outputFilename, String preset, boolean retag) {
124 
125 		try {
126 			File outputFolder = new File(outputFilename).getParentFile();
127 			if (!outputFolder.exists()) {
128 				if (!outputFolder.mkdirs()) {
129 					throw new RuntimeException("Error creating output folder");
130 				}
131 			}
132 			String commandLine = String.format(COMMAND_LINE_TEMPLATE, mp3Encoder, preset, inputFilename, outputFilename).replaceAll("\\\\", "/");
133 
134 			logger.debug("Running command: " + commandLine);
135 
136 			Process p = Runtime.getRuntime().exec(commandLine);
137 
138 			StreamConsumer stdout = new StreamConsumer(p.getInputStream(), true);
139 			StreamConsumer error = new StreamConsumer(p.getErrorStream(), false);
140 
141 			new Thread(stdout, "stdout reader").start();
142 			new Thread(error, "error reader").start();
143 
144 			p.waitFor();
145 			int exitValue = p.exitValue();
146 			if (exitValue != 0) {
147 				throw new RuntimeException("MP3 Encoder exited with code: " + exitValue);
148 			}
149 			if (retag) {
150 				copyTags(inputFilename, outputFilename);
151 			}
152 		} catch (Exception e) {
153 			logger.error("Error running mp3 encoder", e);
154 			throw new RuntimeException("Error running mp3 encoder", e);
155 		}
156 	}
157 
158 	private void copyTags(String inputFilename, String outputFilename) {
159 		ID3V2Tag inputTags = getID3V2Tags(inputFilename);
160 		MediaFile mediaFile = new MP3File(new File(outputFilename));
161 		mediaFile.setID3Tag(inputTags);
162 		try {
163 			mediaFile.sync();
164 		} catch (ID3Exception e) {
165 			throw new RuntimeException("Error writing tags to: " + outputFilename, e);
166 		}
167 	}
168 
169 	private class StreamConsumer implements Runnable {
170 		private Logger logger = Logger.getLogger(getClass());
171 		private InputStream stream;
172 		private boolean log;
173 
174 		public StreamConsumer(InputStream stream, boolean log) {
175 			this.stream = stream;
176 			this.log = log;
177 		}
178 
179 		public void run() {
180 			BufferedReader br = new BufferedReader(new InputStreamReader(stream));
181 			try {
182 				for (String line; (line = br.readLine()) != null;) {
183 					if (log) {
184 						logger.info(line);
185 					}
186 				}
187 			} catch (IOException e) {
188 				logger.error("Error reading stream", e);
189 			}
190 		}
191 	}
192 
193 	public void retagMP3(String filename, RadioProgramBean program) {
194 		try {
195 			ID3V2_3_0Tag tag = new ID3V2_3_0Tag();
196 			tag.setTitle(program.getEpisodeTitle());
197 			tag.setAlbum(program.getSeriesTitle());
198 			tag.setArtist(program.getArtist());
199 
200 			MediaFile mediaFile = new MP3File(new File(filename));
201 			mediaFile.setID3Tag(tag);
202 			mediaFile.sync();
203 		} catch (Exception e) {
204 			logger.error("Error writing mp3 tags", e);
205 			throw new RuntimeException("Error writing mp3 tags", e);
206 		}
207 	}
208 
209 	public void retagMP3(String filename, ProgramInformation prog) {
210 		try {
211 			ID3V2_3_0Tag tag = new ID3V2_3_0Tag();
212 			tag.setTitle(prog.getProgramName());
213 			tag.setAlbum(prog.getProgramSetName());
214 			tag.setArtist(prog.getAuthor());
215 
216 			MediaFile mediaFile = new MP3File(new File(filename));
217 			mediaFile.setID3Tag(tag);
218 			mediaFile.sync();
219 		} catch (Exception e) {
220 			logger.error("Error writing mp3 tags", e);
221 			throw new RuntimeException("Error writing mp3 tags", e);
222 		}
223 	}
224 
225 }