Search

July 29, 2012

Accessing MP3 Tags in Ruby

I wanted to explore Ruby a little more so I needed to give myself a project. I figured I would try to create a script to copy a playlist to my MP3 player, since both Rhythmbox and Banshee had issues with this.

The first thing is that I would need to get the ID3 tag information from the mp3 files so I could copy them to the right filename on the mp3 player. (Both Rhythmbox and Banshee use Artist - Album - Track - Title.mp3 so I could that to check for duplicates.) So I needed to be able to read mp3 tags.

I started at the ID3 format web site, http://www.id3.org/. They had a helpful section on implementations (http://www.id3.org/Implementations) so I started there.

The first suggestion, id3lib-ruby was no longer under development. The site did suggest taglib-ruby but I decided to move to the next one.

The next one was ruby-mp3info. I rejected that one on the basis that the installation used Ruby’s gem utility. (That later turned out to be a mistake.)

The final one was id3.rb and I did download the gem. But the documentation was a little thin and it felt too much like a side project for a university student.

I decided to fire up synaptic and see if there was a package. Lo and behold, there was one for ruby-taglib. I installed it and tried the following simple script:

require 'taglib'

ARGV.each do |f|
  TagLib::FileRef.open(f) do |fileref|
    tag = fileref.tag
    puts "#{f}: #{tag.title} by #{tag.artist}; track #{tag.track} on #{tag.album}"
  end
end

Unfortunately the result was: NameError: uninitialized constant TagLib::FileRef. I puzzled over this for a while and examined the documentation and probed the Ruby object to no avail. Eventually I decided to try installing via the gem method. No luck that way, same result.

Finally it occurred to me that the two installations might be conflicting. I uninstalled both and re-installed via gem install taglib-ruby. Now my script worked.

July 21, 2012

Ruby, Wrapping Up

My notes on the final section on Ruby from Seven Languages in Seven Days by Bruce Tate.

Ruby certainly seems to be a tight, fun language. Although the lack of an IDE is a negative for me, especially since I don’t have encyclopedic knowledge of the libraries. But it would be worth it to learn the basics to have a nice scripting language.

I was curious as to how a professional Ruby project might look. How namespaces are handled, how the directory structure goes, etc.

I was surprised we weren’t spending more time with Ruby. I guess I didn’t realize that we were getting four days off every week.

July 19, 2012

Ruby, Day 3

My notes on Day 3 of Ruby from Seven Languages in Seven Weeks by Bruce Tate follow.

My knee-jerk reaction to the power of Ruby modules to change the behavior of classes at run-time is probably the typical response of programmers of my generation: self-modifying code is bad. Now that I’ve got that out of my system, let’s be open-minded and consider what we have.

Ruby modules remind me a lot of aspect-oriented programming in the sense that you can add behavior to a class (or not) at runtime. I don’t see a way right now to do truly aspect-oriented programming with it (i.e. wrapping methods with logging or transaction boundaries) but then I’m only on my third day with Ruby, I imagine someone clever has already worked it out.

Pondering the idea a little more, it seems that if, for example, method renaming was viable then one could write a module that renames all (or a targeted subset) of the existing methods then use method_missing to provide the desired aspect. That might get funky when trying to apply multiple aspects but that could likely be worked out.

I can see the appeal of creating simple data holders at runtime to correspond to tables. We actually considered doing something like that in Java many moons ago (before Hibernate) using a custom ClassLoader but ultimately decided in was too complicated for the relative benefit. Had we been using Ruby we definitely would have done it.

Only one exercise this time and it is not too bad. I did get tripped up briefly not realizing that the argument to method_missing is a Symbol and not a String. I also briefly thought I would need to create an Enumerator but quickly realized code blocks would come to the rescue. Here’s the pieces I added to Tate’s acts_as_csv_module.rb:

module ActsAsCsv

# snip...

  module InstanceMethods

    def read
      # snip...

      file.each do |row|
        @csv_contents << CsvRow.new(headers, row.chomp.split(', '))
      end
    end

    def each(&block)
      csv_contents.each(&block)                                     # <1>
    end

    # snip...

  end

end

class CsvRow

  def initialize(headers,data)
    @headers = headers
    @data = data
  end

  def method_missing(name)
    index = @headers.index(name.to_s)                               # <2>
    return @data[index] if index
  end
end
  1. Code blocks to the rescue.
  2. Don’t forget to use name.to_s

Nice and compact but powerful.

July 16, 2012

Deleting old files on Windows

I ran into a situation today where I wanted to script deletion of folders older than a set number days on an old Windows 2000 machine. (The culprit is a commercial SMTP spam and virus filter that does not clean up after itself when it updates. Eventually the drive gets full and no mail comes through.) I found a solution using forfiles but this version of Windows does not have it. I found myself searching the web and gnashing my teeth over the limitations of Windows batch scripting.

That is when I remembered that Windows batch processing can be quite robust if you use the right tools. Namely, you can write JScript (i.e. JavaScript) and Visual Basic scripts for Windows that utilize ActiveX to make use the of whole Windows API. A little googling later and I had the following script:

