summaryrefslogtreecommitdiff
path: root/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java
diff options
context:
space:
mode:
authorBjörn Stenberg <bjorn@haxx.se>2007-01-08 23:53:00 +0000
committerBjörn Stenberg <bjorn@haxx.se>2007-01-08 23:53:00 +0000
commit7039a05147b8bbfc829babea1c65bd436450b505 (patch)
tree4ba555eb84ed97b72b0575034d5b0530a393713e /songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java
parent6d4c19707ef95942e323cbdc89fbbfdbe45e7cc5 (diff)
downloadrockbox-7039a05147b8bbfc829babea1c65bd436450b505.tar.gz
rockbox-7039a05147b8bbfc829babea1c65bd436450b505.zip
Splitting out songdbj
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11953 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java')
-rw-r--r--songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java772
1 files changed, 0 insertions, 772 deletions
diff --git a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java b/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java
deleted file mode 100644
index 54440551a1..0000000000
--- a/songdbj/javazoom/spi/mpeg/sampled/file/MpegAudioFileReader.java
+++ /dev/null
@@ -1,772 +0,0 @@
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
35package javazoom.spi.mpeg.sampled.file;
36
37
38import java.io.BufferedInputStream;
39import java.io.File;
40import java.io.FileInputStream;
41import java.io.IOException;
42import java.io.InputStream;
43import java.io.PushbackInputStream;
44import java.io.UnsupportedEncodingException;
45import java.net.URL;
46import java.net.URLConnection;
47import java.security.AccessControlException;
48import java.util.HashMap;
49
50import javax.sound.sampled.AudioFileFormat;
51import javax.sound.sampled.AudioFormat;
52import javax.sound.sampled.AudioInputStream;
53import javax.sound.sampled.AudioSystem;
54import javax.sound.sampled.UnsupportedAudioFileException;
55
56import javazoom.jl.decoder.Bitstream;
57import javazoom.jl.decoder.Header;
58import javazoom.spi.mpeg.sampled.file.tag.IcyInputStream;
59import javazoom.spi.mpeg.sampled.file.tag.MP3Tag;
60
61import org.tritonus.share.TDebug;
62import org.tritonus.share.sampled.file.TAudioFileReader;
63
64/**
65 * This class implements AudioFileReader for MP3 SPI.
66 */
67public 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