Mp3TagFS

FUSEじゃねーのだけど。DokanというWINDOWS用のFUSE的なもの用なのだけど。
でもたぶんちょっといじればFUSE用になるはず。


えーとMp3のTagでファイルシステムを作ります。指定したディレクトリ以下のMP3をさらい、
タグを読み込んで"/アーティスト名/アルバム/曲"という階層を作ります。
iPodがさー、ファイルネームを独自形式で保存するのでそっから取り出したい場合、
こんなんあればいいなあと思って作った。


Dokanとmp3infoに依存します。別途入れてください。
Dokan: user-mode file system for Windows
http://rubyforge.org/projects/ruby-mp3info

#!ruby -Ku

require 'dokanfs'
require 'delegate'

class DokanProxy
  def read(path, offset, length, fileinfo)
    return "" unless @root.file?(path)
    if @root.respond_to?(:read_offset)
      @root.read_offset(path, offset, length)
    else
      str = @root.read_file(path)
      if offset < str.length
        str[offset, length]
      else
        false
      end
    end
  end
end

class InsensitiveString < DelegateClass(String)
  def hash
    __getobj__.downcase.hash
  end
  def eql?(oth)
    __getobj__.downcase.eql?(oth.downcase)
  end
  def ==(oth)
    eql?(oth)
  end
end

class Mp3Tag
  require 'mp3info'
  require 'jcode'
  require 'kconv'

  def cleaning(s)
    InsensitiveString.new(s.gsub(/[\/\\:]/, "_").kconv(Kconv::SJIS))
  end

  def initialize(path)
    @map = Hash.new
    Dir.glob(path+"/**/*.mp3").each{|filename|
      p filename

      info = Mp3Info.new(filename)
      tag = info.tag
      tag2 = info.tag2

      ar = tag.artist || tag2.TP1 || '不明なアーティスト'
      al = tag.album  || tag2.TAL || '不明なアルバム'
      ti = tag.title  || tag2.TT2 || '不明な曲'

      artist_album = ((@map[cleaning(ar)] ||= {})[cleaning(al)] ||= {})
      i = 0; tmp = ti
      while artist_album[title = cleaning(tmp+".mp3")]
        tmp = "%s-%04d" % [ti, i+=1]
      end

      artist_album[title] = filename
    }
  end

  def split(path)
    path.split("/")[1..-1].to_a.map{|e| InsensitiveString.new(e)}
  end

  def contents path
    if path == "/"
      @map.keys
    else
      a = split(path)
      case a.size
      when 1
        @map[a[0]].keys
      when 2
        @map[a[0]][a[1]].keys
      else
        []
      end
    end.compact
  end

  def file? path
    a = split(path)
    a.to_a.size == 3 && @map[a[0]][a[1]][a[2]]
  end

  def directory? path
    a = split(path)
    case a.size
    when 0
      true
    when 1
      @map[a[0]]
    when 2
      @map[a[0]][a[1]]
    end
  end

  def read_offset(path, offset, len)
    a = split(path)
    open(@map[a[0]][a[1]][a[2]], 'rb'){|port|
      port.pos = offset
      return port.read(len)
    }
  end

  def size path
    a = split(path)
    file?(path) ? FileTest.size(@map[a[0]][a[1]][a[2]]) : 0
  end
end

if __FILE__ == $0
  DokanFS.set_root(Mp3Tag.new(ARGV[0]))
  DokanFS.mount_under("r")
  DokanFS.run
end