This simple tag parser is very useful when you just need to get the basic information about the MP3 files, such as the title and the artist. Of course it could be extended to extract more information if necessary.
Apache license.
/* * Copyright (C) 2011-2012 George Yunaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. */ public class MetaInfoParser_MP3 { // Stores the data private String m_artist; private String m_title; public String getArtist() { return m_artist == null ? "Unknown" : m_artist; } public String getTitle() { return m_title == null ? "Unknown" : m_title; } // Same as parse but doesn't throw exceptions public boolean parseNoThrow( File mp3file ) { try { return parse( mp3file ); } catch ( Exception ex ) { return false; } } // Parses the MP3 file, and sets up artist and/or title. // Returns true if the file is MP3 file, it contains the ID3 tag, and at least artist or title is parsed public boolean parse( File mp3file ) throws IOException { m_artist = null; m_title = null; // Read the first five bytes of the ID3 header - http://id3.org/id3v2-00 RandomAccessFile file = new RandomAccessFile( mp3file, "r" ); byte[] headerbuf = new byte[10]; file.read( headerbuf ); // Parse it quickly if ( headerbuf[0] != 'I' || headerbuf[1] != 'D' || headerbuf[2] != '3' ) { file.close(); return false; } // True if the tag is pre-V3 tag (shorter headers) final int TagVersion = headerbuf[3]; // Check the version if ( TagVersion < 0 || TagVersion > 4 ) { file.close(); return false; } // Get the ID3 tag size and flags; see 3.1 int tagsize = (headerbuf[9] & 0xFF) | ((headerbuf[8] & 0xFF) << 7 ) | ((headerbuf[7] & 0xFF) << 14 ) | ((headerbuf[6] & 0xFF) << 21 ) + 10; boolean uses_synch = (headerbuf[5] & 0x80) != 0 ? true : false; boolean has_extended_hdr = (headerbuf[5] & 0x40) != 0 ? true : false; // Read the extended header length and skip it if ( has_extended_hdr ) { int headersize = file.read() << 21 | file.read() << 14 | file.read() << 7 | file.read(); file.skipBytes( headersize - 4 ); } // Read the whole tag byte[] buffer = new byte[ tagsize ]; file.read( buffer ); file.close(); // Prepare to parse the tag int length = buffer.length; // Recreate the tag if desynchronization is used inside; we need to replace 0xFF 0x00 with 0xFF if ( uses_synch ) { int newpos = 0; byte[] newbuffer = new byte[ tagsize ]; for ( int i = 0; i < buffer.length; i++ ) { if ( i < buffer.length - 1 && (buffer[i] & 0xFF) == 0xFF && buffer[i+1] == 0 ) { newbuffer[newpos++] = (byte) 0xFF; i++; continue; } newbuffer[newpos++] = buffer[i]; } length = newpos; buffer = newbuffer; } // Set some params int pos = 0; final int ID3FrameSize = TagVersion < 3 ? 6 : 10; // Parse the tags while ( true ) { int rembytes = length - pos; // Do we have the frame header? if ( rembytes < ID3FrameSize ) break; // Is there a frame? if ( buffer[pos] < 'A' || buffer[pos] > 'Z' ) break; // Frame name is 3 chars in pre-ID3v3 and 4 chars after String framename; int framesize; if ( TagVersion < 3 ) { framename = new String( buffer, pos, 3 ); framesize = ((buffer[pos+5] & 0xFF) << 8 ) | ((buffer[pos+4] & 0xFF) << 16 ) | ((buffer[pos+3] & 0xFF) << 24 ); } else { framename = new String( buffer, pos, 4 ); framesize = (buffer[pos+7] & 0xFF) | ((buffer[pos+6] & 0xFF) << 8 ) | ((buffer[pos+5] & 0xFF) << 16 ) | ((buffer[pos+4] & 0xFF) << 24 ); } if ( pos + framesize > length ) break; if ( framename.equals( "TPE1" ) || framename.equals( "TPE2" ) || framename.equals( "TPE3" ) || framename.equals( "TPE" ) ) { if ( m_artist == null ) m_artist = parseTextField( buffer, pos + ID3FrameSize, framesize ); } if ( framename.equals( "TIT2" ) || framename.equals( "TIT" ) ) { if ( m_title == null ) m_title = parseTextField( buffer, pos + ID3FrameSize, framesize ); } pos += framesize + ID3FrameSize; continue; } return m_title != null || m_artist != null; } private String parseTextField( final byte[] buffer, int pos, int size ) { if ( size < 2 ) return null; Charset charset; int charcode = buffer[pos]; if ( charcode == 0 ) charset = Charset.forName( "ISO-8859-1" ); else if ( charcode == 3 ) charset = Charset.forName( "UTF-8" ); else charset = Charset.forName( "UTF-16" ); return charset.decode( ByteBuffer.wrap( buffer, pos + 1, size - 1) ).toString(); } }
Great work with reading the tags, can you give any guidance what can be done in this code for editing and writing the tags on mp3 file?
You checked if the version is smaller than 0. There is no version id3.2.0 AND you didn’t parse the 2.1 version.
Wasn’t needed for my purposes.
This code is both instructive and incredibly weird.
Why are you constantly &ing bytes with 0xFF? Isn’t that basically a NOP?
Why are you doing ‘bool blah = conditional ? true : false;’ ? Bool will automatically store your truth value as true or false.
Why are you doing ‘& 0xFF == 0xFF’ on a byte? isn’t that just ‘==0xFF’?
I kind of wonder if stuff like that is like trap streets on a map, where you’re trying to catch people using your code verbatim by putting a bunch of working-but-pointless things in it.
because byte type in Java is signed. Please see https://stackoverflow.com/questions/11380062/what-does-value-0xff-do-in-java
boolean assignment makes it easier to understand for the people who read the code what happens.