Victus Spiritus

home

How I cleaned up my music collection with 22 lines of ruby

16 Jul 2010

*update* I added the music sort files to a github repo: http://github.com/victusfate/music-sort

I've got a fairly sizable collection of music. It's more than I need, and certainly more than I want to worry about. I decided before nuking it to make one more effort to clean it up and shrink it down to one directory. This script could have been 21 lines of code but I preferred monitoring it with a puts statement.

The first pass code:

require 'fileutils'
orig = '/home/messel/Desktop/music/'
dest = '/home/messel/Desktop/mobilemusic/'
Dir.chdir(orig)
Dir.glob('*.m3u') do |m3u_name|
  m3us = IO.readlines(m3u_name)
  out_m3u = File.new(dest+m3u_name,'w+')
  m3us.each do |m3u|
    m3u.gsub!(/\/,'/')
    b1 = File.basename(m3u)
    out_m3u << b1 # write new m3u with carriage return
    m3u = m3u.chomp # remove carriage return for file manip
    unless m3u['#']
      basename = File.basename(m3u)
      puts "orig #{orig+m3u} dest #{dest+basename}"
      if File.exist? orig+m3u and !File.exist? dest+basename
        FileUtils.cp(orig+m3u,dest+basename)
      end
    end
  end
  out_m3u.close
end

As you can imagine for large music collection operations, there were a number of exception files that were missing or had special characters I failed to handle. My next script identified the missing files from my new music collection folder, but that were still referenced on playlists.

def check_for_file(m3u_name)
  m3us = IO.readlines(m3u_name)
  m3us.each do |m3u|
    m3u = m3u.chomp # remove carriage return for file manip
    puts "List #{m3u_name} missing #{m3u}" unless File.exist? m3u or m3u['#']
  end
end

if (ARGV.size > 0)
  check_for_file ARGV[0].to_s
else
  Dir.glob('*.m3u').each do |m3u_name|
    check_for_file m3u_name
  end
end

For this script I added an optional parameter (ARGV) to let me test it on a single file first before going hog wild in the full directory. After identifying a number of missed files, I modified the missing script to go back and grab the files from another remote disk where I back up much of my older music.

require 'fileutils'

def check_for_file(m3u_name,path,opath)
  m3us = IO.readlines(m3u_name)
  m3us.each do |m3u|
    m3u = m3u.chomp # remove carriage return for file manip
    unless File.exist? m3u or m3u['#']
      puts m3u
      Dir.glob(path+'**/'+m3u).each do |found|
         puts "found file on FIRELITE #{found}"
         FileUtils.cp(found,opath + m3u)
      #      Process.exit
      end
    end
  end
end

path = '/media/FIRELITE_/music/'
opath = '/home/messel/Desktop/music/'

if (ARGV.size > 0)
  check_for_file ARGV[0].to_s, path, opath
else
  Dir.glob('*.m3u').each do |m3u_name|
    check_for_file m3u_name, path, opath
  end
end

Even after all that there were many play lists with files that I no longer had or knew their whereabouts. Thus came the great pruning. The following program identifies at least one missing music file, then creates a new output play list skipping over any missing files.

require 'fileutils'
def check_for_file(m3u_name,opath)
  prune = false
  m3us = IO.readlines(m3u_name)
  m3us.each do |m3u|
    m3u = m3u.chomp # remove carriage return for file manip
    unless File.exist? m3u or m3u['#']
      prune = true
      break
    end
  end
  if prune
    puts "Pruning from #{m3u_name}"
    FileUtils.mv(m3u_name,opath+"back_"+File.basename(m3u_name))
    out_m3u = File.new(opath+File.basename(m3u_name),'w+')
    prevline = ''
    m3us.each do |m3u_line|
      m3u = m3u_line.chomp # remove carriage return for file manip
      if m3u['#']
        prevline = m3u + "\n"
        next
      end
      if File.exist? m3u
#        puts prevline unless prevline.length == 0
#        puts m3u_line
        out_m3u << prevline unless prevline.length == 0
        out_m3u << m3u_line
        prevline = ''
      end

    end
    out_m3u.close
  end
end

opath = '/home/messel/Desktop/music/'

if (ARGV.size > 0)
  check_for_file ARGV[0].to_s, opath
else
  Dir.glob('*.m3u').each do |m3u_name|
    check_for_file m3u_name, opath
  end
end

But I wasn't very careful with my script, and used '\n' instead of "\n" (corrected above) which mean instead of line feeds (carriage returns for ancient typewriter folks like me) between description and file lines, I had ONE BIG MESS. After rolling my sleeves up and spelunking the dangerous caverns of regular expressions I came up with one final script to correct my woes. This script was appropriately labeled "oh_god_why.rb".

require 'fileutils'
def check_for_file(m3u_name,opath)
  prune = false
  m3us = IO.readlines(m3u_name)
  out_m3u = File.new(opath+File.basename(m3u_name),'w+')
  m3us.each do |m3u_line|
    out_m3u << m3u_line.gsub(/\n/,"\n")
  end
  out_m3u.close
end

opath = '/home/messel/Desktop/music/'

if (ARGV.size > 0)
  check_for_file ARGV[0].to_s, opath
else
  Dir.glob('*.m3u').each do |m3u_name|
    check_for_file m3u_name, opath
  end
end

Astute readers will notice that I've included several programs beyond the original 22 line script throughout this post. When it comes to doing jobs right, I believe a slight exaggeration goes a long way, but it's best to show your friends the full truth. "Pay no attention to the man behind the curtain".

references: