[QUIZ] ID3 Tags (#136)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone
on Ruby Talk follow the discussion. Please reply to the original quiz message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

The MP3 file format, didn't provide any means for including metadata about the
song. ID3 tags were invented to solve this problem.

You can tell if an MP3 file includes ID3 tags by examining the last 128 bytes of
the file. If they begin with the characters TAG, you have found an ID3 tag.
The format of the tag is as follows:

TAG song album artist comment year genre

The spaces above are just for us humans. The actual tags are fixed-width fields
with no spacing between them. Song, album, artist, and comment are 30 bytes
each. The year is four bytes and the genre just gets one, which is an index
into a list of predefined genres I'll include at the end of this quiz.

A minor change was later made to ID3 tags to allow them to include track
numbers, creating ID3v1.1. In that format, if the 29th byte of a comment is
null and the 30th is not, the 30th byte is an integer representing the track
number.

Later changes evolved ID3v2 which is a scary beast we won't worry about.

This week's Ruby Quiz is to write an ID3 tag parser. Using a library is
cheating. Roll up your sleeves and parse it yourself. It's not hard at all.

If you don't have MP3 files to test your solution on, you can find some free
files at:

http://www.mfiles.co.uk/mp3-files.htm

Here's the official genre list with some extensions added by Winamp:

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 Bass
Primus
Porn Groove
Satire
Slow Jam
Club
Tango
Samba
Folklore
Ballad
Power Ballad
Rhythmic Soul
Freestyle
Duet
Punk Rock
Drum Solo
A capella
Euro-House
Dance Hall
 
R

Robert Dober

The three rules of Ruby Quiz:
The spaces above are just for us humans. The actual tags are fixed-width fields
with no spacing between them. Song, album, artist, and comment are 30 bytes
each. The year is four bytes and the genre just gets one, which is an index
into a list of predefined genres I'll include at the end of this quiz.
zero based, I guess?
<snip>

Cheers
Robert
 
J

James Edward Gray II

I think that the fields order is wrong.
I found this:
TAG song artist album year comment genre

You are right. Sorry about that. I've fixed it on the Ruby Quiz site.

James Edward Gray II=
 
J

John Miller

James said:
The format of the tag is as follows:

I assume that the song album artist and comment fields are NUL padded?

The 4 bytes of Year are 4 character and not a 32bit number?

John Miller
 
J

James Edward Gray II

I assume that the song album artist and comment fields are NUL padded?

The 4 bytes of Year are 4 character and not a 32bit number?

Yes and yes. :)

James Edward Gray II
 
B

Brad Ediger

You are right. Sorry about that. I've fixed it on the Ruby Quiz =20
site.

You fixed one problem, but artist and album are still flipped.

(this didn't come through the first time, trying without the S/MIME =20
signature)
 
J

James Edward Gray II

You fixed one problem, but artist and album are still flipped.

Egad. I must have had a massive dyslexia attack when I wrote that quiz.

It should be fixed now.

James Edward Gray II=
 
J

Jesse Merriman

--Boundary-00=_kPY0G1htRWIYx/V
Content-Type: Multipart/Mixed;
boundary="Boundary-00=_kPY0G1htRWIYx/V"

--Boundary-00=_kPY0G1htRWIYx/V
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Here's my solution. Should be pretty straightforward.
id3_tags.rb takes a list of filenames as arguments:

$ ./id3_tags.rb 04_Prepare_Yourself.mp3 05_Moonloop.mp3
04_Prepare_Yourself.mp3:
song: Prepare Yourself
track: 4
artist: Porcupine Tree
comment: some comment
year: 1995
album: The Sky Moves Sideways
genre: Progressive Rock

05_Moonloop.mp3:
song: Moonloop
track: 5
artist: Porcupine Tree
comment: test comment
year: 1995
album: The Sky Moves Sideways
genre: Progressive Rock


--
Jesse Merriman
(e-mail address removed)
http://www.jessemerriman.com/

--Boundary-00=_kPY0G1htRWIYx/V
Content-Type: application/x-ruby;
name="hashy.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="hashy.rb"

#!/usr/bin/env ruby
# Ruby Quiz 136: ID3 Tags

class Hash
def map_keys!
# This is kind of heavy, but we can't just iterate through like map_vals!
# because earlier keys might map to later keys, and we'd lose stuff.
# Consider {1=>:a, 2=>:b}.map_keys! { |k| k+1 }
pairs = to_a.map { |pair| [yield(pair.first), pair.last] }
clear
pairs.each { |pair| self[pair.first] = pair.last }
self
end

def map_vals!
each { |k, v| self[k] = yield(v) }
self
end
end

