I finally got back to my little Ruby project over the weekend. The idea was to write a tool to copy an m3u play list and associated files to my mp3 player since Rhythmbox and Banshee were not up to the task. I used the ruby-taglib library from http://robinst.github.com/taglib-ruby/ to access mp3 tags.
My first attempt was turning out a little too much like an enterprisey Java project so I decided to back up and try to make it a little lighter and more Ruby-esque. I decided on a module for parsing play lists would allow for the best re-use for that functionality while simple classes would represent play lists and play list entries. With the library written the main script became the following:
#!/usr/bin/env ruby # require 'fileutils' require './playlist-parser' dest_dir = File::expand_path(ARGV[0]) source = PlayList.new(ARGV[1]) dest = PlayList.new(File::join(dest_dir, File::basename(source.to_s))) source.read_playlist do |entry| basename = PlayListEntry::sanitize(entry.artist + ' - ' + entry.album + ' - ' + entry.track + ' - ' + entry.title + '.mp3') dest_entry = PlayListEntry.new(basename) dest.playlist_entries << dest_entry dest_file = File::join(dest_dir, dest_entry.to_s) if not File::exists?(dest_file) then puts "#{entry.source} => #{dest_file}" FileUtils.copy_file(entry.source, dest_file) else puts "#{dest_file} exists" end end dest.write_playlist |
This script is pretty simple. It opens the given play list and iterates over the entries, creating a new play list based on the passed destination directory. The file is copied over as Rhythmbox and Banshee do, using the tag information to determine the file name. Then when we are done we write out the new play list.
The library file is little longer. It includes a module named PlayListParser which had the parsing functionality (such as it is, a play list file is not really very complicated; if you are reading this far open one up in a text editor and you’ll figure it out no problem). Then we have the PlayList class which includes the parser module and provides a write_playlist method. Finally the PlayListEntry which makes tag access convenient.
# http://robinst.github.com/taglib-ruby/ require 'taglib' module PlayListParser attr_accessor :playlist, :playlist_entries def parse_playlist(playlist, &block) @playlist = playlist @playlist_entries = [] save_dir = Dir::pwd Dir::chdir(File::dirname(playlist)) File.open(playlist) do |file| file.readlines.each do |line| line = line.strip if line.empty? or line[0] == '#' then next end if not File.exists?(line) then puts "WARN: File #{line} does not exist in play list #{@playlist}" end entry = PlayListEntry.new(line) @playlist_entries << entry block.call(entry) unless block == nil end end Dir::chdir(save_dir) end end class PlayList include PlayListParser def initialize(playlist) @playlist = playlist @playlist_entries = [] end def read_playlist(&block) parse_playlist(playlist, &block) end def write_playlist File::open(playlist, 'w') do |file| file.puts('#EXTM3U') file.puts(@playlist_entries) end end def to_s return @playlist.to_s end end class PlayListEntry def self.pad_track(track) return ( track < 10 ? '0' + track.to_s : track.to_s) end def self.sanitize(source) return source.gsub(/[":\?]/, '_') end attr_accessor :source, :album, :artist, :comment, :genre, :title, :track, :year def initialize(source) @source = source read_tags end def read_tags if File.exists?(source) then TagLib::FileRef.open(source) do |fileref| tag = fileref.tag @album = tag.album @artist = tag.artist @comment = tag.comment @genre = tag.genre @title = tag.title @track = PlayListEntry::pad_track(tag.track) @year = tag.year unless tag.year == 0 end end end def to_s return @source.to_s end end |
One drawback of the parser is the use of the current working directory to handle relative paths in the play list file. This construct makes the parse_playlist method not thread-safe. (I can’t help but think about these things after working on servers; but I left it that way since this is supposed to be a simple script.)
In the end I learned a few useful things along the way, like the difference between sub and gsub as well as some of the characters that are escaped by Rhythmbox and Banshee when making file names. Also how to split up a Ruby project into more than one file. And I ended up with something I can actually use. All in all a successful excursion into Ruby.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.