MP3Tunesで日本語変換トラブル

MP3tunes.comを使用していてトラブルに遭遇し、なんとか復旧できたので、その顛末をメモしておく。

サービス概略

MP3tunes.comは、mp3等の音楽ファイルを保存してくれるサービスで、驚いたことにフリーのアカウントでも容量無制限で保存してくれる。フリーでの制限は実質的には「一つのファイルの容量が10MB」ということのみ。

本当にそれ以外に制限無いのかが疑問で試してみたのだけど、私の場合は、2200曲、8.5Gは(後述する日本語関連の問題を除き)問題なく保存し、ダウンロードできている。一ファイルの容量制限は、時間にするとだいたい10分くらいが上限になるようだ。JAZZやクラシックでは、この制限で中抜けになっているものが目立つが、ロックやポップスでは、ほとんど問題ない。

サービス内容は、oboe sync という専用クライアントをダウンロードして、これを利用して、アップロードダウンロードすることが基本。それ以外に、WEBページ上のプレイヤーで聞けたり、モバイル用にビットレートを下げて配信してくれるサービスもある(ただこれはひどい音でちょっと聞く気にはならない)。

WEBページ上では、日本語のアーチスト名やアルバムは全部文字化けしてしまうが、曲の中身自体は保存されている模様で、何度かダウンロードのテストをした時には問題なく、一時保存用の「ロッカー」として使うなら問題ないと判断した。

トラブル概略

最初は機能を確認するだけのつもりで使いはじめたが、上記のように文字化け以外は問題なさそうなので、手元のマシンをクリーンインストールする時に、リッピングした音楽ファイルをここに保存することにした。

ところが、テストの時は問題なかったのに、クリーンインストールしてからoboe syncを立ち上げると、途中で内部エラーを表示して異常終了してしまった。(メッセージ等は記録してない)

全部、手持ちのCDからリッピングしたものなので、それをやり直せばよいのだが、さすがに2000曲やり直すのかと思うと、ちょっと落ちこんだ。

そこで、速攻でRubyスクリプトを書いて試行錯誤してみたら、mp3ファイルはなんとかダウンロードできた。

これをiTunesに登録してみたら、一部のid3タグが壊れているみたいで曲名が無いものや化けているものがあるが、8割以上はライブラリとして復旧できた。曲名が無いファイルも聞く分には何も問題ない。

それから、そのダウンロードしたmp3ファイルを調べてみて、問題についておよその目星はついたが、完全には現象、対策等を究明する所まではいってない。

MP3Tunesのサポートには「日本語で問題が起きて、自力復旧したからいいけど、情報が欲しかったらメールください」と連絡したけど、返事はなかった。

トラブル詳細

(網羅的な調査はしてないので、目安として考えてください)

  • リッピングしたソフトは、最初wmaで、途中からiTunes(バージョン等は覚えていない)
  • 壊れているのはアーチスト、アルバム名、曲名のどれかに日本語を含むもののみ
  • 壊れかたにはいろいろある
  • アルバム丸ごと全曲ダメになっている場合と、曲単位でおかしくなっている場合がある
  • iTunes7.1, wma9, winamp5.35 で調査した所、以下のパターンがある
  1. wma9では再生できず、他では文字化けするが再生できる
  2. wma9ではタグ情報(曲名等)が表示されないが再生は可能で、他では再生もタグ表示も正常
  3. どのプレイヤーでも再生は可能だが、タグ情報は一切表示されない

cygwin ruby 1.85 + http://ruby-mp3info.rubyforge.org/ で読ませてみた所、wma9で読めないファイルでは、下記のようなエラーが発生する。

/usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:209:in `iconv': invalid encoding ("
ISO-8859-1", "UNICODE") (Iconv::InvalidEncoding)
        from /usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:209:in `decode_tag'
        from /usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:280:in `add_value_to_t
ag2'
        from /usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:231:in `read_id3v2_3_f
rames'
        from /usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:220:in `loop'
        from /usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:220:in `read_id3v2_3_f
rames'
        from /usr/lib/ruby/site_ruby/1.8/mp3info/id3v2.rb:135:in `from_io'
        from /usr/lib/ruby/site_ruby/1.8/mp3info.rb:412:in `parse_tags'
        from /usr/lib/ruby/site_ruby/1.8/mp3info.rb:171:in `initialize'
        from /usr/lib/ruby/site_ruby/1.8/mp3info.rb:271:in `new'
        from /usr/lib/ruby/site_ruby/1.8/mp3info.rb:271:in `open'
        from check_mp3.rb:7
        from check_mp3.rb:4:in `glob'
        from check_mp3.rb:4