--Boundary-00=_kPY0G1htRWIYx/V
Content-Type: application/x-ruby;
name="genres.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="genres.rb"

#!/usr/bin/env ruby
# Ruby Quiz 136: ID3 Tags

require 'hashy'

# Genre codes, taken from `mp3info -G`
# The only edits were adding backslashes before spaces within genre names.
Genres = Hash[*%w{
123 A\ Cappella 25 Euro-Techno 13 Pop
74 Acid\ Jazz 54 Eurodance 109 Porn\ Groove
73 Acid\ Punk 84 Fast-Fusion 117 Power\ Ballad
34 Acid 81 Folk/Rock 23 Pranks
99 Acoustic 115 Folklore 108 Primus
40 Alt.\ Rock 80 Folk 92 Progressive\ Rock
20 Alternative 119 Freestyle 93 Psychedelic\ Rock
26 Ambient 5 Funk 67 Psychedelic
145 Anime 30 Fusion 121 Punk\ Rock
90 Avantgarde 36 Game 43 Punk
116 Ballad 59 Gangsta\ Rap 14 R&B
41 Bass 126 Goa 15 Rap
135 Beat 38 Gospel 68 Rave
85 Bebob 91 Gothic\ Rock 16 Reggae
96 Big\ Band 49 Gothic 76 Retro
138 Black\ Metal 6 Grunge 87 Revival
89 Bluegrass 79 Hard\ Rock 118 Rhythmic\ Soul
0 Blues 129 Hardcore 78 Rock\ &\ Roll
107 Booty\ Bass 137 Heavy\ Metal 17 Rock
132 BritPop 7 Hip-Hop 143 Salsa
65 Cabaret 35 House 114 Samba
88 Celtic 100 Humour 110 Satire
104 Chamber\ Music 131 Indie 69 Showtunes
102 Chanson 19 Industrial 21 Ska
97 Chorus 46 Instrumental\ Pop 111 Slow\ Jam
136 Christian\ Gangsta\ Rap 47 Instrumental\ Rock 95 Slow\ Rock
61 Christian\ Rap 33 Instrumental 105 Sonata
141 Christian\ Rock 146 JPop 42 Soul
1 Classic\ Rock 29 Jazz+Funk 37 Sound\ Clip
32 Classical 8 Jazz 24 Soundtrack
128 Club-House 63 Jungle 56 Southern\ Rock
112 Club 86 Latin 44 Space
57 Comedy 71 Lo-Fi 101 Speech
140 Contemporary\ Christian 45 Meditative 83 Swing
2 Country 142 Merengue 94 Symphonic\ Rock
139 Crossover 9 Metal 106 Symphony
58 Cult 77 Musical 147 Synthpop
125 Dance\ Hall 82 National\ Folk 113 Tango
3 Dance 64 Native\ American 51 Techno-Industrial
50 Darkwave 133 Negerpunk 18 Techno
22 Death\ Metal 10 New\ Age 130 Terror
4 Disco 66 New\ Wave 144 Thrash\ Metal
55 Dream 39 Noise 60 Top\ 40
127 Drum\ &\ Bass 11 Oldies 70 Trailer
122 Drum\ Solo 103 Opera 31 Trance
120 Duet 12 Other 72 Tribal
98 Easy\ Listening 75 Polka 27 Trip-Hop
52 Electronic 134 Polsk\ Punk 28 Vocal
48 Ethnic 53 Pop-Folk
124 Euro-House 62 Pop/Funk
}].map_keys! { |k| k.to_i }

--Boundary-00=_kPY0G1htRWIYx/V
Content-Type: application/x-ruby;
name="id3_tags.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="id3_tags.rb"

#!/usr/bin/env ruby
# Ruby Quiz 136: ID3 Tags

require 'genres'
require 'hashy'

# TAG song artist album year comment genre
Tag = /^TAG(.{30})(.{30})(.{30})(.{4})(.{30})(.)$/
Null = "\000"

# Retrieve the last n bytes of a file.
def get_last_bytes n, filename
File.open(filename) do |f|
f.seek -n, IO::SEEK_END
f.read
end
end

# Determine whether the last bytes of a file are a valid ID3 tag.
def valid? str; str[0..2] == 'TAG'; end

# Parse the last bytes of a file into a Hash containing the tags.
def parse str
if m = Tag.match(str)
fields = { :song => m[1], :artist => m[2], :album => m[3],
:year => m[4], :comment => m[5], :genre => Genres[m[6][0]] }

com = fields[:comment]
if com[28..28] == Null and com[29..29] != Null
fields[:track] = com[29..29][0]
fields[:comment] = com[0..27]
end

fields.map_vals! { |v| (v.is_a?(String) ? v.strip : v) }
else
nil
end
end

