mp3のタグの文字コードがそろってないのと、ID3v1だけだったり、ID3v2とID3v1が両方あったりとなんだか美しくないので、統一したくなった。Foobar2000はUnicodeに対応しているらしい。そう、もう時代はきっとUnicode。
そこで、Rubyでちゃちゃっとできないかなとぐぐるさんに質問。いくつかRubyでID3タグを操作するライブラリがあったが、外部ライブラリ依存の物は、インストールが面倒なので却下。必然的に、mp3info というライブラリになった。Pure Rubyらしい。
まあ、かといってもインストールはしなければならないわけです。
インストールの方法は2通りあって、
- Gems経由のインストール
- 自前のインストール
Gemsは今後も使うかなと思うので、今回はGems経由で。
- Gems
http://rubygems.rubyforge.org
から取ってきて,解凍したフォルダで
ruby setup.rb
- mp3info
$ gem install ruby-mp3info
でインストールできます.
このmp3info、ファイルのタグにハッシュ形式でアクセスできる。便利だ。
そしてできあがった物がこんな物。適当&心配性で執拗なチェックしてるので、汚さとか無駄さは勘弁。使う場合は、UTF-8で保存してください。
#!/bin/ruby =begin ruby ID3TagStandardizer.rb [DirPath] 指定したディレクトリにある*.mp3を全部 1.ID3v2化 2.ID3v1削除(ID3v2もあれば比較しながら長いものをコピー) 3.Unicode化 **************************** Gemsのmp3infoライブラリを必要とします. =end require 'rubygems' require 'mp3info' require 'nkf' $KCODE="u" module Terminal def set_initialize() case RUBY_PLATFORM when "i386-cygwin" $TERMINAL_CODE="s" else $TERMINAL_CODE="e" end end def conv(obj) begin if obj.class != String.class then obj = obj.to_s end rescue return "" end kcode = {nil=>"W", "UTF8"=>"W", "SJIS"=>"S", "EUC"=>"E"} return NKF.nkf("-#{kcode[$KCODE]}#{$TERMINAL_CODE}",obj) end def message(msg="") print(conv(msg)) STDOUT.flush end end include Terminal Terminal.set_initialize def strict_convert_into_utf16(str) # ソースがUTF-16でも-w16Bに統一するため、インプット-W16としてエンコードする guessCode_to_nkfOption_hash = { NKF::JIS => "-J", NKF::EUC => "-E", NKF::SJIS => "--oc=CP932", # naruseさんのご指摘から NKF::BINARY => "BINARY", NKF::UNKNOWN => "UNKNOWN(ASCII)", NKF::UTF8 => "-W8", NKF::UTF16 => "-W16" } guess_code = NKF.guess2(str) input_option_nkf = guessCode_to_nkfOption_hash[guess_code] if input_option_nkf == guessCode_to_nkfOption_hash[NKF::UNKNOWN] && input_option_nkf == guessCode_to_nkfOption_hash[NKF::BINARY] then raise "文字コードが判別できません" end return NKF.nkf("-m0 #{input_option_nkf} -w16B",str) end def main path = ARGV[0] if path =~ /(.*)\/$/ then path = $1 end # main processing non_processed_file_list = Array.new() dir = Dir.new(path) dir.each{|mp3file| if mp3file =~ /\.mp3$/ then # Beginning Setting mp3 = Mp3Info.open(path+"/"+mp3file) mp3.tag2.options[:encoding] = 0 Terminal.message("\nNow :#{mp3file}\n") begin # ID3v1のUTF16化 if mp3.hastag1? then Terminal.message("Convert ID3v1...") Mp3Info::V1_V2_TAG_MAPPING.each{|id3v1, id3v2| Terminal.message(",#{id3v1}") if mp3.tag1[id3v1] != nil && mp3.tag1[id3v1].class == String then mp3.tag1[id3v1] = strict_convert_into_utf16(mp3.tag1[id3v1]) end } Terminal.message("...Done\n") end # mp3.hastag1? then # ID3v2のUTF16化 if mp3.hastag2? then Terminal.message("Convert ID3v2...") ID3v2::TAGS.each{|id3v2, description| Terminal.message(",#{id3v2}") if mp3.tag2[id3v2] != nil && mp3.tag2[id3v2].class == String then mp3.tag2[id3v2] = strict_convert_into_utf16(mp3.tag2[id3v2]) end } Terminal.message("...Done\n") end # mp3.hastag1? then # ID3v1とID3v2のマージ # ID3v1、ID3v2があるとき:タグ要素で文字列が長い方を優先 # ID3v1のみある時:ID3v2にコピー if mp3.hastag1? && mp3.hastag2? then Terminal.message("Marging ID3v1 and ID3v2...\n") Mp3Info::V1_V2_TAG_MAPPING.each{|id3v1, id3v2| if mp3.tag1[id3v1] != nil && mp3.tag2[id3v2] != nil then if mp3.tag1[id3v1].to_s.length > mp3.tag2[id3v2].to_s.length then mp3.tag2[id3v2] = mp3.tag1[id3v1] Terminal.message("Revised #{id3v2}: =>#{NKF.nkf("-w",mp3.tag2[id3v2].to_s)}\n") end end } elsif mp3.hastag1? && !(mp3.hastag2?) then Terminal.message("Setting ID3v1 to ID3v2...\n") Mp3Info::V1_V2_TAG_MAPPING.each{|id3v1, id3v2| Terminal.message("#{id3v2}") if mp3.tag1[id3v1] != nil then mp3.tag2[id3v2] = mp3.tag1[id3v1].to_s Terminal.message(" =>#{NKF.nkf("-w",mp3.tag1[id3v1].to_s)}\n") end } end # if mp3.hastag1? && mp3.hastag2? then #write mp3.close Mp3Info.removetag1(path+"/"+mp3file) rescue => e Terminal.message("\n処理中にError: \"#{e}\"\n") Terminal.message("無視します: #{mp3file}.\n") non_processed_file_list.push(mp3file) mp3.reload mp3.close break end # begin end # if mp3file =~ /\.mp3$/ then } # dir io = open("IgnoreFiles.txt","w") non_processed_file_list.each{|line| io.print("#{line}\n") } io.close end #main main
やってることは、
- オプションで指定したフォルダにある拡張子mp3のファイル全てに対して以下をおこなう。
- ID3v1があれば中身をNKF.guess2で判定しながらUnicode(UTF-16BigEndian With BOM)へ変換。
- ID3v2もあれば同じように変換
- ID3v1だけならそれをID3v2へコピー
- ID3v1とID3v2があれば、要素を比較し、「文字列の長い方を優先」してID3v2へコピー
- ID3v1を削除
- 文字コードが判定できないときは、手を付けずIgnoreList.txtにそのファイル名を書き出す
ということです。
ちなみによく分からないんだけれど、ちょっとはまったのが
mp3.tag2.options[:encoding] = 0
の部分。始め何度やっても文字化けしていた。RDocにも情報がない。
なんで、ソースを眺めると、よく分かってないけど内部で文字コードをSJISからUnicodeに変換している部分があった。どうやら、これのせいでこっちが変換した文字をSJIS仮定でUnicodeに再変換されていたっぽい。そのルーチンを上記のようにしておくと外れる様子。
RDocに書いといてよ('A`)
見たら分かると思いますが、一応「フォルダの中全部」、「mp3だけ」を処理します。mp3infoライブラリは、Ogg、Apeその他にも対応しているらしいので(つか、先頭からのバイト数で読み込んでるから同じならいけるんだろな)
if mp3file =~ /\.mp3$/ then
の辺りとか、あとファイル単位に処理したかったらDirの辺りなどを適当に改造してください。