[SUMMARY] ID3 Tags (#136)

R

Ruby Quiz

This quiz was another idea I got out of the Erlang book. The author uses a
similar example to show how smooth processing binary data in Erlang can be. I'm
happy to say that I found the submitted Ruby solutions to be equally smooth, if
not more so.

The secret to binary parsing in Ruby is generally the String.unpack() method and
the majority of the solutions capitalized on this technique. Technically, ID3
tags are mainly in plain text, with some null characters thrown in. Still, I
think it's a good idea to get into the unpack() mindset anytime you start
slicing up binary data.

I want to take a look at Eugene Kalenkovich's code below. It's a pretty typical
usage of unpack() to parse some data. It also includes a nicety when reading
the file that I'm ashamed to admit I didn't think of. Let's start with that:

def fileTail (file, offset)
f=File.new(file)
f.seek(-offset,IO::SEEK_END)
f.read
end

# ...

In my own code, I read the whole file into memory and indexed out the last 128
bytes. That's almost always the wrong approach and Eugene shows the correct
strategy above. This code just opens the file, seek()s to offset bytes before
the end, and read()s the needed data. That scales much better when the data
sizes are significant.

As a quick aside, file_tail() would probably be a more Rubyish method name.

The code now builds a data structure class to hold the tag details. It starts
like this:

# ...

class ID3Tag
GENRES=["Blues","Classic Rock","Country",…,"Dance Hall"]
attr_reader :title, :artist, :album, :year, :comment, :genre, :track

# ...

You can see that this class is mainly just a data structure that defines readers
for all of the elements in a tag. I've trimmed the GENRES listing here, but the
code included the full set.

I will say that some found more clever means to load the GENRES Array. Several
people did fancy heredoc manipulations, but the most clever pulled the list out
of the quiz document using open-uri and hpricot. That was especially wise this
time since I made so many mistakes in the quiz description.

We're now ready for the actual parsing code:

# ...

def initialize fname
tag,@title,@artist,@album,@year,@comment,@genre=
fileTail(fname,128).unpack "A3A30A30A30A4A30C"
raise "No ID3 Info" if tag!='TAG'
s_com,flag,[email protected] "A28CC"
if flag==0 and track!=0
@comment=s_com
@track=track
end
@genre=GENRES[@genre]
@genre="Unknown" if !@genre
end
end

# ...

As you can see, the majority of the work is done on the first line with a single
call to unpack(). The template fed to unpack() is the key to the whole puzzle.
An "A" in the unpack() template instructs it to extract a String, removing any
trailing spaces or null characters. By default the String is just one character
long, but you can provide a number after the "A" to increase that count. The
only other character used in the template is a "C" which is used to extract one
character as an Integer. The unpack() call returns an Array which Eugene just
mass-assigns to the relevant variables.

The rest is simple. The code checks the first chunk for the identifying "TAG"
String and throws an error if it's not there. Then another call to unpack(),
with a template much like the first, pulls the track field out of the comment.
The if statement makes sure that assignment only happens when it is present.
The final two lines are just a longhand form of:

@genre = GENRES[@genre] || "Unknown"

With all of the fields stored away in the proper variables, reader calls can be
used to extract as needed. Eugene's actual application code just punted on that
point though:

# ...

p ID3Tag.new(ARGV[0])

My thanks to all who have helped me with my Erlang comparisons these last two
weeks. I promise, we're on to new topics now.

In fact, tomorrow we will tackle an interesting subproblem from this year's ICFP
contest...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top