if $0 == __FILE__
ARGV.each do |file|
if File.exists? file and File.readable? file
puts "#{file}:"
bytes = get_last_bytes 128, file

if valid? bytes
parse(bytes).each { |field, val| puts " #{field}: #{val}" }
else
puts 'No ID3 tag found.'
end
puts
else
$stderr.puts "Can't read #{file}"
end
end
end

--Boundary-00=_kPY0G1htRWIYx/V--
--Boundary-00=_kPY0G1htRWIYx/V--
 
K

Ken Bloom

class NoID3Error < StandardError
end

class ID3
Genres=" 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 Bass
Primus
Porn Groove
Satire
Slow Jam
Club
Tango
Samba
Folklore
Ballad
Power Ballad
Rhythmic Soul
Freestyle
Duet
Punk Rock
Drum Solo
A capella
Euro-House
Dance Hall".split("\n").map{|x| x.gsub(/^\s+/,'')}

attr_accessor :title, :artist, :album, :year, :comment, :genre, :track
def genre_name
Genres[@genre]
end

def initialize(filename)
rawdata=open(filename) do |f|
f.seek(f.lstat.size-128)
f.read
end
tag,@title,@artist,@album,@year,@comment,@genre=rawdata.unpack "A3A30A30A30A4A30c"
if rawdata[3+30+30+30+4+28]==0
@track=rawdata[3+30+30+30+4+29]
@track=nil if @track==0
end
if tag!="TAG"
raise NoID3Error
end
end
end
 
J

Jesse Merriman

--Boundary-00=_wXZ0GzHHsrFl7ks
Content-Type: Multipart/Mixed;
boundary="Boundary-00=_wXZ0GzHHsrFl7ks"

--Boundary-00=_wXZ0GzHHsrFl7ks
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

Here's my solution. Should be pretty straightforward.

Here's a very slightly improved version of id3_tags.rb (which still requires
the other two files I submitted, unchanged). The only change is less ugly
use of String#[], and no more Null constant.


--
Jesse Merriman
(e-mail address removed)
http://www.jessemerriman.com/

--Boundary-00=_wXZ0GzHHsrFl7ks
Content-Type: application/x-ruby;
name="id3_tags.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="id3_tags.rb"

#!/usr/bin/env ruby
# Ruby Quiz 136: ID3 Tags

require 'genres'
require 'hashy'

# TAG song artist album year comment genre
Tag = /^TAG(.{30})(.{30})(.{30})(.{4})(.{30})(.)$/

# Retrieve the last n bytes of a file.
def get_last_bytes n, filename
File.open(filename) do |f|
f.seek -n, IO::SEEK_END
f.read
end
end

# Determine whether the last bytes of a file are a valid ID3 tag.
def valid? str; str[0..2] == 'TAG'; end

# Parse the last bytes of a file into a Hash containing the tags.
def parse str
if m = Tag.match(str)
fields = { :song => m[1], :artist => m[2], :album => m[3],
:year => m[4], :comment => m[5], :genre => Genres[m[6][0]] }

com = fields[:comment]
if com[28].zero? and not com[29].zero?
fields[:track] = com[29]
fields[:comment] = com[0..27]
end

fields.map_vals! { |v| (v.is_a?(String) ? v.strip : v) }
else
nil
end
end

if $0 == __FILE__
ARGV.each do |file|
if File.exists? file and File.readable? file
puts "#{file}:"
bytes = get_last_bytes 128, file

if valid? bytes
parse(bytes).each { |field, val| puts " #{field}: #{val}" }
else
puts 'No ID3 tag found.'
end
puts
else
$stderr.puts "Can't read #{file}"
end
end
end

--Boundary-00=_wXZ0GzHHsrFl7ks--
--Boundary-00=_wXZ0GzHHsrFl7ks--
 
C

come

Hi,

Here is my solution :

require "delegate"

class ID3Tags < DelegateClass(Struct)
MP3_TYPE=%w(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 Bass Primus Porn Groove Satire Slow Jam Club Tango
Samba Folklore Ballad Power Ballad Rhythmic Soul Freestyle Duet Punk
Rock Drum Solo A capella Euro-House Dance Hall)

Tag=Struct.new:)song,:album,:artist,:year,:comment,:track,:genre)

def initialize(file)
raise "No ID3 Tag detected" unless File.size(file) > 128
File.open(file,"r") do |f|
f.seek(-128, IO::SEEK_END)
tag = f.read.unpack('A3A30A30A30A4A30C1')
raise "No ID3 Tag detected" unless tag[0] == 'TAG'
if tag[5][-2] == 0 and tag[5][-1] != 0
tag[5]=tag[5].unpack('A28A1C1').values_at(0,2)
else
tag[5]=[tag[5],nil]
end
super(@tag=Tag.new(*tag.flatten[1..-1]))
end
end