推測

アップロードする前には、たぶんどのプレイヤーでも全部のファイルを再生、表示できてたと思われる。チェックはしてないけど、特にトラブルは無かった。だから、MP3Tunesが、アップロードしたファイルを保存する時かそれを読みこむ時に、一部のidタグを(意図せずに)壊していることは間違いないと思う。

そして壊しているのは、日本語が含まれる文字列なので、日本語のコード変換に関連していることは推測できる。

たくさんある日本語の文字列の中の一部でしか問題が発生しないことや、壊したファイルを読めるソフトと読めないソフトがあることから考えると、このあたりに関係ありそう。

Windows 機種依存文字等の日本語関係の文字コード変換を Iconv モジュールで行うには、対策を施してある iconv ライブラリを用いる必要があります。具体的には以下のライブラリを使う事になるでしょう。

ただし、たとえば、YMOの「ジャム」というタイトルが表示されてないので、JIS X 0213で追加された文字を含まない曲でも問題が起こっているようで、そこはよくわからない。

各ファイルを徹底的に調査すれば、パターンや原因が見つかるかもしれないけど、そこまでやる時間はありません。

ダウンロードスクリプト

oboe sync がダウンしてしまうアカウントに対しても、下記のスクリプトで全曲ダウンロードはできた。

ブラウザから1曲をダウンロードしてみて、それを回線トレースして、繰り返し処理するスクリプトにしたもの。やっつけ仕事なので、中身はかなりいいかげん。

基本的には、Rubyがわかる方に限り自己責任でご利用ください。

require 'open-uri'
require 'net/http'

EMAIL="tnaka@dc4.so-net.ne.jp"
PASSWORD="*********"
Net::HTTP.version_1_2   
$CNT = 0
OUTDIR="mp3"

def get_path
  $CNT += 1
  path = "#{OUTDIR}/%04d.mp3" % $CNT
end

class MP3Tunes
  attr_reader :sessid
  def login(mail, pass)
    Net::HTTP.start('shop.mp3tunes.com', 80)  do |http|
      response = http.post('/loginform.php',
                           "frmEmail=#{mail}&frmPassword=#{pass}")
      p response
      case response
      when Net::HTTPSuccess 
      when Net::HTTPRedirection 
        if  response.header["set-cookie"].to_s =~ /PHPSESSID=([0-9a-f]*)/
          @sessid = $1
        end
      end
      p @sessid
    end
  end

  def get_albums
    ret = []
    open("http://www.mp3tunes.com/locker/cb/index_albums?loadflag=yes", 
         "Cookie"=> "PHPSESSID=#{@sessid}")  do |f|
      f.each_line do |l|
        #puts l
        if l =~ /FilterDelay\('(\d+)'\)/
          ret << $1
        end
      end
    end
    ret
  end

  def get_playlist(album)
    ret = []
    open("http://www.mp3tunes.com/locker/mp3tunes_playlist.m3u?albums=#{album}", 
         "Cookie"=> "PHPSESSID=#{@sessid}")  do |f|
      f.each_line do |l|
        next unless l =~ /^http/
        ret << l
      end
    end
    ret
  end
end

if ARGV[0] == "test"
  require 'test/unit'

  class TestMP3Tunes < Test::Unit::TestCase

    def setup
      @m = MP3Tunes.new
    end

    def test_login_fail
      @m.login(EMAIL, "xxx")
      assert_nil(@m.sessid)
    end

    def test_login
      @m.login(EMAIL, PASSWORD)
      assert_not_nil(@m.sessid)
    end

    def test_albums
      @m.login(EMAIL, PASSWORD)
      a = @m.get_albums
      assert_not_equal(0, a.size)
    end

    def test_playlist
      @m.login(EMAIL, PASSWORD)
      a = @m.get_albums
      p a
      assert_not_equal(0, a.size)
      l = @m.get_playlist(a[0])
      assert_not_equal(0, l.size)
    end
  end
  puts "end"
else
  m = MP3Tunes.new
  m.login(EMAIL, PASSWORD)
  m.get_albums.each_with_index do |album, n|
    next if n % 2 == 0
    puts "album #{album}"
    begin
      m.get_playlist(album).each do |song|
        path = get_path
        puts "song #{song}"
        next if FileTest::exists?(path)
        begin
          open(song,
               "Cookie"=> "PHPSESSID=#{m.sessid}")  do |f|
            File::open(path, "wb") do |of|
              of.write f.read
            end
          end
        rescue
          p $!
        end
      end
    rescue
      p $!
    end
  end
end