diff options
Diffstat (limited to 'songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java')
-rw-r--r-- | songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java | 772 |
1 files changed, 772 insertions, 0 deletions
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java new file mode 100644 index 0000000000..54440551a1 --- /dev/null +++ b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java | |||
@@ -0,0 +1,772 @@ | |||
1 | /* | ||
2 | * MpegAudioFileReader. | ||
3 | * | ||
4 | * 12/31/04 : mp3spi.weak system property added to skip controls. | ||
5 | * | ||
6 | * 11/29/04 : ID3v2.2, v2.3 & v2.4 support improved. | ||
7 | * "mp3.id3tag.composer" (TCOM/TCM) added | ||
8 | * "mp3.id3tag.grouping" (TIT1/TT1) added | ||
9 | * "mp3.id3tag.disc" (TPA/TPOS) added | ||
10 | * "mp3.id3tag.encoded" (TEN/TENC) added | ||
11 | * "mp3.id3tag.v2.version" added | ||
12 | * | ||
13 | * 11/28/04 : String encoding bug fix in chopSubstring method. | ||
14 | * | ||
15 | * JavaZOOM : mp3spi@javazoom.net | ||
16 | * http://www.javazoom.net | ||
17 | * | ||
18 | *----------------------------------------------------------------------- | ||
19 | * This program is free software; you can redistribute it and/or modify | ||
20 | * it under the terms of the GNU Library General Public License as published | ||
21 | * by the Free Software Foundation; either version 2 of the License, or | ||
22 | * (at your option) any later version. | ||
23 | * | ||
24 | * This program is distributed in the hope that it will be useful, | ||
25 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
26 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
27 | * GNU Library General Public License for more details. | ||
28 | * | ||
29 | * You should have received a copy of the GNU Library General Public | ||
30 | * License along with this program; if not, write to the Free Software | ||
31 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
32 | *---------------------------------------------------------------------- | ||
33 | */ | ||
34 | |||
35 | package javazoom.spi.mpeg.sampled.file; | ||
36 | |||
37 | |||
38 | import java.io.BufferedInputStream; | ||
39 | import java.io.File; | ||
40 | import java.io.FileInputStream; | ||
41 | import java.io.IOException; | ||
42 | import java.io.InputStream; | ||
43 | import java.io.PushbackInputStream; | ||
44 | import java.io.UnsupportedEncodingException; | ||
45 | import java.net.URL; | ||
46 | import java.net.URLConnection; | ||
47 | import java.security.AccessControlException; | ||
48 | import java.util.HashMap; | ||
49 | |||
50 | import javax.sound.sampled.AudioFileFormat; | ||
51 | import javax.sound.sampled.AudioFormat; | ||
52 | import javax.sound.sampled.AudioInputStream; | ||
53 | import javax.sound.sampled.AudioSystem; | ||
54 | import javax.sound.sampled.UnsupportedAudioFileException; | ||
55 | |||
56 | import javazoom.jl.decoder.Bitstream; | ||
57 | import javazoom.jl.decoder.Header; | ||
58 | import javazoom.spi.mpeg.sampled.file.tag.IcyInputStream; | ||
59 | import javazoom.spi.mpeg.sampled.file.tag.MP3Tag; | ||
60 | |||
61 | import org.tritonus.share.TDebug; | ||
62 | import org.tritonus.share.sampled.file.TAudioFileReader; | ||
63 | |||
64 | /** | ||
65 | * This class implements AudioFileReader for MP3 SPI. | ||
66 | */ | ||
67 | public class MpegAudioFileReader extends TAudioFileReader | ||
68 | { | ||
69 | private final int SYNC = 0xFFE00000; | ||
70 | private String weak = null; | ||
71 | private final AudioFormat.Encoding[][] sm_aEncodings = | ||
72 | { | ||
73 | {MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3}, | ||
74 | {MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3}, | ||
75 | {MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2, MpegEncoding.MPEG2DOT5L3}, | ||
76 | |||
77 | }; | ||
78 | |||
79 | private static final int INITAL_READ_LENGTH = 64000; | ||
80 | private static final int MARK_LIMIT = INITAL_READ_LENGTH + 1; | ||
81 | |||
82 | private static final String[] id3v1genres = { | ||
83 | "Blues" | ||
84 | , "Classic Rock" | ||
85 | , "Country" | ||
86 | , "Dance" | ||
87 | , "Disco" | ||
88 | , "Funk" | ||
89 | , "Grunge" | ||
90 | , "Hip-Hop" | ||
91 | , "Jazz" | ||
92 | , "Metal" | ||
93 | , "New Age" | ||
94 | , "Oldies" | ||
95 | , "Other" | ||
96 | , "Pop" | ||
97 | , "R&B" | ||
98 | , "Rap" | ||
99 | , "Reggae" | ||
100 | , "Rock" | ||
101 | , "Techno" | ||
102 | , "Industrial" | ||
103 | , "Alternative" | ||
104 | , "Ska" | ||
105 | , "Death Metal" | ||
106 | , "Pranks" | ||
107 | , "Soundtrack" | ||
108 | , "Euro-Techno" | ||
109 | , "Ambient" | ||
110 | , "Trip-Hop" | ||
111 | , "Vocal" | ||
112 | , "Jazz+Funk" | ||
113 | , "Fusion" | ||
114 | , "Trance" | ||
115 | , "Classical" | ||
116 | , "Instrumental" | ||
117 | , "Acid" | ||
118 | , "House" | ||
119 | , "Game" | ||
120 | , "Sound Clip" | ||
121 | , "Gospel" | ||
122 | , "Noise" | ||
123 | , "AlternRock" | ||
124 | , "Bass" | ||
125 | , "Soul" | ||
126 | , "Punk" | ||
127 | , "Space" | ||
128 | , "Meditative" | ||
129 | , "Instrumental Pop" | ||
130 | , "Instrumental Rock" | ||
131 | , "Ethnic" | ||
132 | , "Gothic" | ||
133 | , "Darkwave" | ||
134 | , "Techno-Industrial" | ||
135 | , "Electronic" | ||
136 | , "Pop-Folk" | ||
137 | , "Eurodance" | ||
138 | , "Dream" | ||
139 | , "Southern Rock" | ||
140 | , "Comedy" | ||
141 | , "Cult" | ||
142 | , "Gangsta" | ||
143 | , "Top 40" | ||
144 | , "Christian Rap" | ||
145 | , "Pop/Funk" | ||
146 | , "Jungle" | ||
147 | , "Native American" | ||
148 | , "Cabaret" | ||
149 | , "New Wave" | ||
150 | , "Psychadelic" | ||
151 | , "Rave" | ||
152 | , "Showtunes" | ||
153 | , "Trailer" | ||
154 | , "Lo-Fi" | ||
155 | , "Tribal" | ||
156 | , "Acid Punk" | ||
157 | , "Acid Jazz" | ||
158 | , "Polka" | ||
159 | , "Retro" | ||
160 | , "Musical" | ||
161 | , "Rock & Roll" | ||
162 | , "Hard Rock" | ||
163 | , "Folk" | ||
164 | , "Folk-Rock" | ||
165 | , "National Folk" | ||
166 | , "Swing" | ||
167 | , "Fast Fusion" | ||
168 | , "Bebob" | ||
169 | , "Latin" | ||
170 | , "Revival" | ||
171 | , "Celtic" | ||
172 | , "Bluegrass" | ||
173 | , "Avantgarde" | ||
174 | , "Gothic Rock" | ||
175 | , "Progressive Rock" | ||
176 | , "Psychedelic Rock" | ||
177 | , "Symphonic Rock" | ||
178 | , "Slow Rock" | ||
179 | , "Big Band" | ||
180 | , "Chorus" | ||
181 | , "Easy Listening" | ||
182 | , "Acoustic" | ||
183 | , "Humour" | ||
184 | , "Speech" | ||
185 | , "Chanson" | ||
186 | , "Opera" | ||
187 | , "Chamber Music" | ||
188 | , "Sonata" | ||
189 | , "Symphony" | ||
190 | , "Booty Brass" | ||
191 | , "Primus" | ||
192 | , "Porn Groove" | ||
193 | , "Satire" | ||
194 | , "Slow Jam" | ||
195 | , "Club" | ||
196 | , "Tango" | ||
197 | , "Samba" | ||
198 | , "Folklore" | ||
199 | , "Ballad" | ||
200 | , "Power Ballad" | ||
201 | , "Rhythmic Soul" | ||
202 | , "Freestyle" | ||
203 | , "Duet" | ||
204 | , "Punk Rock" | ||
205 | , "Drum Solo" | ||
206 | , "A Capela" | ||
207 | , "Euro-House" | ||
208 | , "Dance Hall" | ||
209 | , "Goa" | ||
210 | , "Drum & Bass" | ||
211 | , "Club-House" | ||
212 | , "Hardcore" | ||
213 | , "Terror" | ||
214 | , "Indie" | ||
215 | , "BritPop" | ||
216 | , "Negerpunk" | ||
217 | , "Polsk Punk" | ||
218 | , "Beat" | ||
219 | , "Christian Gangsta Rap" | ||
220 | , "Heavy Metal" | ||
221 | , "Black Metal" | ||
222 | , "Crossover" | ||
223 | , "Contemporary Christian" | ||
224 | , "Christian Rock" | ||
225 | , "Merengue" | ||
226 | , "Salsa" | ||
227 | , "Thrash Metal" | ||
228 | , "Anime" | ||
229 | , "JPop" | ||
230 | , "SynthPop" | ||
231 | }; | ||
232 | |||
233 | public MpegAudioFileReader() | ||
234 | { | ||
235 | super(MARK_LIMIT, true); | ||
236 | if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader(1.9.2-FINAL)"); | ||
237 | try | ||
238 | { | ||
239 | weak = System.getProperty("mp3spi.weak"); | ||
240 | } | ||
241 | catch(AccessControlException e) | ||
242 | {} | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * Returns AudioFileFormat from File. | ||
247 | */ | ||
248 | public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException | ||
249 | { | ||
250 | return super.getAudioFileFormat(file); | ||
251 | } | ||
252 | |||
253 | /** | ||
254 | * Returns AudioFileFormat from URL. | ||
255 | */ | ||
256 | public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException | ||
257 | { | ||
258 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): begin"); } | ||
259 | long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; | ||
260 | URLConnection conn = url.openConnection(); | ||
261 | // Tell shoucast server (if any) that SPI support shoutcast stream. | ||
262 | conn.setRequestProperty ("Icy-Metadata", "1"); | ||
263 | InputStream inputStream = conn.getInputStream(); | ||
264 | AudioFileFormat audioFileFormat = null; | ||
265 | try | ||
266 | { | ||
267 | audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); | ||
268 | } | ||
269 | finally | ||
270 | { | ||
271 | inputStream.close(); | ||
272 | } | ||
273 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): end"); } | ||
274 | return audioFileFormat; | ||
275 | } | ||
276 | |||
277 | /** | ||
278 | * Returns AudioFileFormat from inputstream and medialength. | ||
279 | */ | ||
280 | public AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLength) throws UnsupportedAudioFileException, IOException | ||
281 | { | ||
282 | if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader.getAudioFileFormat(InputStream inputStream, long mediaLength): begin"); | ||
283 | HashMap aff_properties = new HashMap(); | ||
284 | HashMap af_properties = new HashMap(); | ||
285 | int mLength = (int) mediaLength; | ||
286 | int size = inputStream.available(); | ||
287 | PushbackInputStream pis = new PushbackInputStream(inputStream, MARK_LIMIT); | ||
288 | byte head[] = new byte[12]; | ||
289 | pis.read(head); | ||
290 | if (TDebug.TraceAudioFileReader) | ||
291 | { | ||
292 | TDebug.out("InputStream : "+inputStream + " =>" + new String(head)); | ||
293 | } | ||
294 | /* | ||
295 | * Check for WAV, AU, and AIFF file formats. | ||
296 | * | ||
297 | * Next check for Shoutcast (supported) and OGG (unsupported) streams. | ||
298 | * | ||
299 | * Note -- the check for WAV files will reject Broadcast WAV files. | ||
300 | * This may be incorrect as broadcast WAV files may contain MPEG data. | ||
301 | * Need to investigate. | ||
302 | * | ||
303 | */ | ||
304 | if ((head[0] == 'R') && (head[1] == 'I') && (head[2] == 'F') && (head[3] == 'F') && (head[8] == 'W') && (head[9] == 'A') && (head[10] == 'V') && (head[11] == 'E')) | ||
305 | { | ||
306 | if (TDebug.TraceAudioFileReader) TDebug.out("WAV stream found"); | ||
307 | if (weak == null) throw new UnsupportedAudioFileException("WAV stream found"); | ||
308 | } | ||
309 | else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') && (head[3] == 'd')) | ||
310 | { | ||
311 | if (TDebug.TraceAudioFileReader) TDebug.out("AU stream found"); | ||
312 | if (weak == null) throw new UnsupportedAudioFileException("AU stream found"); | ||
313 | } | ||
314 | else if ((head[0] == 'F') && (head[1] == 'O') && (head[2] == 'R') && (head[3] == 'M') && (head[8] == 'A') && (head[9] == 'I') && (head[10] == 'F') && (head[11] == 'F')) | ||
315 | { | ||
316 | if (TDebug.TraceAudioFileReader) TDebug.out("AIFF stream found"); | ||
317 | if (weak == null) throw new UnsupportedAudioFileException("AIFF stream found"); | ||
318 | } | ||
319 | // Shoutcast stream ? | ||
320 | else if (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y'))) | ||
321 | { | ||
322 | pis.unread(head); | ||
323 | // Load shoutcast meta data. | ||
324 | loadShoutcastInfo(pis, aff_properties); | ||
325 | } | ||
326 | // Ogg stream ? | ||
327 | else if (((head[0] == 'O') | (head[0] == 'o')) && ((head[1] == 'G') | (head[1] == 'g')) && ((head[2] == 'G') | (head[2] == 'g'))) | ||
328 | { | ||
329 | if (TDebug.TraceAudioFileReader) TDebug.out("Ogg stream found"); | ||
330 | if (weak == null) throw new UnsupportedAudioFileException("Ogg stream found"); | ||
331 | } | ||
332 | // No, so pushback. | ||
333 | else | ||
334 | { | ||
335 | pis.unread(head); | ||
336 | } | ||
337 | // MPEG header info. | ||
338 | int nVersion = AudioSystem.NOT_SPECIFIED; | ||
339 | int nLayer = AudioSystem.NOT_SPECIFIED; | ||
340 | int nSFIndex = AudioSystem.NOT_SPECIFIED; | ||
341 | int nMode = AudioSystem.NOT_SPECIFIED; | ||
342 | int FrameSize = AudioSystem.NOT_SPECIFIED; | ||
343 | int nFrameSize = AudioSystem.NOT_SPECIFIED; | ||
344 | int nFrequency = AudioSystem.NOT_SPECIFIED; | ||
345 | int nTotalFrames = AudioSystem.NOT_SPECIFIED; | ||
346 | float FrameRate = AudioSystem.NOT_SPECIFIED; | ||
347 | int BitRate = AudioSystem.NOT_SPECIFIED; | ||
348 | int nChannels = AudioSystem.NOT_SPECIFIED; | ||
349 | int nHeader = AudioSystem.NOT_SPECIFIED; | ||
350 | int nTotalMS = AudioSystem.NOT_SPECIFIED; | ||
351 | boolean nVBR = false; | ||
352 | AudioFormat.Encoding encoding = null; | ||
353 | try | ||
354 | { | ||
355 | Bitstream m_bitstream = new Bitstream(pis); | ||
356 | aff_properties.put("mp3.header.pos",new Integer(m_bitstream.header_pos())); | ||
357 | Header m_header = m_bitstream.readFrame(); | ||
358 | // nVersion = 0 => MPEG2-LSF (Including MPEG2.5), nVersion = 1 => MPEG1 | ||
359 | nVersion = m_header.version(); | ||
360 | if (nVersion == 2) aff_properties.put("mp3.version.mpeg",Float.toString(2.5f)); | ||
361 | else aff_properties.put("mp3.version.mpeg",Integer.toString(2-nVersion)); | ||
362 | // nLayer = 1,2,3 | ||
363 | nLayer = m_header.layer(); | ||
364 | aff_properties.put("mp3.version.layer",Integer.toString(nLayer)); | ||
365 | nSFIndex = m_header.sample_frequency(); | ||
366 | nMode = m_header.mode(); | ||
367 | aff_properties.put("mp3.mode",new Integer(nMode)); | ||
368 | nChannels = nMode == 3 ? 1 : 2; | ||
369 | aff_properties.put("mp3.channels",new Integer(nChannels)); | ||
370 | nVBR = m_header.vbr(); | ||
371 | af_properties.put("vbr",new Boolean(nVBR)); | ||
372 | aff_properties.put("mp3.vbr",new Boolean(nVBR)); | ||
373 | aff_properties.put("mp3.vbr.scale",new Integer(m_header.vbr_scale())); | ||
374 | FrameSize = m_header.calculate_framesize(); | ||
375 | aff_properties.put("mp3.framesize.bytes",new Integer(FrameSize)); | ||
376 | if (FrameSize < 0) throw new UnsupportedAudioFileException("Invalid FrameSize : " + FrameSize); | ||
377 | nFrequency = m_header.frequency(); | ||
378 | aff_properties.put("mp3.frequency.hz",new Integer(nFrequency)); | ||
379 | FrameRate = (float) ((1.0 / (m_header.ms_per_frame())) * 1000.0); | ||
380 | aff_properties.put("mp3.framerate.fps",new Float(FrameRate)); | ||
381 | if (FrameRate < 0) throw new UnsupportedAudioFileException("Invalid FrameRate : " + FrameRate); | ||
382 | if (mLength != AudioSystem.NOT_SPECIFIED) | ||
383 | { | ||
384 | aff_properties.put("mp3.length.bytes",new Integer(mLength)); | ||
385 | nTotalFrames = m_header.max_number_of_frames(mLength); | ||
386 | aff_properties.put("mp3.length.frames",new Integer(nTotalFrames)); | ||
387 | } | ||
388 | BitRate = m_header.bitrate(); | ||
389 | af_properties.put("bitrate",new Integer(BitRate)); | ||
390 | aff_properties.put("mp3.bitrate.nominal.bps",new Integer(BitRate)); | ||
391 | nHeader = m_header.getSyncHeader(); | ||
392 | encoding = sm_aEncodings[nVersion][nLayer - 1]; | ||
393 | aff_properties.put("mp3.version.encoding",encoding.toString()); | ||
394 | if (mLength != AudioSystem.NOT_SPECIFIED) | ||
395 | { | ||
396 | nTotalMS = Math.round(m_header.total_ms(mLength)); | ||
397 | aff_properties.put("duration",new Long((long)nTotalMS*1000L)); | ||
398 | } | ||
399 | aff_properties.put("mp3.copyright",new Boolean(m_header.copyright())); | ||
400 | aff_properties.put("mp3.original",new Boolean(m_header.original())); | ||
401 | aff_properties.put("mp3.crc",new Boolean(m_header.checksums())); | ||
402 | aff_properties.put("mp3.padding",new Boolean(m_header.padding())); | ||
403 | InputStream id3v2 = m_bitstream.getRawID3v2(); | ||
404 | if (id3v2 != null) | ||
405 | { | ||
406 | aff_properties.put("mp3.id3tag.v2",id3v2); | ||
407 | parseID3v2Frames(id3v2,aff_properties); | ||
408 | } | ||
409 | if (TDebug.TraceAudioFileReader) TDebug.out(m_header.toString()); | ||
410 | } | ||
411 | catch (Exception e) | ||
412 | { | ||
413 | if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream:" + e.getMessage()); | ||
414 | throw new UnsupportedAudioFileException("not a MPEG stream:" + e.getMessage()); | ||
415 | } | ||
416 | |||
417 | // Deeper checks ? | ||
418 | int cVersion = (nHeader >> 19) & 0x3; | ||
419 | if (cVersion == 1) | ||
420 | { | ||
421 | if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong version"); | ||
422 | throw new UnsupportedAudioFileException("not a MPEG stream: wrong version"); | ||
423 | } | ||
424 | |||
425 | int cSFIndex = (nHeader >> 10) & 0x3; | ||
426 | if (cSFIndex == 3) | ||
427 | { | ||
428 | if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong sampling rate"); | ||
429 | throw new UnsupportedAudioFileException("not a MPEG stream: wrong sampling rate"); | ||
430 | } | ||
431 | |||
432 | // Look up for ID3v1 tag | ||
433 | if ((size == mediaLength) && (mediaLength != AudioSystem.NOT_SPECIFIED)) | ||
434 | { | ||
435 | FileInputStream fis = (FileInputStream) inputStream; | ||
436 | byte[] id3v1 = new byte[128]; | ||
437 | long bytesSkipped = fis.skip(inputStream.available()-id3v1.length); | ||
438 | int read = fis.read(id3v1,0,id3v1.length); | ||
439 | if ((id3v1[0]=='T') && (id3v1[1]=='A') && (id3v1[2]=='G')) | ||
440 | { | ||
441 | parseID3v1Frames(id3v1, aff_properties); | ||
442 | } | ||
443 | } | ||
444 | |||
445 | AudioFormat format = new MpegAudioFormat(encoding, (float) nFrequency, AudioSystem.NOT_SPECIFIED // SampleSizeInBits - The size of a sample | ||
446 | , nChannels // Channels - The number of channels | ||
447 | , -1 // The number of bytes in each frame | ||
448 | , FrameRate // FrameRate - The number of frames played or recorded per second | ||
449 | , true | ||
450 | , af_properties); | ||
451 | return new MpegAudioFileFormat(MpegFileFormatType.MP3, format, nTotalFrames, mLength,aff_properties); | ||
452 | } | ||
453 | |||
454 | /** | ||
455 | * Returns AudioInputStream from file. | ||
456 | */ | ||
457 | public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException | ||
458 | { | ||
459 | if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(File file)"); | ||
460 | InputStream inputStream = new FileInputStream(file); | ||
461 | try | ||
462 | { | ||
463 | return getAudioInputStream(inputStream); | ||
464 | } | ||
465 | catch (UnsupportedAudioFileException e) | ||
466 | { | ||
467 | if (inputStream != null) inputStream.close(); | ||
468 | throw e; | ||
469 | } | ||
470 | catch (IOException e) | ||
471 | { | ||
472 | if (inputStream != null) inputStream.close(); | ||
473 | throw e; | ||
474 | } | ||
475 | } | ||
476 | |||
477 | /** | ||
478 | * Returns AudioInputStream from url. | ||
479 | */ | ||
480 | public AudioInputStream getAudioInputStream(URL url) | ||
481 | throws UnsupportedAudioFileException, IOException | ||
482 | { | ||
483 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): begin"); } | ||
484 | long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; | ||
485 | URLConnection conn = url.openConnection(); | ||
486 | // Tell shoucast server (if any) that SPI support shoutcast stream. | ||
487 | boolean isShout = false; | ||
488 | int toRead=4; | ||
489 | byte[] head = new byte[toRead]; | ||
490 | |||
491 | conn.setRequestProperty ("Icy-Metadata", "1"); | ||
492 | BufferedInputStream bInputStream = new BufferedInputStream(conn.getInputStream()); | ||
493 | bInputStream.mark(toRead); | ||
494 | int read = bInputStream.read(head,0,toRead); | ||
495 | if ((read>2) && (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y')))) isShout = true; | ||
496 | bInputStream.reset(); | ||
497 | InputStream inputStream = null; | ||
498 | // Is is a shoutcast server ? | ||
499 | if (isShout == true) | ||
500 | { | ||
501 | // Yes | ||
502 | IcyInputStream icyStream = new IcyInputStream(bInputStream); | ||
503 | icyStream.addTagParseListener(IcyListener.getInstance()); | ||
504 | inputStream = icyStream; | ||
505 | } | ||
506 | else | ||
507 | { | ||
508 | // No, is Icecast 2 ? | ||
509 | String metaint = conn.getHeaderField("icy-metaint"); | ||
510 | if (metaint != null) | ||
511 | { | ||
512 | // Yes, it might be icecast 2 mp3 stream. | ||
513 | IcyInputStream icyStream = new IcyInputStream(bInputStream,metaint); | ||
514 | icyStream.addTagParseListener(IcyListener.getInstance()); | ||
515 | inputStream = icyStream; | ||
516 | } | ||
517 | else | ||
518 | { | ||
519 | // No | ||
520 | inputStream = bInputStream; | ||
521 | } | ||
522 | } | ||
523 | AudioInputStream audioInputStream = null; | ||
524 | try | ||
525 | { | ||
526 | audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); | ||
527 | } | ||
528 | catch (UnsupportedAudioFileException e) | ||
529 | { | ||
530 | inputStream.close(); | ||
531 | throw e; | ||
532 | } | ||
533 | catch (IOException e) | ||
534 | { | ||
535 | inputStream.close(); | ||
536 | throw e; | ||
537 | } | ||
538 | if (TDebug.TraceAudioFileReader) {TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): end"); } | ||
539 | return audioInputStream; | ||
540 | } | ||
541 | |||
542 | /** | ||
543 | * Return the AudioInputStream from the given InputStream. | ||
544 | */ | ||
545 | public AudioInputStream getAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException | ||
546 | { | ||
547 | if (TDebug.TraceAudioFileReader) TDebug.out("MpegAudioFileReader.getAudioInputStream(InputStream inputStream)"); | ||
548 | if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); | ||
549 | return super.getAudioInputStream(inputStream); | ||
550 | } | ||
551 | |||
552 | /** | ||
553 | * Parser ID3v1 frames | ||
554 | * @param frames | ||
555 | * @param props | ||
556 | */ | ||
557 | protected void parseID3v1Frames(byte[] frames, HashMap props) | ||
558 | { | ||
559 | if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v1"); | ||
560 | String tag = null; | ||
561 | try | ||
562 | { | ||
563 | tag = new String(frames, 0, frames.length, "ISO-8859-1"); | ||
564 | } | ||
565 | catch (UnsupportedEncodingException e) | ||
566 | { | ||
567 | tag = new String(frames, 0, frames.length); | ||
568 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot use ISO-8859-1"); | ||
569 | } | ||
570 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 frame dump='"+tag+"'"); | ||
571 | int start = 3; | ||
572 | String titlev1 = chopSubstring(tag, start, start += 30); | ||
573 | String titlev2 = (String) props.get("title"); | ||
574 | if (((titlev2==null) || (titlev2.length()==0)) && (titlev1 != null)) props.put("title",titlev1); | ||
575 | String artistv1 = chopSubstring(tag, start, start += 30); | ||
576 | String artistv2 = (String) props.get("author"); | ||
577 | if (((artistv2==null) || (artistv2.length()==0)) && (artistv1 != null)) props.put("author",artistv1); | ||
578 | String albumv1 = chopSubstring(tag, start, start += 30); | ||
579 | String albumv2 = (String) props.get("album"); | ||
580 | if (((albumv2==null) || (albumv2.length()==0)) && (albumv1 != null)) props.put("album",albumv1); | ||
581 | String yearv1 = chopSubstring(tag, start, start += 4); | ||
582 | String yearv2 = (String) props.get("year"); | ||
583 | if (((yearv2==null) || (yearv2.length()==0)) && (yearv1 != null)) props.put("date",yearv1); | ||
584 | String commentv1 = chopSubstring(tag, start, start += 28); | ||
585 | String commentv2 = (String) props.get("comment"); | ||
586 | if (((commentv2==null) || (commentv2.length()==0)) && (commentv1 != null)) props.put("comment",commentv1); | ||
587 | String trackv1 = ""+((int) (frames[126] & 0xff)); | ||
588 | String trackv2 = (String) props.get("mp3.id3tag.track"); | ||
589 | if (((trackv2==null) || (trackv2.length()==0)) && (trackv1 != null)) props.put("mp3.id3tag.track",trackv1); | ||
590 | |||
591 | int genrev1 = (int) (frames[127] & 0xff); | ||
592 | if ((genrev1 >=0) && (genrev1<id3v1genres.length)) | ||
593 | { | ||
594 | String genrev2 = (String) props.get("mp3.id3tag.genre"); | ||
595 | if (((genrev2==null) || (genrev2.length()==0))) props.put("mp3.id3tag.genre",id3v1genres[genrev1]); | ||
596 | } | ||
597 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 parsed"); | ||
598 | } | ||
599 | |||
600 | /** | ||
601 | * Extract | ||
602 | * @param s | ||
603 | * @param start | ||
604 | * @param end | ||
605 | * @return | ||
606 | */ | ||
607 | private String chopSubstring(String s, int start, int end) | ||
608 | { | ||
609 | String str = null; | ||
610 | // 11/28/04 - String encoding bug fix. | ||
611 | try | ||
612 | { | ||
613 | str = s.substring(start, end); | ||
614 | int loc = str.indexOf('\0'); | ||
615 | if (loc != -1) str = str.substring(0, loc); | ||
616 | } | ||
617 | catch (StringIndexOutOfBoundsException e) | ||
618 | { | ||
619 | // Skip encoding issues. | ||
620 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot chopSubString "+e.getMessage()); | ||
621 | } | ||
622 | return str; | ||
623 | } | ||
624 | /** | ||
625 | * Parse ID3v2 frames to add album (TALB), title (TIT2), date (TYER), author (TPE1), copyright (TCOP), comment (COMM) ... | ||
626 | * @param frames | ||
627 | * @param props | ||
628 | */ | ||
629 | protected void parseID3v2Frames(InputStream frames, HashMap props) | ||
630 | { | ||
631 | if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v2"); | ||
632 | byte[] bframes = null; | ||
633 | int size = -1; | ||
634 | try | ||
635 | { | ||
636 | size = frames.available(); | ||
637 | bframes = new byte[size]; | ||
638 | frames.mark(size); | ||
639 | frames.read(bframes); | ||
640 | frames.reset(); | ||
641 | } | ||
642 | catch (IOException e) | ||
643 | { | ||
644 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :"+e.getMessage()); | ||
645 | } | ||
646 | |||
647 | try | ||
648 | { | ||
649 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 frame dump='"+new String(bframes,0,bframes.length)+"'"); | ||
650 | /* ID3 tags : http://www.unixgods.org/~tilo/ID3/docs/ID3_comparison.html */ | ||
651 | String value = null; | ||
652 | for (int i=0;i<bframes.length-4;i++) | ||
653 | { | ||
654 | String code = new String(bframes,i,4); | ||
655 | String scode = new String(bframes,i,3); | ||
656 | // ID3v2.3 & v2.4 | ||
657 | if ((code.equals("TALB")) || (code.equals("TIT2")) || (code.equals("TYER")) || (code.equals("TPE1")) || (code.equals("TCOP")) || (code.equals("COMM")) || (code.equals("TCON")) || (code.equals("TRCK")) || (code.equals("TPOS")) || (code.equals("TDRC")) || (code.equals("TCOM")) || (code.equals("TIT1")) || (code.equals("TENC"))) | ||
658 | { | ||
659 | i=i+10; | ||
660 | size = (int) (bframes[i-6] << 24) + (bframes[i-5] << 16) + (bframes[i-4] << 8) + (bframes[i-3]); | ||
661 | if (code.equals("COMM")) value = parseText(bframes, i, size, 5); | ||
662 | else value = parseText(bframes,i, size, 1); | ||
663 | if ((value != null) && (value.length()>0)) | ||
664 | { | ||
665 | if (code.equals("TALB")) props.put("album",value); | ||
666 | else if (code.equals("TIT2")) props.put("title",value); | ||
667 | else if (code.equals("TYER")) props.put("date",value); | ||
668 | // ID3v2.4 date fix. | ||
669 | else if (code.equals("TDRC")) props.put("date",value); | ||
670 | else if (code.equals("TPE1")) props.put("author",value); | ||
671 | else if (code.equals("TCOP")) props.put("copyright",value); | ||
672 | else if (code.equals("COMM")) props.put("comment",value); | ||
673 | else if (code.equals("TCON")) props.put("mp3.id3tag.genre",value); | ||
674 | else if (code.equals("TRCK")) props.put("mp3.id3tag.track",value); | ||
675 | else if (code.equals("TPOS")) props.put("mp3.id3tag.disc",value); | ||
676 | else if (code.equals("TCOM")) props.put("mp3.id3tag.composer",value); | ||
677 | else if (code.equals("TIT1")) props.put("mp3.id3tag.grouping",value); | ||
678 | else if (code.equals("TENC")) props.put("mp3.id3tag.encoded",value); | ||
679 | } | ||
680 | i=i+size-1; | ||
681 | } | ||
682 | // ID3v2.2. | ||
683 | else if ((scode.equals("TAL")) || (scode.equals("TT2")) || (scode.equals("TP1")) || (scode.equals("TYE")) || (scode.equals("TRK")) || (scode.equals("TPA")) || (scode.equals("TCR")) || (scode.equals("TCO")) || (scode.equals("TCM")) || (scode.equals("COM")) || (scode.equals("TT1")) || (scode.equals("TEN"))) | ||
684 | { | ||
685 | i=i+6; | ||
686 | size = (int) (0x00000000) + (bframes[i-3] << 16) + (bframes[i-2] << 8) + (bframes[i-1]); | ||
687 | if (scode.equals("COM")) value = parseText(bframes, i, size, 5); | ||
688 | else value = parseText(bframes,i, size, 1); | ||
689 | if ((value != null) && (value.length()>0)) | ||
690 | { | ||
691 | if (scode.equals("TAL")) props.put("album",value); | ||
692 | else if (scode.equals("TT2")) props.put("title",value); | ||
693 | else if (scode.equals("TYE")) props.put("date",value); | ||
694 | else if (scode.equals("TP1")) props.put("author",value); | ||
695 | else if (scode.equals("TCR")) props.put("copyright",value); | ||
696 | else if (scode.equals("COM")) props.put("comment",value); | ||
697 | else if (scode.equals("TCO")) props.put("mp3.id3tag.genre",value); | ||
698 | else if (scode.equals("TRK")) props.put("mp3.id3tag.track",value); | ||
699 | else if (scode.equals("TPA")) props.put("mp3.id3tag.disc",value); | ||
700 | else if (scode.equals("TCM")) props.put("mp3.id3tag.composer",value); | ||
701 | else if (scode.equals("TT1")) props.put("mp3.id3tag.grouping",value); | ||
702 | else if (scode.equals("TEN")) props.put("mp3.id3tag.encoded",value); | ||
703 | } | ||
704 | i=i+size-1; | ||
705 | } | ||
706 | else if (code.startsWith("ID3")) | ||
707 | { | ||
708 | // ID3v2 Header. | ||
709 | int v2version = (int) (bframes[3] & 0xFF); | ||
710 | props.put("mp3.id3tag.v2.version",String.valueOf(v2version)); | ||
711 | i=i+4; | ||
712 | } | ||
713 | } | ||
714 | } | ||
715 | catch (RuntimeException e) | ||
716 | { | ||
717 | // Ignore all parsing errors. | ||
718 | if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :"+e.getMessage()); | ||
719 | } | ||
720 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 parsed"); | ||
721 | } | ||
722 | |||
723 | /** | ||
724 | * Parse Text Frames. | ||
725 | * @param bframes | ||
726 | * @param offset | ||
727 | * @param size | ||
728 | * @param skip | ||
729 | * @return | ||
730 | */ | ||
731 | protected String parseText(byte[] bframes, int offset, int size, int skip) | ||
732 | { | ||
733 | String value = null; | ||
734 | try | ||
735 | { | ||
736 | String[] ENC_TYPES = {"ISO-8859-1", "UTF16","UTF-16BE", "UTF-8"}; | ||
737 | value = new String(bframes,offset+skip,size-skip,ENC_TYPES[bframes[offset]]); | ||
738 | value = chopSubstring(value,0,value.length()); | ||
739 | } | ||
740 | catch (UnsupportedEncodingException e) | ||
741 | { | ||
742 | if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 Encoding error :"+e.getMessage()); | ||
743 | } | ||
744 | return value; | ||
745 | } | ||
746 | |||
747 | /** | ||
748 | * Load shoutcast (ICY) info. | ||
749 | * @param input | ||
750 | * @param props | ||
751 | * @throws IOException | ||
752 | */ | ||
753 | protected void loadShoutcastInfo(InputStream input, HashMap props) throws IOException | ||
754 | { | ||
755 | IcyInputStream icy = new IcyInputStream(new BufferedInputStream(input)); | ||
756 | HashMap metadata = icy.getTagHash(); | ||
757 | MP3Tag titleMP3Tag = icy.getTag("icy-name"); | ||
758 | if (titleMP3Tag != null) props.put("title",((String) titleMP3Tag.getValue()).trim()); | ||
759 | MP3Tag[] meta = icy.getTags(); | ||
760 | if (meta != null) | ||
761 | { | ||
762 | StringBuffer metaStr = new StringBuffer(); | ||
763 | for (int i=0;i<meta.length;i++) | ||
764 | { | ||
765 | String key = meta[i].getName(); | ||
766 | String value = ((String) icy.getTag(key).getValue()).trim(); | ||
767 | props.put("mp3.shoutcast.metadata."+key, value); | ||
768 | } | ||
769 | } | ||
770 | } | ||
771 | |||
772 | } \ No newline at end of file | ||