def to_s
members.each do |name|
puts "#{name} : #{send(name)}"
end
end

def genre
MP3_TYPE[@tag.genre]
end

end

Come
 
B

Brad Ediger

One of the biggest problems in software development is feature creep.
In the case of this Quiz, specification creep was the culprit, with
the spec being changed two times in two days. No offense intended,
JEG2 ;-)

Luckily, we can use the mighty power of Ruby to make our application
impervious to such changes, and save a couple heredocs to boot.

-------------------------

#!/usr/bin/env ruby -rubygems

%w(hpricot open-uri).each(&method:)require))

fields, genres = (Hpricot(open("http://www.rubyquiz.com/
quiz136.html")) / "p.example").map{|e| e.inner_html}
fields = fields.split
genres = genres.split "<br />"

values = IO.read(ARGV.first)[-128..-1].unpack("A3 A30 A30 A30 A4 A30 A")

unless values.first == 'TAG'
puts "No ID3 tag found"
exit 1
end

fields.zip(values).each do |field, value|
case field # this feels dirty
when 'TAG': # nada
when 'genre': puts "#{field}: #{genres[value[0]]}"
when 'comment'
puts "#{field}: #{value}"
if value[28].to_i.zero? && !value[29].to_i.zero? # ID3v1.1
puts "track: #{value[29]}"
end
else puts "#{field}: #{value}"
end
end
 
B

Brad Ediger

Hi,

Here is my solution :

require "delegate"

class ID3Tags < DelegateClass(Struct)
MP3_TYPE=%w(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 Bass Primus Porn Groove Satire Slow Jam Club Tango
Samba Folklore Ballad Power Ballad Rhythmic Soul Freestyle Duet Punk
Rock Drum Solo A capella Euro-House Dance Hall)

That's not going to work like you think it will:
=> ["New", "Age"]
 
J

James Edward Gray II

Here's my solution.

Here's my own:

#!/usr/bin/env ruby -w

GENRES = %w[ 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\ Bass Primus Porn\ Groove Satire
Slow\ Jam
Club Tango Samba Folklore Ballad Power\ Ballad Rhythmic
\ Soul
Freestyle Duet Punk\ Rock Drum\ Solo A\ capella Euro-House
Dance\ Hall ]

abort "Usage: #{File.basename($PROGRAM_NAME)} MP3_FILE" unless
ARGV.size == 1

tag, song, artist, album, year, comment, genre =
ARGF.read[-128..-1].unpack("A3A30A30A30A4A30C")
if comment.size == 30 and comment[28] == ?\0
track = comment[29]
comment = comment[0..27].strip
else
track = nil
end

abort "ID3v1 tag not found." unless tag == "TAG"

puts "Song: #{song}"
puts "Artist: #{artist}"
puts "Album: #{album}"
puts "Comment: #{comment}" unless comment.empty?
puts "Track: #{track}" unless track.nil?
puts "Year: #{year}"
puts "Genre: #{GENRES[genre] || 'Unknown'}"

__END__

James Edward Gray II
 
J

James Edward Gray II

One of the biggest problems in software development is feature
creep. In the case of this Quiz, specification creep was the
culprit, with the spec being changed two times in two days. No
offense intended, JEG2 ;-)

I just did that to inspire you to such a clever solution. ;)

James Edward Gray II
 
C

come

My corrected version :


require "delegate"

class ID3Tags < DelegateClass(Struct)
MP3_TYPE=["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 Bass","Primus","Porn
Groove","Satire","Slow
Jam","Club","Tango","Samba","Folklore","Ballad","Power
Ballad","Rhythmic Soul","Freestyle","Duet","Punk Rock","Drum Solo","A
capella","Euro-House","Dance Hall"]

Tag=Struct.new:)song,:album,:artist,:year,:comment,:track,:genre)

def initialize(file)
raise "No ID3 Tag detected" unless File.size(file) > 128
File.open(file,"r") do |f|
f.seek(-128, IO::SEEK_END)
tag = f.read.unpack('A3A30A30A30A4A30C1')
raise "No ID3 Tag detected" unless tag[0] == 'TAG'
if tag[5][-2] == 0 and tag[5][-1] != 0
tag[5]=tag[5].unpack('A28A1C1').values_at(0,2)
else
tag[5]=[tag[5],nil]
end
super(@tag=Tag.new(*tag.flatten[1..-1]))
end
end

def to_s
members.each do |name|
puts "#{name} : #{send(name)}"
end
end

def genre
MP3_TYPE[@tag.genre]
end

end
 

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

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top