Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
[QUIZ] ID3 Tags (#136)
There are currently too many topics in this group that display first. To make this topic appear first, remove this option from another topic.
There was an error processing your request. Please try again.
flag
  Messages 26 - 36 of 36 - Collapse all  -  Translate all to Translated (View all originals) < Older 
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Joel VanderWerf  
View profile  
 More options Aug 26 2007, 5:05 pm
Newsgroups: comp.lang.ruby
From: Joel VanderWerf <vj...@path.berkeley.edu>
Date: Mon, 27 Aug 2007 06:05:18 +0900
Local: Sun, Aug 26 2007 5:05 pm
Subject: Re: [SOLUTION][QUIZ] ID3 Tags (#136)

Johannes Held wrote:
> What the heck is ARGF?

It's a pseudo-IO that reads the concatenation of the files named in
ARGV, unless ARGV is empty, in which case it just reads standard input.
It's very useful in writing little command-line programs that can be
used as filters or on a list of named files (after you delete any
switches or options from the command line).

[~] cat >foo.txt
foo
[~] cat >bar.txt
bar
[~] ruby -e 'puts ARGF.read' foo.txt bar.txt
foo
bar

[~] echo zap | ruby -e 'puts ARGF.read'
zap

--
       vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Tom Metge  
View profile  
 More options Aug 26 2007, 5:16 pm
Newsgroups: comp.lang.ruby
From: Tom Metge <t...@accident-prone.com>
Date: Mon, 27 Aug 2007 06:16:33 +0900
Local: Sun, Aug 26 2007 5:16 pm
Subject: Re: [SOLUTION] [QUIZ] ID3 Tags (#136)

Hey all, here's another one for you.  I admit that there isn't anything
special about it... I think it's one of the more direct solutions (i.e.
Nothing clever here guys).  I didn't see a reason to include the entire
genre, so it's attached in a separate file.  It simply declares a constant
(an array which is indexed in read_tags).

Tom

--BEGIN SOLUTION--
require 'id3_tag_genre'

class NoTagError < RuntimeError; end

class Mp3
  attr_reader :song, :artist, :album, :year, :comment, :genre, :track

  def initialize(file)
    read_tags(file)
  end

  def read_tags(file)
    begin
      size = File.stat(file).size
      f = File.open(file)
      f.pos = size - 128
      tag = f.read
      raise NoTagError unless tag[0..2] == "TAG"
      @song = tag[3..32].strip
      @artist = tag[33..62].strip
      @album = tag[63..92].strip
      @year = tag[93..96].strip
      @comment = tag[97..126]
        if @comment[28] == 0 && @comment[29] != 0
          @track = @comment[29..29].to_i
          @comment = @comment[0..28].strip
        end
      @genre = Genre[tag[127]]
    rescue NoTagError
      puts "No tags found!"
      return false
    end
    true
  end
end

  id3_tag_genre.rb
1K Download

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Johannes Held  
View profile  
 More options Aug 26 2007, 6:38 pm
Newsgroups: comp.lang.ruby
From: Johannes Held <u...@hehejo.de>
Date: Mon, 27 Aug 2007 00:38:33 +0200
Local: Sun, Aug 26 2007 6:38 pm
Subject: Re: [SOLUTION][QUIZ] ID3 Tags (#136)
Joel VanderWerf schrieb:
> Johannes Held wrote:
>> What the heck is ARGF?>
> It's a pseudo-IO that reads the concatenation of the files named in
> ARGV, unless ARGV is empty, in which case it just reads standard input.
> It's very useful in writing little command-line programs that can be
> used as filters or on a list of named files (after you delete any
> switches or options from the command line).

Thank you.

--
Gruß, Johannes
Täglich http://blog.hehejo.de und du fühlst dich gut.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Discussion subject changed to "ID3 Tags (#136)" by Erik Bryn
Erik Bryn  
View profile  
 More options Aug 26 2007, 7:18 pm
Newsgroups: comp.lang.ruby
From: Erik Bryn <erik.b...@gmail.com>
Date: Sun, 26 Aug 2007 23:18:48 -0000
Local: Sun, Aug 26 2007 7:18 pm
Subject: Re: ID3 Tags (#136)
Here's mine. Takes a directory as input and exports a tab-seperated
list.

- Erik

--

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"]
FIELDS = [:song, :artist, :album, :year, :comment, :genre]

def find_track_number(fields)
  if fields[:comment][-2] == 0 && fields[:comment][-1] != 0
    fields[:track_number] = fields[:comment].slice!(-2..-1)[1]
    fields[:comment].strip!
  end
end

abort "Usage: #{File.basename($PROGRAM_NAME)} <dir>" unless ARGV.size
== 1
Dir["#{ARGV.first}/*.mp3"].each do |path|
  File.open(path, 'rb') do |f|
    f.seek(-128, IO::SEEK_END)
    bytes = f.read
    next if bytes.slice!(0..2) != "TAG"

    tags = Hash[*FIELDS.zip(bytes.unpack('A30A30A30A4A30C')).flatten]
    tags[:genre] = GENRES[tags[:genre]]
    find_track_number(tags)
    puts "#{File.basename(path)}\t#{tags[:artist]}\t#{tags[:song]}
\t#{tags[:album]}\t#{tags[:track_number]}\t#{tags[:year]}
\t#{tags[:genre]}\t#{tags[:comment]}"
  end
end


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Alpha Chen  
View profile  
 More options Aug 27 2007, 12:04 pm
Newsgroups: comp.lang.ruby
From: Alpha Chen <alpha.c...@gmail.com>
Date: Mon, 27 Aug 2007 16:04:45 -0000
Local: Mon, Aug 27 2007 12:04 pm
Subject: Re: ID3 Tags (#136)
My fairly straightforward solution:

class ID3
  genre_list = <<-GENRES
Blues
... # snipped for brevity
Dance Hall
  GENRES

  GENRE_LIST = genre_list.split("\n")
  TAGS = [ :title, :artist, :album, :year, :comment, :track, :genre ]

  attr_accessor *TAGS

  def initialize(filename)
    id3 = File.open(filename) do |mp3|
      mp3.seek(-128, IO::SEEK_END)
      mp3.read
    end

    raise "No ID3 tags" if id3 !~ /^TAG/

    @title, @artist, @album, @year, @comment, @genre =
id3.unpack('xxxA30A30A30A4A30C1')
    @comment, @track = @comment.unpack('Z*@28C1') if @comment =~ /
\0.$/

    @genre = GENRE_LIST[@genre]
  end
end

if __FILE__ == $0
  id3 = ID3.new(ARGV.shift)
  ID3::TAGS.each do |tag|
    puts "#{tag.to_s.capitalize.rjust(8)}: #{id3.send(tag)}"
  end
end


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
John Miller  
View profile  
 More options Aug 27 2007, 12:12 pm
Newsgroups: comp.lang.ruby
From: John Miller <jfmille...@yahoo.com>
Date: Tue, 28 Aug 2007 01:12:42 +0900
Local: Mon, Aug 27 2007 12:12 pm
Subject: Re: ID3 Tags (#136)
Here is my go at things:

__BEGIN__
#Note: this script assumes Ruby 1.8.6 style handeling of strings.  Some
changes
#will need to be made for Ruby 1.9 to work correctly

require 'genre.rb' #an array of the official genera list

def id3(filename)
  id3 =  File.open(filename,'r') do |file|
    file.seek(-128,IO::SEEK_END) #get to the end of the file
    file.read(128)
  end
  return "" unless id3 #protect against read error
  if id3.slice(0,3) == "TAG"
     #Skip the first 3 bytes grab three thirty byte fields
     #and a 4 byte field dropping trailing whitespace.
     #While we can assume the old style comment field and
     #take 30 bytes (we'll com back for the track number later)
     #we must use 'Z' instead of 'A' to avoid having the track
     #show up in our comment field.
     #The last byte is the genre index.
    song,artist,album,year,comment,genre = id3.unpack
"x3A30A30A30A4Z30C"
     #grab the track with a pain slice
    track = id3.slice(-2) if id3.slice(-3) == 0 && id3.slice(-2) != 0
    desc = "#{artist}: #{album}(#{year})\n"
    desc << "  #{song}.  "
    desc << "tr. #{track}" if track
    desc <<"\n"
    desc << "  Comment:  #{comment.chomp(" ")}\n" if comment.length != 0
    desc << "    Genre:  #{Genres[genre]}\n"
    return desc
  end

  return "" #tag not forund

end

#usage id3.rb filename [filename*]
ARGV.each do |filename|
  puts filename
  puts id3(filename) if File.exists? filename
  puts "\n"
end

__END__

I think the only real difference between what I'm seeing on this list
and my own solution is the unpack string.  The 'Comment' filed must use
'Z' and strip trailing white space separately otherwise the track number
could get pulled and stuck on the end of the output.

I like the use of ARGF in other implementations.  Something new to put
in my hat.

John Miller
--
Posted via http://www.ruby-forum.com/.


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Moss  
View profile  
 More options Aug 29 2007, 7:56 pm
Newsgroups: comp.lang.ruby
From: "Matthew Moss" <matthew.moss.co...@gmail.com>
Date: Thu, 30 Aug 2007 08:56:28 +0900
Local: Wed, Aug 29 2007 7:56 pm
Subject: Re: [QUIZ] ID3 Tags (#136)
I've been extremely busy lately, but I wanted to give this one a try.
This solution is not complete as far as the problem specification
goes, but my bit o' metaprogramming-type stuff works, though I'd have
liked to push it further.

class ID3

   @@recLen = 0

   def ID3.field(name, len, flags=[])
      class_eval(%Q[
         def #{name}
            @data[#{@@recLen}, #{len}].strip
         end
      ])

      unless flags.include?(:readonly)
         class_eval(%Q[
            def #{name}=(val)
               # need to pad val to len
               @data[#{@@recLen}, #{len}] = val.ljust(#{len}, "\000")
            end
         ])
      end
      @@recLen += len
   end

   # --------------------------------------------------------------
   #     name, length,       flags
   field :sig,      3,  [:readonly]
   field :song,    30
   field :album,   30
   field :artist,  30
   field :year,     4
   field :comment, 30
   field :genre,    1

   TAG_SIG  = "TAG"
   TAG_SIZE = @@recLen
   raise "ID3 tag size not 128!" unless TAG_SIZE == 128

   # --------------------------------------------------------------

   def ID3.createFromBuffer(buffer)
      ID3.new(buffer)
   end

   def ID3.createFromFile(fname)
      size = File.size?(fname)
      raise "Missing or empty file" unless size
      raise "Invalid file" if size < TAG_SIZE

      # Read the tag and pass to createFromBuffer
      open(fname, "rb") do |f|
         f.seek(-TAG_SIZE, IO::SEEK_END)
         createFromBuffer(f.read(TAG_SIZE))
      end
   end

   # --------------------------------------------------------------

   def initialize(data)
      @data = data

      raise "Wrong buffer size" unless @data.size == TAG_SIZE
      raise "ID3 tag not found" unless self.sig == TAG_SIG
   end

end

id = ID3.createFromFile("maple-leaf-rag.mp3")
puts id.song


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
James Edward Gray II  
View profile  
 More options Aug 29 2007, 10:30 pm
Newsgroups: comp.lang.ruby
From: James Edward Gray II <ja...@grayproductions.net>
Date: Thu, 30 Aug 2007 11:30:36 +0900
Local: Wed, Aug 29 2007 10:30 pm
Subject: Re: [QUIZ] ID3 Tags (#136)
On Aug 29, 2007, at 6:56 PM, Matthew Moss wrote:

> I've been extremely busy lately, but I wanted to give this one a try.
> This solution is not complete as far as the problem specification
> goes, but my bit o' metaprogramming-type stuff works, though I'd have
> liked to push it further.

This is a very clever solution.  I have one suggestion though…

> class ID3

>    @@recLen = 0

>    def ID3.field(name, len, flags=[])

Changing flags=[] to *flags gives a nicer interface, I think.

James Edward Gray II


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Moss  
View profile  
 More options Aug 30 2007, 11:30 am
Newsgroups: comp.lang.ruby
From: "Matthew Moss" <matthew.moss.co...@gmail.com>
Date: Fri, 31 Aug 2007 00:30:50 +0900
Local: Thurs, Aug 30 2007 11:30 am
Subject: Re: [QUIZ] ID3 Tags (#136)
On 8/29/07, James Edward Gray II <ja...@grayproductions.net> wrote:

True... I had thought of that this morning, though I also wanted to
add a conversion parameter... so a lambda or block could be provided
that would convert between the record's string data and an integer
(e.g. the ID3 year).

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Matthew Moss  
View profile  
 More options Aug 30 2007, 11:35 am
Newsgroups: comp.lang.ruby
From: "Matthew Moss" <matthew.moss.co...@gmail.com>
Date: Fri, 31 Aug 2007 00:35:51 +0900
Local: Thurs, Aug 30 2007 11:35 am
Subject: Re: [QUIZ] ID3 Tags (#136)
On 8/30/07, Matthew Moss <matthew.moss.co...@gmail.com> wrote:

And, of course, the whole field/record thingy should be separated out
into its own class/module/whatever.  I did see bit-struct out there,
and considered a solution using that, but it felt weird to be doing
things at a bit-level, so I just kept on with my own.

 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
Ruby Quiz  
View profile  
 More options Aug 30 2007, 1:58 pm
Newsgroups: comp.lang.ruby
From: Ruby Quiz <ja...@grayproductions.net>
Date: Fri, 31 Aug 2007 02:58:44 +0900
Local: Thurs, Aug 30 2007 1:58 pm
Subject: [SUMMARY] ID3 Tags (#136)
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,tra...@comment.unpack "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...


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.
End of messages < Older 
« Back to Discussions « Newer topic     Older topic »