MP3 ファイルのタグ付けを一気にやるrubyスクリプト改

ここを見てついむらむらっときて、以前作ったスクリプト書き直した。

コメントにも書いてありますが実行するとカレントディレクトリ以下の mp3 ファイルを操作して自動的にタグ付けを行います。詳細はスクリプトのコメント参照のこと。

もっと便利にして exe 化したらみんな喜ぶのかしら。

あ、使う前に

gem install mp3info

する必要があります。

(2009/07/01 追記: 動作確認は Windows で行いました)

#!/bin/env ruby
#
# MP3 ファイルのタグ付けを一気にやるrubyスクリプト改
#
# 名前の通り mp3 ファイルのタグ付けを行います。実行すると、
# カレントディレクトリ以下を操作して全ての mp3 ファイルのタグ付けを
# ディレクトリ構造に従って行います。
#
# 設定は Settings::Configuration に対して行います。この定数は Hash です。
# キー名がディレクトリ名を示し、ディレクトリ単位に設定をすることができます。
# :__default__ キーはデフォルトの設定です。
#
# 今のところ設定できる項目は :artist キーだけです。これはアーティスト名の
# 指定で、カレントディレクトリから何階層名をアーティスト名として扱うか
# 設定します。デフォルトは1です。つまりカレントディレクトリ直下の
# ディレクトリ名がアーティスト名として利用されます。
#
# アルバム名は mp3 ファイルの直上のディレクトリ名です。タイトルは mp3 ファイルの
# ファイル名から .mp3 を除いたものが設定されます。
#
require 'rubygems'
require 'mp3info'
require 'pathname'

module Settings
  Configuration =
    {
      :__default__ =>
        {
          :artist => 1
        }
    }
end

class Mp3Tagger
  def initialize(path, artist)
    @path = path
    @artist = artist
    @album  = File.basename(File.expand_path(path.parent.to_s))
    @title  = File.basename(path.to_s)[0..-5]
  end

  def need_update?(name)
    @original[name.to_sym] != instance_variable_get("@#{name}".to_sym)
  end

  def update_tag(name)
    new_name= instance_variable_get("@#{name}".to_sym)
    @mp3.tag.__send__((name.to_s << '=').to_sym, new_name)
    @changed ||= {}
    @changed[name] = new_name
  end

  def putchanges(*keys)
    keys.each do |key|
      puts "  #{key}: #{@original[key]} => #{@changed[key]}" if @changed.has_key?(key)
    end
  end

  def update_tags_if_need(*tag_names)
    tag_names.each do |tag_name|
      update_tag(tag_name) if need_update?(tag_name)
    end
  end

  def update
    begin
      update_tags_if_need :artist, :album, :title
    ensure
      @mp3.close if @changed
    end
    if @changed
      puts @path
      putchanges :artist, :album, :title
    end
  end

  def execute
    @mp3 = Mp3Info.open @path.realpath.to_s
    @original = {:title => @mp3.tag.title, :artist => @mp3.tag.artist, :album => @mp3.tag.album}
    update
  end

end

class TagHandler
  include Settings

  def initialize(path='./')
    @path = path
    @setting = Configuration[:__default__]
  end

  private

    def record
      yield read_last_run
    end

  public

    def process(path, relpath_str='', depth=1)
      path = Pathname.new path
      Dir.open(path).each do |relpath_str|
        next if relpath_str == '.' || relpath_str == '..'
        relpath = Pathname.new relpath_str
        if File.directory?(path+relpath)
          if depth == 1
            if Configuration.include? relpath_str
              @setting = Configuration[relpath_str] 
            else
              @setting = Configuration[:__default__]
            end
            @artist = nil
          end
          @artist = relpath_str if depth == @setting[:artist]
          process(path+relpath, relpath_str, (depth+1))
        else
          next if File.extname(relpath_str) != '.mp3'
          begin
            tagger = Mp3Tagger.new(path+relpath, @artist)
            tagger.execute
          rescue Mp3InfoError => e
            @errors ||= []
            error =
              {
                :file => path+relpath,
                :message => e.message
              }
            @errors << error
          end
        end
      end
    end

    def tagging
      process @path
    end

  attr_reader :errors
end

handler = TagHandler.new
handler.tagging
if handler.errors
  puts
  puts "Error occured"
  handler.errors.each do |error|
    puts "  #{error[:file]}"
    puts "    #{error[:message]}"
  end
end

print "\nPlease hit Enter for exit."
$stdin.gets

exit