/* * MpegAudioFileReader. * * 10/10/05 : size computation bug fixed in parseID3v2Frames. * RIFF/MP3 header support added. * FLAC and MAC headers throw UnsupportedAudioFileException now. * "mp3.id3tag.publisher" (TPUB/TPB) added. * "mp3.id3tag.orchestra" (TPE2/TP2) added. * "mp3.id3tag.length" (TLEN/TLE) added. * * 08/15/05 : parseID3v2Frames improved. * * 12/31/04 : mp3spi.weak system property added to skip controls. * * 11/29/04 : ID3v2.2, v2.3 & v2.4 support improved. * "mp3.id3tag.composer" (TCOM/TCM) added * "mp3.id3tag.grouping" (TIT1/TT1) added * "mp3.id3tag.disc" (TPA/TPOS) added * "mp3.id3tag.encoded" (TEN/TENC) added * "mp3.id3tag.v2.version" added * * 11/28/04 : String encoding bug fix in chopSubstring method. * * JavaZOOM : mp3spi@javazoom.net * http://www.javazoom.net * *----------------------------------------------------------------------- * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *---------------------------------------------------------------------- */ package javazoom.spi.mpeg.sampled.file; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLConnection; import java.security.AccessControlException; import java.util.HashMap; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.UnsupportedAudioFileException; import javazoom.jl.decoder.Bitstream; import javazoom.jl.decoder.Header; import javazoom.spi.mpeg.sampled.file.tag.IcyInputStream; import javazoom.spi.mpeg.sampled.file.tag.MP3Tag; import org.tritonus.share.TDebug; import org.tritonus.share.sampled.file.TAudioFileReader; /** * This class implements AudioFileReader for MP3 SPI. */ @SuppressWarnings("all") public class MpegAudioFileReader extends TAudioFileReader { public static final String VERSION = "MP3SPI 1.9.5"; private final int SYNC = 0xFFE00000; private String weak = null; private final AudioFormat.Encoding[][] sm_aEncodings = { { MpegEncoding.MPEG2L1, MpegEncoding.MPEG2L2, MpegEncoding.MPEG2L3 }, { MpegEncoding.MPEG1L1, MpegEncoding.MPEG1L2, MpegEncoding.MPEG1L3 }, { MpegEncoding.MPEG2DOT5L1, MpegEncoding.MPEG2DOT5L2, MpegEncoding.MPEG2DOT5L3 }, }; public static int INITAL_READ_LENGTH = 128000*32; private static int MARK_LIMIT = INITAL_READ_LENGTH + 1; static { String s = System.getProperty("marklimit"); if (s != null) { try { INITAL_READ_LENGTH = Integer.parseInt(s); MARK_LIMIT = INITAL_READ_LENGTH + 1; } catch (NumberFormatException e) { e.printStackTrace(); } } } private static final String[] id3v1genres = { "Blues" , "Classic Rock" , "Country" , "Dance" , "Disco" , "Funk" , "Grunge" , "Hip-Hop" , "Jazz" , "Metal" , "New Age" , "Oldies" , "Other" , "Pop" , "R&B" , "Rap" , "Reggae" , "Rock" , "Techno" , "Industrial" , "Alternative" , "Ska" , "Death Metal" , "Pranks" , "Soundtrack" , "Euro-Techno" , "Ambient" , "Trip-Hop" , "Vocal" , "Jazz+Funk" , "Fusion" , "Trance" , "Classical" , "Instrumental" , "Acid" , "House" , "Game" , "Sound Clip" , "Gospel" , "Noise" , "AlternRock" , "Bass" , "Soul" , "Punk" , "Space" , "Meditative" , "Instrumental Pop" , "Instrumental Rock" , "Ethnic" , "Gothic" , "Darkwave" , "Techno-Industrial" , "Electronic" , "Pop-Folk" , "Eurodance" , "Dream" , "Southern Rock" , "Comedy" , "Cult" , "Gangsta" , "Top 40" , "Christian Rap" , "Pop/Funk" , "Jungle" , "Native American" , "Cabaret" , "New Wave" , "Psychadelic" , "Rave" , "Showtunes" , "Trailer" , "Lo-Fi" , "Tribal" , "Acid Punk" , "Acid Jazz" , "Polka" , "Retro" , "Musical" , "Rock & Roll" , "Hard Rock" , "Folk" , "Folk-Rock" , "National Folk" , "Swing" , "Fast Fusion" , "Bebob" , "Latin" , "Revival" , "Celtic" , "Bluegrass" , "Avantgarde" , "Gothic Rock" , "Progressive Rock" , "Psychedelic Rock" , "Symphonic Rock" , "Slow Rock" , "Big Band" , "Chorus" , "Easy Listening" , "Acoustic" , "Humour" , "Speech" , "Chanson" , "Opera" , "Chamber Music" , "Sonata" , "Symphony" , "Booty Brass" , "Primus" , "Porn Groove" , "Satire" , "Slow Jam" , "Club" , "Tango" , "Samba" , "Folklore" , "Ballad" , "Power Ballad" , "Rhythmic Soul" , "Freestyle" , "Duet" , "Punk Rock" , "Drum Solo" , "A Capela" , "Euro-House" , "Dance Hall" , "Goa" , "Drum & Bass" , "Club-House" , "Hardcore" , "Terror" , "Indie" , "BritPop" , "Negerpunk" , "Polsk Punk" , "Beat" , "Christian Gangsta Rap" , "Heavy Metal" , "Black Metal" , "Crossover" , "Contemporary Christian" , "Christian Rock" , "Merengue" , "Salsa" , "Thrash Metal" , "Anime" , "JPop" , "SynthPop" }; public MpegAudioFileReader() { super(MARK_LIMIT, true); if (TDebug.TraceAudioFileReader) TDebug.out(VERSION); try { weak = System.getProperty("mp3spi.weak"); } catch (AccessControlException e) { } } /** * Returns AudioFileFormat from File. */ public AudioFileFormat getAudioFileFormat(File file) throws UnsupportedAudioFileException, IOException { return super.getAudioFileFormat(file); } /** * Returns AudioFileFormat from URL. */ public AudioFileFormat getAudioFileFormat(URL url) throws UnsupportedAudioFileException, IOException { if (TDebug.TraceAudioFileReader) { TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): begin"); } long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; URLConnection conn = url.openConnection(); // Tell shoucast server (if any) that SPI support shoutcast stream. conn.setRequestProperty("Icy-Metadata", "1"); InputStream inputStream = conn.getInputStream(); AudioFileFormat audioFileFormat = null; try { audioFileFormat = getAudioFileFormat(inputStream, lFileLengthInBytes); } finally { inputStream.close(); } if (TDebug.TraceAudioFileReader) { TDebug.out("MpegAudioFileReader.getAudioFileFormat(URL): end"); } return audioFileFormat; } /** * Returns AudioFileFormat from inputstream and medialength. */ public AudioFileFormat getAudioFileFormat(InputStream inputStream, long mediaLength) throws UnsupportedAudioFileException, IOException { if (TDebug.TraceAudioFileReader) TDebug.out(">MpegAudioFileReader.getAudioFileFormat(InputStream inputStream, long mediaLength): begin"); HashMap aff_properties = new HashMap(); HashMap af_properties = new HashMap(); int mLength = (int) mediaLength; int size = inputStream.available(); PushbackInputStream pis = new PushbackInputStream(inputStream, MARK_LIMIT); byte head[] = new byte[22]; pis.read(head); if (TDebug.TraceAudioFileReader) { TDebug.out("InputStream : " + inputStream + " =>" + new String(head)); } // Check for WAV, AU, and AIFF, Ogg Vorbis, Flac, MAC file formats. // Next check for Shoutcast (supported) and OGG (unsupported) streams. 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')) { if (TDebug.TraceAudioFileReader) TDebug.out("RIFF/WAV stream found"); int isPCM = ((head[21]<<8)&0x0000FF00) | ((head[20])&0x00000FF); if (weak == null) { if (isPCM == 1) throw new UnsupportedAudioFileException("WAV PCM stream found"); } } else if ((head[0] == '.') && (head[1] == 's') && (head[2] == 'n') && (head[3] == 'd')) { if (TDebug.TraceAudioFileReader) TDebug.out("AU stream found"); if (weak == null) throw new UnsupportedAudioFileException("AU stream found"); } 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')) { if (TDebug.TraceAudioFileReader) TDebug.out("AIFF stream found"); if (weak == null) throw new UnsupportedAudioFileException("AIFF stream found"); } else if (((head[0] == 'M') | (head[0] == 'm')) && ((head[1] == 'A') | (head[1] == 'a')) && ((head[2] == 'C') | (head[2] == 'c'))) { if (TDebug.TraceAudioFileReader) TDebug.out("APE stream found"); if (weak == null) throw new UnsupportedAudioFileException("APE stream found"); } else if (((head[0] == 'F') | (head[0] == 'f')) && ((head[1] == 'L') | (head[1] == 'l')) && ((head[2] == 'A') | (head[2] == 'a')) && ((head[3] == 'C') | (head[3] == 'c'))) { if (TDebug.TraceAudioFileReader) TDebug.out("FLAC stream found"); if (weak == null) throw new UnsupportedAudioFileException("FLAC stream found"); } // Shoutcast stream ? else if (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y'))) { pis.unread(head); // Load shoutcast meta data. loadShoutcastInfo(pis, aff_properties); } // Ogg stream ? else if (((head[0] == 'O') | (head[0] == 'o')) && ((head[1] == 'G') | (head[1] == 'g')) && ((head[2] == 'G') | (head[2] == 'g'))) { if (TDebug.TraceAudioFileReader) TDebug.out("Ogg stream found"); if (weak == null) throw new UnsupportedAudioFileException("Ogg stream found"); } // No, so pushback. else { pis.unread(head); } // MPEG header info. int nVersion = AudioSystem.NOT_SPECIFIED; int nLayer = AudioSystem.NOT_SPECIFIED; int nSFIndex = AudioSystem.NOT_SPECIFIED; int nMode = AudioSystem.NOT_SPECIFIED; int FrameSize = AudioSystem.NOT_SPECIFIED; int nFrameSize = AudioSystem.NOT_SPECIFIED; int nFrequency = AudioSystem.NOT_SPECIFIED; int nTotalFrames = AudioSystem.NOT_SPECIFIED; float FrameRate = AudioSystem.NOT_SPECIFIED; int BitRate = AudioSystem.NOT_SPECIFIED; int nChannels = AudioSystem.NOT_SPECIFIED; int nHeader = AudioSystem.NOT_SPECIFIED; int nTotalMS = AudioSystem.NOT_SPECIFIED; boolean nVBR = false; AudioFormat.Encoding encoding = null; try { Bitstream m_bitstream = new Bitstream(pis); int streamPos = m_bitstream.header_pos(); aff_properties.put("mp3.header.pos", new Integer(streamPos)); Header m_header = m_bitstream.readFrame(); // nVersion = 0 => MPEG2-LSF (Including MPEG2.5), nVersion = 1 => MPEG1 nVersion = m_header.version(); if (nVersion == 2) aff_properties.put("mp3.version.mpeg", Float.toString(2.5f)); else aff_properties.put("mp3.version.mpeg", Integer.toString(2 - nVersion)); // nLayer = 1,2,3 nLayer = m_header.layer(); aff_properties.put("mp3.version.layer", Integer.toString(nLayer)); nSFIndex = m_header.sample_frequency(); nMode = m_header.mode(); aff_properties.put("mp3.mode", new Integer(nMode)); nChannels = nMode == 3 ? 1 : 2; aff_properties.put("mp3.channels", new Integer(nChannels)); nVBR = m_header.vbr(); af_properties.put("vbr", new Boolean(nVBR)); aff_properties.put("mp3.vbr", new Boolean(nVBR)); aff_properties.put("mp3.vbr.scale", new Integer(m_header.vbr_scale())); FrameSize = m_header.calculate_framesize(); aff_properties.put("mp3.framesize.bytes", new Integer(FrameSize)); if (FrameSize < 0) throw new UnsupportedAudioFileException("Invalid FrameSize : " + FrameSize); nFrequency = m_header.frequency(); aff_properties.put("mp3.frequency.hz", new Integer(nFrequency)); FrameRate = (float) ((1.0 / (m_header.ms_per_frame())) * 1000.0); aff_properties.put("mp3.framerate.fps", new Float(FrameRate)); if (FrameRate < 0) throw new UnsupportedAudioFileException("Invalid FrameRate : " + FrameRate); // Remove heading tag length from real stream length. int tmpLength = mLength; if ((streamPos > 0) && (mLength != AudioSystem.NOT_SPECIFIED) && (streamPos < mLength)) tmpLength = tmpLength - streamPos; if (mLength != AudioSystem.NOT_SPECIFIED) { aff_properties.put("mp3.length.bytes", new Integer(mLength)); nTotalFrames = m_header.max_number_of_frames(tmpLength); aff_properties.put("mp3.length.frames", new Integer(nTotalFrames)); } BitRate = m_header.bitrate(); af_properties.put("bitrate", new Integer(BitRate)); aff_properties.put("mp3.bitrate.nominal.bps", new Integer(BitRate)); nHeader = m_header.getSyncHeader(); encoding = sm_aEncodings[nVersion][nLayer - 1]; aff_properties.put("mp3.version.encoding", encoding.toString()); if (mLength != AudioSystem.NOT_SPECIFIED) { nTotalMS = Math.round(m_header.total_ms(tmpLength)); aff_properties.put("duration", new Long((long) nTotalMS * 1000L)); } aff_properties.put("mp3.copyright", new Boolean(m_header.copyright())); aff_properties.put("mp3.original", new Boolean(m_header.original())); aff_properties.put("mp3.crc", new Boolean(m_header.checksums())); aff_properties.put("mp3.padding", new Boolean(m_header.padding())); InputStream id3v2 = m_bitstream.getRawID3v2(); if (id3v2 != null) { aff_properties.put("mp3.id3tag.v2", id3v2); parseID3v2Frames(id3v2, aff_properties); } if (TDebug.TraceAudioFileReader) TDebug.out(m_header.toString()); } catch (Exception e) { if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream:" + e.getMessage()); throw new UnsupportedAudioFileException("not a MPEG stream:" + e.getMessage()); } // Deeper checks ? int cVersion = (nHeader >> 19) & 0x3; if (cVersion == 1) { if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong version"); throw new UnsupportedAudioFileException("not a MPEG stream: wrong version"); } int cSFIndex = (nHeader >> 10) & 0x3; if (cSFIndex == 3) { if (TDebug.TraceAudioFileReader) TDebug.out("not a MPEG stream: wrong sampling rate"); throw new UnsupportedAudioFileException("not a MPEG stream: wrong sampling rate"); } // Look up for ID3v1 tag if ((size == mediaLength) && (mediaLength != AudioSystem.NOT_SPECIFIED)) { byte[] id3v1 = new byte[128]; long bytesSkipped = inputStream.skip(inputStream.available() - id3v1.length); int read = inputStream.read(id3v1, 0, id3v1.length); if ((id3v1[0] == 'T') && (id3v1[1] == 'A') && (id3v1[2] == 'G')) { parseID3v1Frames(id3v1, aff_properties); } } AudioFormat format = new MpegAudioFormat(encoding, (float) nFrequency, AudioSystem.NOT_SPECIFIED // SampleSizeInBits - The size of a sample , nChannels // Channels - The number of channels , -1 // The number of bytes in each frame , FrameRate // FrameRate - The number of frames played or recorded per second , true, af_properties); return new MpegAudioFileFormat(MpegFileFormatType.MP3, format, nTotalFrames, mLength, aff_properties); } /** * Returns AudioInputStream from file. */ public AudioInputStream getAudioInputStream(File file) throws UnsupportedAudioFileException, IOException { if (TDebug.TraceAudioFileReader) TDebug.out("getAudioInputStream(File file)"); InputStream inputStream = new FileInputStream(file); try { return getAudioInputStream(inputStream); } catch (UnsupportedAudioFileException e) { if (inputStream != null) inputStream.close(); throw e; } catch (IOException e) { if (inputStream != null) inputStream.close(); throw e; } } /** * Returns AudioInputStream from url. */ public AudioInputStream getAudioInputStream(URL url) throws UnsupportedAudioFileException, IOException { if (TDebug.TraceAudioFileReader) { TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): begin"); } long lFileLengthInBytes = AudioSystem.NOT_SPECIFIED; URLConnection conn = url.openConnection(); // Tell shoucast server (if any) that SPI support shoutcast stream. boolean isShout = false; int toRead = 4; byte[] head = new byte[toRead]; conn.setRequestProperty("Icy-Metadata", "1"); BufferedInputStream bInputStream = new BufferedInputStream(conn.getInputStream()); bInputStream.mark(toRead); int read = bInputStream.read(head, 0, toRead); if ((read > 2) && (((head[0] == 'I') | (head[0] == 'i')) && ((head[1] == 'C') | (head[1] == 'c')) && ((head[2] == 'Y') | (head[2] == 'y')))) isShout = true; bInputStream.reset(); InputStream inputStream = null; // Is is a shoutcast server ? if (isShout == true) { // Yes IcyInputStream icyStream = new IcyInputStream(bInputStream); icyStream.addTagParseListener(IcyListener.getInstance()); inputStream = icyStream; } else { // No, is Icecast 2 ? String metaint = conn.getHeaderField("icy-metaint"); if (metaint != null) { // Yes, it might be icecast 2 mp3 stream. IcyInputStream icyStream = new IcyInputStream(bInputStream, metaint); icyStream.addTagParseListener(IcyListener.getInstance()); inputStream = icyStream; } else { // No inputStream = bInputStream; } } AudioInputStream audioInputStream = null; try { audioInputStream = getAudioInputStream(inputStream, lFileLengthInBytes); } catch (UnsupportedAudioFileException e) { inputStream.close(); throw e; } catch (IOException e) { inputStream.close(); throw e; } if (TDebug.TraceAudioFileReader) { TDebug.out("MpegAudioFileReader.getAudioInputStream(URL): end"); } return audioInputStream; } /** * Return the AudioInputStream from the given InputStream. */ public AudioInputStream getAudioInputStream(InputStream inputStream) throws UnsupportedAudioFileException, IOException { if (TDebug.TraceAudioFileReader) TDebug.out("MpegAudioFileReader.getAudioInputStream(InputStream inputStream)"); if (!inputStream.markSupported()) inputStream = new BufferedInputStream(inputStream); return super.getAudioInputStream(inputStream); } /** * Parser ID3v1 frames * @param frames * @param props */ protected void parseID3v1Frames(byte[] frames, HashMap props) { if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v1"); String tag = null; try { tag = new String(frames, 0, frames.length, "ISO-8859-1"); } catch (UnsupportedEncodingException e) { tag = new String(frames, 0, frames.length); if (TDebug.TraceAudioFileReader) TDebug.out("Cannot use ISO-8859-1"); } if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 frame dump='" + tag + "'"); int start = 3; String titlev1 = chopSubstring(tag, start, start += 30); String titlev2 = (String) props.get("title"); if (((titlev2 == null) || (titlev2.length() == 0)) && (titlev1 != null)) props.put("title", titlev1); String artistv1 = chopSubstring(tag, start, start += 30); String artistv2 = (String) props.get("author"); if (((artistv2 == null) || (artistv2.length() == 0)) && (artistv1 != null)) props.put("author", artistv1); String albumv1 = chopSubstring(tag, start, start += 30); String albumv2 = (String) props.get("album"); if (((albumv2 == null) || (albumv2.length() == 0)) && (albumv1 != null)) props.put("album", albumv1); String yearv1 = chopSubstring(tag, start, start += 4); String yearv2 = (String) props.get("year"); if (((yearv2 == null) || (yearv2.length() == 0)) && (yearv1 != null)) props.put("date", yearv1); String commentv1 = chopSubstring(tag, start, start += 28); String commentv2 = (String) props.get("comment"); if (((commentv2 == null) || (commentv2.length() == 0)) && (commentv1 != null)) props.put("comment", commentv1); String trackv1 = "" + ((int) (frames[126] & 0xff)); String trackv2 = (String) props.get("mp3.id3tag.track"); if (((trackv2 == null) || (trackv2.length() == 0)) && (trackv1 != null)) props.put("mp3.id3tag.track", trackv1); int genrev1 = (int) (frames[127] & 0xff); if ((genrev1 >= 0) && (genrev1 < id3v1genres.length)) { String genrev2 = (String) props.get("mp3.id3tag.genre"); if (((genrev2 == null) || (genrev2.length() == 0))) props.put("mp3.id3tag.genre", id3v1genres[genrev1]); } if (TDebug.TraceAudioFileReader) TDebug.out("ID3v1 parsed"); } /** * Extract * @param s * @param start * @param end * @return */ private String chopSubstring(String s, int start, int end) { String str = null; // 11/28/04 - String encoding bug fix. try { str = s.substring(start, end); int loc = str.indexOf('\0'); if (loc != -1) str = str.substring(0, loc); } catch (StringIndexOutOfBoundsException e) { // Skip encoding issues. if (TDebug.TraceAudioFileReader) TDebug.out("Cannot chopSubString " + e.getMessage()); } return str; } /** * Parse ID3v2 frames to add album (TALB), title (TIT2), date (TYER), author (TPE1), copyright (TCOP), comment (COMM) ... * @param frames * @param props */ protected void parseID3v2Frames(InputStream frames, HashMap props) { if (TDebug.TraceAudioFileReader) TDebug.out("Parsing ID3v2"); byte[] bframes = null; int size = -1; try { size = frames.available(); bframes = new byte[size]; frames.mark(size); frames.read(bframes); frames.reset(); } catch (IOException e) { if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :" + e.getMessage()); } if (!"ID3".equals(new String(bframes, 0, 3))) { TDebug.out("No ID3v2 header found!"); return; } int v2version = (int) (bframes[3] & 0xFF); props.put("mp3.id3tag.v2.version", String.valueOf(v2version)); if (v2version < 2 || v2version > 4) { TDebug.out("Unsupported ID3v2 version " + v2version + "!"); return; } try { if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 frame dump='" + new String(bframes, 0, bframes.length) + "'"); /* ID3 tags : http://www.unixgods.org/~tilo/ID3/docs/ID3_comparison.html */ String value = null; for (int i = 10; i < bframes.length && bframes[i] > 0; i += size) { if (v2version == 3 || v2version == 4) { // ID3v2.3 & ID3v2.4 String code = new String(bframes, i, 4); size = (int) ((bframes[i + 4] << 24) & 0xFF000000 | (bframes[i + 5] << 16) & 0x00FF0000 | (bframes[i + 6] << 8) & 0x0000FF00 | (bframes[i + 7]) & 0x000000FF); i += 10; 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")) || (code.equals("TPUB")) || (code.equals("TPE2")) || (code.equals("TLEN")) ) { if (code.equals("COMM")) value = parseText(bframes, i, size, 5); else value = parseText(bframes, i, size, 1); if ((value != null) && (value.length() > 0)) { if (code.equals("TALB")) props.put("album", value); else if (code.equals("TIT2")) props.put("title", value); else if (code.equals("TYER")) props.put("date", value); // ID3v2.4 date fix. else if (code.equals("TDRC")) props.put("date", value); else if (code.equals("TPE1")) props.put("author", value); else if (code.equals("TCOP")) props.put("copyright", value); else if (code.equals("COMM")) props.put("comment", value); else if (code.equals("TCON")) props.put("mp3.id3tag.genre", value); else if (code.equals("TRCK")) props.put("mp3.id3tag.track", value); else if (code.equals("TPOS")) props.put("mp3.id3tag.disc", value); else if (code.equals("TCOM")) props.put("mp3.id3tag.composer", value); else if (code.equals("TIT1")) props.put("mp3.id3tag.grouping", value); else if (code.equals("TENC")) props.put("mp3.id3tag.encoded", value); else if (code.equals("TPUB")) props.put("mp3.id3tag.publisher", value); else if (code.equals("TPE2")) props.put("mp3.id3tag.orchestra", value); else if (code.equals("TLEN")) props.put("mp3.id3tag.length", value); } } } else { // ID3v2.2 String scode = new String(bframes, i, 3); size = (int) (0x00000000) + (bframes[i + 3] << 16) + (bframes[i + 4] << 8) + (bframes[i + 5]); i += 6; 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")) || (scode.equals("TPB")) || (scode.equals("TP2")) || (scode.equals("TLE")) ) { if (scode.equals("COM")) value = parseText(bframes, i, size, 5); else value = parseText(bframes, i, size, 1); if ((value != null) && (value.length() > 0)) { if (scode.equals("TAL")) props.put("album", value); else if (scode.equals("TT2")) props.put("title", value); else if (scode.equals("TYE")) props.put("date", value); else if (scode.equals("TP1")) props.put("author", value); else if (scode.equals("TCR")) props.put("copyright", value); else if (scode.equals("COM")) props.put("comment", value); else if (scode.equals("TCO")) props.put("mp3.id3tag.genre", value); else if (scode.equals("TRK")) props.put("mp3.id3tag.track", value); else if (scode.equals("TPA")) props.put("mp3.id3tag.disc", value); else if (scode.equals("TCM")) props.put("mp3.id3tag.composer", value); else if (scode.equals("TT1")) props.put("mp3.id3tag.grouping", value); else if (scode.equals("TEN")) props.put("mp3.id3tag.encoded", value); else if (scode.equals("TPB")) props.put("mp3.id3tag.publisher", value); else if (scode.equals("TP2")) props.put("mp3.id3tag.orchestra", value); else if (scode.equals("TLE")) props.put("mp3.id3tag.length", value); } } } } } catch (RuntimeException e) { // Ignore all parsing errors. if (TDebug.TraceAudioFileReader) TDebug.out("Cannot parse ID3v2 :" + e.getMessage()); } if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 parsed"); } /** * Parse Text Frames. * * @param bframes * @param offset * @param size * @param skip * @return */ protected String parseText(byte[] bframes, int offset, int size, int skip) { String value = null; try { String[] ENC_TYPES = { "ISO-8859-1", "UTF16", "UTF-16BE", "UTF-8" }; value = new String(bframes, offset + skip, size - skip, ENC_TYPES[bframes[offset]]); value = chopSubstring(value, 0, value.length()); } catch (UnsupportedEncodingException e) { if (TDebug.TraceAudioFileReader) TDebug.out("ID3v2 Encoding error :" + e.getMessage()); } return value; } /** * Load shoutcast (ICY) info. * * @param input * @param props * @throws IOException */ protected void loadShoutcastInfo(InputStream input, HashMap props) throws IOException { IcyInputStream icy = new IcyInputStream(new BufferedInputStream(input)); HashMap metadata = icy.getTagHash(); MP3Tag titleMP3Tag = icy.getTag("icy-name"); if (titleMP3Tag != null) props.put("title", ((String) titleMP3Tag.getValue()).trim()); MP3Tag[] meta = icy.getTags(); if (meta != null) { StringBuffer metaStr = new StringBuffer(); for (int i = 0; i < meta.length; i++) { String key = meta[i].getName(); String value = ((String) icy.getTag(key).getValue()).trim(); props.put("mp3.shoutcast.metadata." + key, value); } } } }