if (WScript.arguments.length != 2)
{
    WScript.StdErr.write("Invalid arguments\n");
    WScript.StdErr.write(WScript.ScriptName + " <dir> <no. of days>\n");
    WScript.Quit();
}
var dirname = WScript.arguments.item(0);
var days = parseInt(WScript.arguments.item(1));
var cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
WScript.StdOut.write("Deleting files older than: " + cutoff + "\n");

var fso = new ActiveXObject("Scripting.FileSystemObject");
var root = fso.getFolder(dirname);
var subdirs = new Enumerator(root.SubFolders);
for (; !subdirs.atEnd(); subdirs.moveNext())
{
    var candidate = subdirs.item();
    var lastMod = candidate.DateLastModified;
    if (lastMod < cutoff)
    {
        WScript.StdOut.write("Deleting " + candidate + "\n");
        candidate.Delete(true);
    }
}

As a nice bonus, the Folder.Delete method works recursively, even on non-empty directories. You invoke the script using the Windows Script Host, cscript.exe:

C:\>cscript //Nologo delete-folders.js C:\dir\to\clean\up 2
Deleting files older than: Sat Jul 14 14:36:56 EDT 2012
Deleting C:\dir\to\clean\up\1342451674
Deleting C:\dir\to\clean\up\1342458973
Deleting C:\dir\to\clean\up\1342463875
C:\>

Some breadcrumbs if you are looking for the documentation:

July 15, 2012

Ruby, Day 2

My notes on day 2 of Ruby from Seven Languages in Seven Weeks by Bruce Tate.

Most of the section focuses elsewhere, but my curiosity was piqued by the brief note about functions at the beginning. It looks like Ruby has the equivalent of C's function pointers. Also, like everything else functions are first class objects in Ruby with methods, etc.

The convention with code blocks is too use {} for one-liner and do-end otherwise. There's no do-end example, let's try to make one:

irb(main):006:0> 3.times => #<Enumerator: 3:times> irb(main):007:0> 3.times do irb(main):008:1* puts 'tastes great' irb(main):009:1> puts 'less filling' irb(main):010:1> end tastes great less filling tastes great less filling tastes great less filling => 3

In case you haven't realized, I'm showing how the sausage gets made by leaving in my mistaken guesses.

The yield feature looks pretty interesting, let's try to do something with it.  I ran into some issues by putting an extraneous do after my whiles but there's the end result:

irb(main):103:0> class Fixnum irb(main):104:1>   def calculate irb(main):105:2>     n = 0 irb(main):106:2>     while n < self irb(main):107:3>       n = n + 1 irb(main):108:3>       yield n irb(main):109:3>     end irb(main):110:2>   end irb(main):111:1> end => nil irb(main):112:0> x = 0 => 0 irb(main):113:0> 3.calculate {|n| x = x + n } => nil irb(main):114:0> x => 6 irb(main):115:0> x = 0 => 0 irb(main):116:0> 1000.calculate {|n| x = x + n } => nil irb(main):117:0> x => 500500 irb(main):118:0> x = 1 => 1 irb(main):119:0> 10.calculate {|n| x = x * n } => nil irb(main):120:0> x => 3628800

Figure 2.1 could use a little work, it is not exactly kosher UML.  But if you are confused, the lefts leading left are the "is a" relationship, i.e. Numeric is a Class and the arrows leading up are the "extends" relationship, i.e. Fixnum extends Integer.  Which begs the question, what about floating point numbers?

irb(main):121:0> 3.14149.superclass NoMethodError: undefined method `superclass' for 3.14149:Float from (irb):121 from /usr/bin/irb:12:in `<main>' irb(main):122:0> 3.14149.class => Float irb(main):123:0> 3.14149.class.superclass => Numeric

Of course upon further reading I see that there is a simple way to do what I tried to do with the calculate  example above using inject.  I suppose that's the point of Ruby, there's always an elegant way.

Time to take on the exercises.  First, accessing files with and without code blocks.  Using File.open with a code block has the clear advantage of closing the file when the block completes.  An elegant solution for Ruby again.

Given that the Hash class has a to_a method for converting it to an array, that might be the way to go.  There is also Hash.keys and Hash.values.  In the other direction, I don't see an obvious method on Array so how about:

irb(main):124:0> a = ['b','e','m','u','s'] => ["b", "e", "m", "u", "s"] irb(main):125:0> h = {} => {} irb(main):126:0> a.each_index {|i| h[i] = a[i]} => ["b", "e", "m", "u", "s"] irb(main):127:0> h => {0=>"b", 1=>"e", 2=>"m", 3=>"u", 4=>"s"}

Doing 4 at a time using each_slice is doable:

irb(main):141:0> a.each_slice(4) do |x| irb(main):142:1*   x.each {|y| print y, ' '} irb(main):143:1>   puts irb(main):144:1> end 0 1 2 3  4 5 6 7  8 9 10 11  12 13 14 15 

But I'm not sure I see how to do it with just Array.each.  With Array.each_index it is not hard:

