require "mp3tag" require "fileutils" require "mp3info" require "shellwords" class Folder attr_reader :dirname,:tocname,:parentname def initialize(givenpath) Dir.chdir givenpath @dirname = File.basename(Dir.pwd) @parentname = File.dirname(Dir.pwd) @tocname = find_tocname end def find_tocname expr = %r{(.*)\.0} m1 = expr.match(dirname) if m1 toc = m1[1] + ".toc" else toc = dirname + ".toc" end toc end def parse_toc_v1 expr1 = %r{^DTITLE=(.*) / ([^\r]*)} expr2 = %r{^TTITLE(\d+)=([^\r]*)} title = [] artist = "" album = "" # reading lines... into line tocfile = File.new(tocname) tocfile.each_line {|line| m1 = expr1.match(line) if m1 artist = m1[1].chomp album = m1[2].chomp end m2 = expr2.match(line) if m2 # We store Track Number minus one n = m2[1].to_i title[n] = m2[2].chomp #puts "title[#{n}] = {#{title[n]}}" end } title != [] && artist != "" && album != "" && {:artist => artist,:album => album,:title => title} end def valid_toc_v1 File.exist?(tocname) && parse_toc_v1 end def classify {:toc_exists => File.exist?(tocname), :toc_version => (if valid_toc_v1 then 1 else 0 end)} end def debug puts "directory: #{dirname}" puts "parentname: #{parentname}" puts "tocname: #{tocname}" classify.each_pair {|k,v| puts "#{k.to_s}: #{v.to_s}"} puts " " end def songs expr = %r{\.mp3$} Dir.entries(".").delete_if{|file| !(expr =~ file) || !FileTest.file?(file) || !FileTest.readable?(file) || FileTest.zero?(file) }.sort end end class Song attr_reader :filename,:dirname attr_writer :filename def initialize(dirname,filename) @dirname = dirname @filename = filename end # An id3 tag is not "valid" unless the four tags below are not null. def has_valid_id3v1 has_id3v1 = Mp3Tag.hastag?(filename) if !has_id3v1 then false else tags = Mp3Tag.new(filename) tags.artist.length > 0 && tags.album.length > 0 && tags.tracknum.class != nil && tags.songname.length > 0 end end def has_id3v2 Mp3Info.hastag2?(filename) end # This is false: nirvana-nevermind-album/01_-_smells_like_teen_spirit_192_lame_cbr.mp3 # This is true: gorillaz-first-album/gorillaz-first-album.01.mp3 def filename_is_album expr1 = %r{^(.*)\.\d\d\.mp3} m1 = expr1.match(filename) (m1 && dirname == m1[1]) || false end def title_puts(title) title.each_index{ |i| puts "track #{i.to_s} is: #{title[i]}"} end def classify {:has_valid_id3v1 => has_valid_id3v1, :has_id3v2 => has_id3v2, :filename_is_album => filename_is_album} end def debug puts "filename: #{filename}" classify.each_pair {|k,v| puts "#{k.to_s}: #{v.to_s}"} puts " " end end def make_songs(folder) folder.songs.collect{|filename| Song.new(folder.dirname,filename)} end class Automaton attr_reader :action_table, :folder, :song, :songhash def initialize(folder,song) @action_table = [{:conditions => {:has_valid_id3v1 => false,:has_id3v2 => false,:toc_exists => true,:toc_version => 1, :filename_is_album => true}, :actions => [:convert_toc1_to_metadata]}, {:conditions => {:has_valid_id3v1 => false,:has_id3v2 => false,:toc_exists => true,:toc_version => 1, :filename_is_album => false}, :actions => [:rename_file, :convert_toc1_to_metadata]}, {:conditions => {:has_valid_id3v1 => false,:has_id3v2 => true}, :actions => [:move_v2_to_v1,:delete_v2,:restart_script]}, {:conditions => {:has_valid_id3v1 => true,:has_id3v2 => true,:filename_is_album => true}, :actions => [:delete_v2]}, {:conditions => {:has_valid_id3v1 => true,:has_id3v2 => true,:filename_is_album => false}, :actions => [:delete_v2,:rename_file]}, {:conditions => {:toc_exists => false,:filename_is_album => false,:has_valid_id3v1 => false,:has_id3v2 => false}, :actions => [:partial_info]}, {:conditions => {:has_valid_id3v1 => true,:has_id3v2 => false,:filename_is_album => true}, :actions => [:everything_is_ok]}, {:conditions => {:has_valid_id3v1 => true,:has_id3v2 => false,:filename_is_album => false}, :actions => [:rename_file]}, {:conditions => {:has_valid_id3v1 => false,:has_id3v2 => false,:toc_exists => true,:toc_version => 0}, :actions => [:unknown_toc_format]}, {:conditions => {:has_valid_id3v1 => false, :has_id3v2 => false, :filename_is_album => true, :toc_exists => false}, :actions => [:youre_screwed]}] @folder = folder @song = song @songhash = merge_hashes(folder.classify,song.classify) end def rename_file expr1 = %r{^(\d\d)[\. _-](.*)\.mp3} m1 = expr1.match(song.filename) expr2 = %r{(.*)_-_(\d\d)_\.(.*)\.mp3} m2 = expr2.match(song.filename) if m1 File.rename("#{song.filename}","#{song.dirname}.#{m1[1]}.mp3") song.filename = "#{song.dirname}.#{m1[1]}.mp3" puts "renaming file to #{song.filename}" else if m2 File.rename("#{song.filename}","#{song.dirname}.#{m2[2]}.mp3") song.filename = "#{song.dirname}.#{m2[2]}.mp3" puts "renaming file to #{song.filename}" else puts "#{song.filename}: filename parse error" end end end def convert_toc1_to_metadata toc = folder.parse_toc_v1 tags = Mp3Tag.new(song.filename) expr = %r{^(.*)\.(\d\d)\.mp3} m1 = expr.match(song.filename) if m1 puts "#{song.filename}: Writing Mp3 Tag." tags.artist = toc[:artist] tags.album = toc[:album] tags.tracknum = m1[2].to_i tags.songname = toc[:title][m1[2].to_i - 1] tags.commit #puts "Artist: #{tags.artist}","Album: #{tags.album}","Track Number: #{tags.tracknum}","Title: #{tags.songname}" else puts "#{song.filename}: filename parse error" end end def move_v2_to_v1 system("eyeD3 --to-v1.1 #{song.filename.shellescape}") end def delete_v2 system("eyeD3 --remove-v2 #{song.filename.shellescape}") end def restart_script make_songs(folder).each{|song| Automaton.new(folder,song).dispatch} end def partial_info # expr1 = %r{^(\d\d)(?:_| )?-(?:_| )?(.*?)(?:_192_lame_cbr)?.mp3} # m1 = expr1.match(song.filename) # if m1 # tracknum = m1[1].to_i # songname = m1[2].gsub("_"," ") # else # puts "#{song.filename}: filename parse error" # end # tags = Mp3Tag.new(song.filename) # tags.artist = artist # tags.album = album # tags.tracknum = tracknum # tags.songname = songname # tags.commit #puts "Artist: #{tags.artist}","Album: #{tags.album}","Track Number: #{tags.tracknum}","Title: #{tags.songname}" puts "#{folder.dirname}/#{song.filename}: partial information" end def everything_is_ok puts "#{song.filename} is okay" end def unknown_toc_format puts "#{folder.dirname}: Unknown toc format" end def youre_screwed puts "#{song.filename}: insufficient data for tags." end def error_message puts "#{song.filename}: unrecognized conditions." end def merge_hashes(hash1,hash2) combined = {} hash1.each_pair{|k,v| combined[k] = v} hash2.each_pair{|k,v| combined[k] = v} combined end def myall(hash) bool = true hash.each_pair{|k,v| bool = bool && yield(k,v)} bool end def left_join(hash1,hash2) myall(hash2) {|k,v| v == hash1[k]} end def dispatch actions = [:error_message] action_table.each{|entry| if left_join(songhash,entry[:conditions]) # puts "#{song.filename}: #{entry[:conditions]} ==> #{entry[:actions]}" actions = entry[:actions] end} actions.each{|v| method(v).call} end end def hashdraw(title,hash) puts title hash.each_pair{|k,v| puts "#{k}: #{v}"} puts "" end folder = Folder.new(ARGV[0]) #folder.debug make_songs(folder).each{|song| Automaton.new(folder,song).dispatch} #hashdraw("lets see if this works",folder.parse_toc_v1)