Oct 112012

Google Music Manager uploads are based on looking for music files in a particular directory. This isn’t helpful if you have a large directory structure of music and want to upload a subset of it. In my case, I want to use Banshee’s smart playlist feature to select songs to upload. Fortunately Banshee has a .m3u playlist export, but this is only half the battle. The other half is to use symlinks to fool Google Music Manager into thinking the songs in the playlist are in its directory.

The following shell command does the trick. It takes input lines in the .m3u of the form <path>/<artist>/<album>/<song> (e.g. ../../../mnt/onion/media/Music/Banshee/Wir Sind Helden/Soundso/01. (Ode) An Die Arbeit.mp3) and makes symlinks of the form <artist>_<album>_<song>.

cat ~/my_playlist.m3u | ruby -ne 'IO.popen(["ln", "-s", "#{$&}", "./#{$2[0..50]}_#{$3[0..50]}_#{$4[0..50]}.#{$5}"]) if $_.strip =~ /^([^#].*)\/([^\/]*)\/([^\/]*)\/([^\/]*)\.([^.\/]*)$/'

For each line ($_) that matches the pattern (not starting with #, having at least the expected number of slashes), execute: ln -s <input line ($&)> <composed filename> . The [0..50] ranges keep filename length manageable.

Feb 082012

Often Array(arg) is used for this, but is flawed. Note the last result when applied to a Hash:

> Array(42)
 => [42] 
> Array([1,2,3])
 => [1, 2, 3] 
> Array(nil)
 => [] 
> Array("foo")
 => ["foo"] 
> Array({"foo" => "bar", "biz" => "baz"})
 => [["foo", "bar"], ["biz", "baz"]]

What went wrong is that Array() calls the (now deprecated) to_a on each of its arguments. Hash has a custom to_a implementation with different semantics. Instead, do  this:

class Array
  def self.wrap(args)
    return [] unless args
    args.is_a?(Array) ? args : [args]

That yields the expected results, even for Hashes:

> Array.wrap(42)
 => [42] 
> Array.wrap([1,2,3])
 => [1, 2, 3] 
> Array.wrap(nil)
 => [] 
> Array.wrap("foo")
 => ["foo"] 
> Array.wrap({"foo" => "bar", "biz" => "baz"})
 => [{"foo"=>"bar", "biz"=>"baz"}]

Use of is_a? is deliberate; duck-typing in this situation ([:[], :each].all? { |m| args.respond_to? m }) yields unexpected surprises since e.g. String is Enumerable and would not get wrapped.

For further discussion see Ruby-forum thread “shortcut for x = [x] unless x.is_a?(Array)” and StackOverflow “Ruby: Object.to_a replacement“.

 Tagged with: