2008年12月18日

FreeDBからCD情報を取得するためのDISCID算出

ウルトラご無沙汰。

WindowsMediaPlayerなどでは、突っ込んだCDの内容を勝手に調べて、そのアルバム名とかアーティスト情報とか、曲名とかを表示してくれる。これはもちろんCDにその内容が書き込まれているわけではなくて、CDに収録された曲の長さと数の情報から、Internetを通して情報を取得し、それを表示するのだ。

自作のアプリケーションに、この、CDから情報を取得する機能をつけようと思った。

CDをPCに突っ込んで、Explorerで内容を見ると、Track01.cdaといったファイル名のファイルがトラック数だけ存在するのがわかる。これがWindowsが仮想的にCDのTOC情報をファイルとして扱えるように見せている仮装TOCファイルだ。一曲分44バイト。CD一枚分のTOCから、あるアルゴリズムに基づいてDISC_IDを算出してやると、それをもとにFreeDBサーバに対して問い合わせをかけて、CD情報を取得することができる。

で、そのDISC_IDの取得だが、Posix系のOSとかでは簡単に取得するプログラムが流通しているけれども、Windows環境、しかも自分の開発言語であるDelphiにはそんなものない。無いものは作るのがMZユーザDelphiユーザ。早速情報を検索する。結論から言うと、Googleで「FreeDB DISCID」で検索して、割と最初の方に出てくるブログのアルゴリズムは、全体的に物凄く参考になるけど、ちょっとだけ間違っている。

DISCIDの算出に使うのは、各曲の開始位置のフレーム番号ではなく、各曲の開始位置の秒数でした。
間違いに気が付いたのは、やはり英語ドキュメントをざっと読んだから。

で、ドライブを指定して、その中のcdaファイルを取得し、そこからDiscIDを計算するロジックなんてものを、さらっとDelphiで書いてみたので参考までに。手持ちのCD3枚程度で試したけど、結果良好でした。

function calcFingerPrint(n: LongInt) : Integer;
var
 R : Integer;
begin
  R := 0;
  while (n > 0) do begin
    R := R + (n mod 10);
    n := (n div 10);
  end;


  Result := R;

end;

function getDiscID(Drive: String) : String;
type
  rowdata = array[0..43] of byte;
  TCDA = record
    Row: rowdata;
    StartFrame: Integer;
    StartSec: Integer;
    StartMin: Integer;
    StartHur: Integer;
    StartTime: LongInt;
    StartFrames: LongInt;
    LengFrame: Integer;
    LengSec: Integer;
    LengMin: Integer;
    LengHur: Integer;
    LengTime: LongInt;
    LengFrames: LongInt;
    FingerPrint: Integer;
  end;
  cdafile = file of rowdata;
var
  DS : String;
  SR : TSearchRec;
  CT : Integer;
  FL : TStringList;
  CDAS: Array of TCDA;
  I : Integer;
  FN : String;
  FS: TFileStream;
  SF : LongInt;
  FT : Integer;
  LN : Integer;
  KV : LongInt;
  KVS : String;
  CMD : String;
  TT : LongInt;
begin
  DS := Drive + ':\';
  CT := 0;
  FT := 0;
  TT := 0;
  FL := TStringList.Create;
  // 指定されたドライブのトラック数取得
  if FindFirst(DS + '*.cda', faAnyFile, SR) = 0 then begin
    repeat
      if not( (SR.Name = '.') or (SR.Name = '..') ) then begin
        if ExtractFileExt(SR.Name)= '.cda' then begin
          inc(CT);
          FL.Add(SR.Name);
        end;
      end;
    until FindNext(SR) <> 0;
    FindClose(SR);
    SetLength(CDAS,CT);

  end;

  // CDAファイルの内容読込み
  for I := 0 to CT - 1 do begin
    FN := DS + FL[I];
    FS := TFileStream.Create(FN,fmShareExclusive);
    FS.Read(CDAS[I].Row,SizeOf(CDAS[I].Row));
    FS.Free;
  end;

  // CDAファイルの解釈
  for I := 0 to CT - 1 do begin
    with CDAS[I] do begin
      StartFrame := Ord(Row[36]);
      StartSec   := Ord(Row[37]);
      StartMin   := Ord(Row[38]);
      StartHur   := Ord(Row[39]);
      LengFrame  := Ord(Row[40]);
      LengSec    := Ord(Row[41]);
      LengMin    := Ord(Row[42]);
      LengHur    := Ord(Row[43]);

      StartTime := StartHur * 3600 + StartMin * 60 + StartSec;
      LengTime  := LengHur  * 3600 + LengMin  * 60 + LengSec;

      TT := TT + LengTime;

      // スタートフレーム数
      SF := StartFrame + StartTime * 75;
      StartFrames := SF;

      LengFrames := LengFrame + LengTime * 75;

      FingerPrint := calcFingerPrint(StartTime);

      FT := FT + FingerPrint;

    end;
  end;

  // キー生成
  FT := FT mod 255;
  LN := round((
    CDAS[CT - 1].StartFrames + CDAS[CT - 1].LengFrames -
    CDAS[0].StartFrames) / 75);
  KV := FT * 16777216 + LN * 256 + CT;
  KVS := IntToHex(KV,8);

  // コマンド生成
  CMD := 'cmd=cddb+query+' + KVS + '+' + IntToStr(CT) + '+';
  for I := 0 to CT - 1 do
    CMD := CMD + IntToStr(CDAS[I].StartFrames) + '+';
  CMD := CMD + IntToStr(TT);

  Result := CMD;


end;

【Delphiの最新記事】
posted by Tig3r at 02:58| Comment(0) | TrackBack(0) | Delphi
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.sakura.ne.jp/tb/24473002

この記事へのトラックバック