irb(main):149:0> a.each_index do |i| irb(main):150:1*   print a[i], ' ' irb(main):151:1>   puts if (i + 1) % 4 == 0 irb(main):152:1> end 0 1 2 3  4 5 6 7  8 9 10 11  12 13 14 15 

I suppose I could do it if I kept track of the index manually but then why am I using each?

irb(main):003:0> i = 0 => 0 irb(main):004:0> a.each do |x| irb(main):005:1* print x, ' ' irb(main):006:1> puts if (i + 1) % 4 == 0 irb(main):007:1> i += 1 irb(main):008:1> end 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

In any case, here's my modified Tree initializer, this was pretty straightforward:

  def initialize(name, nodes)     @node_name = name     @children = []     nodes.each {|key,value| @children.push(Tree.new(key, value))}   end

A simple grep clone wasn't too bad either, although I wouldn't call it robust in its current form:

#!/usr/bin/env ruby re = Regexp.new(ARGV[0]) ARGV.slice(1,ARGV.length).each do |filename| File.open(filename) do |file| line_no = 1 file.readlines.each do |line| puts "#{filename}(#{line_no}):#{line}" if re.match(line) line_no += 1 end end end

July 14, 2012

Ruby, Day 1


Well I have survived day 1 of Ruby in Seven Languages in Seven Days by Bruce Tate. Now I just need to figure out how to do everything I want to do in this blog software.

Finding the Ruby documentation was an easy Google search:

http://www.ruby-doc.org

And the free online version of Programming Ruby: The Pragmatic Programmer's Guide:

http://www.ruby-doc.org/docs/ProgrammingRuby/

I am running Ubuntu so I already have ruby installed. Then the question became what version of ruby do I have installed? The answer should have been obvious:

ray@stingray:~/tmp$ ruby -v ruby 1.9.3p0 (2011-10-30 revision 33570) [i686-linux]

Let's try to find a substitution method the hard way (i.e. without using the documentation):

irb(main):001:0> 'a string'.methods => [:<=>, :==, :===, :eql?, :hash, :casecmp, :+, :*, :%, :[], :[]=, :insert, :length, :size, :bytesize, :empty?, :=~, :match, :succ, :succ!, :next, :next!, :upto, :index, :rindex, :replace, :clear, :chr, :getbyte, :setbyte, :byteslice, :to_i, :to_f, :to_s, :to_str, :inspect, :dump, :upcase, :downcase, :capitalize, :swapcase, :upcase!, :downcase!, :capitalize!, :swapcase!, :hex, :oct, :split, :lines, :bytes, :chars, :codepoints, :reverse, :reverse!, :concat, :<<, :prepend, :crypt, :intern, :to_sym, :ord, :include?, :start_with?, :end_with?, :scan, :ljust, :rjust, :center, :sub, :gsub, :chop, :chomp, :strip, :lstrip, :rstrip, :sub!, :gsub!, :chop!, :chomp!, :strip!, :lstrip!, :rstrip!, :tr, :tr_s, :delete, :squeeze, :count, :tr!, :tr_s!, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :each_codepoint, :sum, :slice, :slice!, :partition, :rpartition, :encoding, :force_encoding, :valid_encoding?, :ascii_only?, :unpack, :encode, :encode!, :to_r, :to_c, :>, :>=, :<, :<=, :between?, :nil?, :!~, :class, :singleton_class, :clone, :dup, :initialize_dup, :initialize_clone, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :respond_to_missing?, :extend, :display, :method, :public_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__] irb(main):002:0> 'a string'.sub ArgumentError: wrong number of arguments (0 for 1..2) from (irb):2:in `sub' from (irb):2 from /usr/bin/irb:12:in `<main>' irb(main):003:0> 'a string'.sub('a') ArgumentError: wrong number of arguments (1 for 1..2) from (irb):3:in `sub' from (irb):3 from /usr/bin/irb:12:in `<main>' irb(main):004:0> 'a string'.sub('a', 'b') => "b string" irb(main):005:0>

OK, I'm a little confused by the arguments message (1 for 1..2) but it worked out.

The next interesting exercise is print your name ten times. I'm going to cheat and use the sneak peek at some Ruby code in section 2.1. Once I got the range syntax down it was no problem:

irb(main):011:0> (1..10).each {|x| puts 'your name'}

Running a ruby program from a file is easier in linux than Windows. (This probably works on a Mac as well.) Put the following at the beginning of the file:

#!/usr/bin/env ruby

Note: env is a nice little program that allows you not to need to know where ruby is installed on the particular machine. Although you do need to get the path to env right so maybe it doesn't buy you too much.

Then make the file executable with chmod a+x and you can run it right from the command line:

chmod a+x sentence.rb ./sentence.rb

OK, on to day two...

Welcome

I created this blog for two purposes.  First, to capture any random notes on problems I have encountered (and hopefully solved).  Hopefully next time it happens, I'll remember the blog post or Goggle will remember it for me.

Second, to capture some thoughts while working through the book Seven Languages in Seven Weeks by Bruce Tate.  I figure that if I am actually going to get anything out of reading the book, I will need to leave some notes behind.

I don't expect to keep the pace set by the author, but I will try to get to it once or twice a week.