«« Empathize with your Blog Audience ... if you want Java Signed Bytes and Decoding Flags »»
blog header image
An Intro to jid3rL

OK I'm ready to talk about my id3v2 library now, which I'm going to name jid3rL. I don't want to jinx it, but everything is working out well so far. The trick, as I guessed in my last post, was to approach it from the write point of view instead of the read. Then things became clearer and cleaner.

If you're unfamiliar with the id3v2 spec, you can read about all of the versions here. It looks intimidating at first but you only need to know about 25% of it. The rest isn't used very much and I'm not sure about supporting all of it in this library.

Here's a simple rundown of the spec. An id3v2 tag is a bunch of bytes at the start of a music file, usually MP3 files. It consists of, in order:

- tag header
- bunch of frames
- footer (only v2.4)
- padding

Frames contain data about the music in the file: metadata. Things like the song name, artist, album name, etc. Each frame is:

- frame header
- frame data

The id3v2 format has gone through 3 iterations of spec: 2, 3 and 4. These are commonly referred to as id3 versions 2.2, 2.3 and 2.4. This is why there is no 2.0 and 2.1.

The only things I've discovered that are different about the three specs are:

- frame headers
- flags in the tag header
- tag extended header (only version 2.3 and 2.4, and they are different)
- tag footer (only version 2.4)

This has interesting implications for a library, since I can easily abstract these details out and use the same objects for:

- tag
- tag header
- frame
- the different types of frame data

... and that's what I did. Here are the objects I have in the library, all in the same package. Constructor signatures listed.

Tag2(byte version)
Tag2Header(byte version, Tag2HeaderFlags flags, int tagLength)
Tag2HeaderFlags(byte version, byte flags)

abstract FrameHeader(String id, int frameLength)
FrameHeaderPoint2(String id, int frameLength)
FrameHeaderPoint3(String id, int frameLength)

abstract Frame(byte version, String id)
TextInformationFrame(byte version, String id, EncodedString information)
CommentsFrame(byte version, String id, TextEncoding encoding, String language, String description, String text)
UniqueFileIdentifierFrame(byte version, String id, String ownerIdentifier, byte[] identifier)
UserDefinedLinkFrame(byte version, String id, EncodedString description, String url)
UnsupportedFrame(byte version, String id, byte[] payload)

abstract Id3v2Segment()

TextEncoding(byte code, String desc)
EncodedString(TextEncoding encoding, String text)
EncodedString(TextEncoding encoding, byte[] buffer)

ByteArrayTools

Colour legend

public class
package visible class
private constructor

All of the classes generate immutable objects. This means they have final member variables initialized in the constructor which cannot be changed and that makes the objects automatically thread-safe with no additional work. Most classes only have only one constructor. Sun Java guru Joshua Bloch explains immutable objects and why you should favour them in his book Effective Java.

Why is there no FrameHeaderPoint4? The frame header spec didn't change from 2.3 to 2.4, so I use FrameHeaderPoint3 for 2.4. Update Actually it did change slightly: 2.4 uses sync safe frame lengths, so I made a FrameHeaderPoint4.

Id3v2Segment is the superclass for every byte-based class in this library. It has an abstract method byte[] bytes(), which every concrete subclass must implement, which is the segment represented in bytes. It also has a method size() which is just the length of the byte[] returned by bytes().

Notice the two constructors for EncodedString. The encoded string is stored as an encoding and a string, but I accept encoded bytes in a constructor as well. In that constructor the string is decoded and also stored as a string. EncodedString then lets you access the string as a decoded string or encoded bytes, based on the encoding. TextEncoding has a private constructor because it is only used to make constants that are available statically.

ByteArrayTools is a static class (which explains the private constructor, see java.lang.Math) with some handy static methods for manipulating arrays of bytes; byte[].

There are some other candidates for constant classes, like how I did TextEncoding. In another language these would be known as enumerations but Java doesn't support them yet so you have to make your own. Java will support enumerations in version 5 (1.5). It's a good way to limit the input range on a variable, and simplifies testing. Those other candidates are the version number and language. There are only three versions numbers (2,3 and 4) and language is a String from a defined standard (ISO-639-2, for example English is "eng").

One of my primary goals is to have a good API. It shouldn't break in later versions -- I can add to it (for example, new supported frame types), just not change method signatures or remove from it. I want to be in that state before I release 1.0.

OK peanut gallery, what do you think of my first run? There are enough software engineers reading this blog that I should have some good peer review. I'll post the full code soon, under the LGPL.

Posted at August 08, 2004 at 08:36 AM EST
Last updated August 08, 2004 at 08:36 AM EST
Comments

Woohoo! Good work Ryan!

» Posted by: roy at August 8, 2004 10:30 AM

Thanks Roy. Not even close to being done yet though. :)

» Posted by: Ryan at August 8, 2004 02:22 PM
Google
 
Search scope: Web ryanlowe.ca