{M4A code template}
unit fmt_M4A;
{$include compilers.inc}

interface
uses wat_api;

function ReadM4A(var Info:tSongInfo):boolean; cdecl;

implementation

uses windows,common,io,srv_format,
{$IFDEF KOL_MCK}KolZlibBzip{$ELSE}Zlib,zwrapper{$ENDIF};

type
  mp4Atom = record
    len:dword;
    name:dword;
  end;

const
  at_moov = $766F6F6D;
  at_mvhd = $6468766D;
  at_udta = $61746475;
  at_meta = $6174656D;
  at_ilst = $74736C69;
  at_cmov = $766F6D63;
  at_dcom = $6D6F6364;
  at_cmvd = $64766D63; // 4 - unpacked size, data
  at_trak = $6B617274;
  at_tkhd = $64686B74; // not needed
  at_mdia = $6169646D;
  at_minf = $666E696D;
  at_smhd = $64686D73;
  at_vmhd = $64686D76;
  at_stbl = $6C627473;
  at_stsd = $64737473;

const
  atm_nam  = $6D616EA9; // title
  atm_ART  = $545241A9; // artist
  atm_wrt  = $747277A9; // writer
  atm_alb  = $626C61A9; // album
  atm_day  = $796164A9; // date
  atm_cmt  = $746D63A9; // comment
  atm_gen  = $6E6567A9; // alt.genre
  atm_gnre = $65726E67; // genre
  atm_trkn = $6E6B7274; // track number
//  atm_zlib = $62696C7A;

type
  pstsd = ^tstsd;
  tstsd = packed record
    version       :byte;
    flags         :array [0..2] of byte;
    NumEntries    :dword;
    SampleDescSize:dword; // $56
    DataFormat    :dword;
    reserved      :array [0..5] of byte;
    RefIndex      :word;
    Ver           :word;
    Revision      :word;
    Vendor        :dword;
    Temporal      :dword;
    Spacial       :dword;
    Width         :word;
    Height        :word;
    HRes          :dword; //single;
    VRes          :dword;
    DataSize      :dword;
    FrameCount    :word;
    CompNameLen   :byte;
    Compressor    :array [0..18] of AnsiChar;
    ColorDepth    :word;
    ColorTableID  :word;
  end;
  pastsd = ^astsd;
  astsd = packed record
    Version     :byte;
    Flags       :array [0..2] of byte;
    NumEntires  :dword;
    DescSize    :dword;
    CodingName  :array[0..3] of AnsiChar;
    Reserved    :array[0..5] of Byte;
    RefIndex    :Word;
    Reserved_   :array[0..1] of dword;
    ChannelCount:Word;
    SampleSize  :Word;
    Pre_defined :Word;
    Reserved___ :Word;
    Samplerate  :dword;
  end;
  pmvhd = ^mvhd;
  mvhd = packed record
    Version:byte;
    flags:array [0..2] of byte;
    Creation:dword;
    Modification:dword;
    TimeScale:dword;
    Duration:dword;
  end;

procedure ReadAtom(f:THANDLE;var atom:mp4Atom);
begin
  BlockRead(f,atom.len,4);
  if atom.len>0 then
  begin
    BlockRead(f,atom.name,4);
    atom.len:=BSwap(atom.len);
  end
  else
  begin
    atom.name:=0;
    atom.len:=8;
  end;
end;

procedure GetAtom(var p:pbyte;var atom:mp4Atom);
begin
  atom.len:=pdword(p)^;
  inc(p,4);
  if atom.len>0 then
  begin
    atom.name:=pdword(p)^;
    inc(p,4);
    atom.len:=BSwap(atom.len);
  end
  else
  begin
    atom.name:=0;
    atom.len:=8;
  end;
end;

function SetTree(from:mp4Atom;var p:pbyte;path:PAnsiChar;var parent:pbyte):integer;
var
  atom:mp4Atom;
  len:cardinal;
  saved:pbyte;
begin
  saved:=p;
  len:=0;
  dec(from.len,SizeOf(from));
  parent:=p;
  repeat
    GetAtom(p,atom);
    if atom.name=pdword(path)^ then
    begin
      inc(path,4);
      if path^<>#0 then
      begin
        parent:=p;
        inc(path);
        len:=0;
        from.len:=atom.len-SizeOf(atom);
      end
      else
      begin
        result:=atom.len;
        exit;
      end;
    end
    else
    begin
      inc(p,atom.len-SizeOf(atom));
      inc(len,atom.len);
    end;
  until len>=from.len;
  result:=-1;
  p:=saved;
end;

function ReadInt(var p:pbyte):dword;
var
  len:integer;
begin
  len:=pdword(p)^;
  inc(p,4);
  len:=BSwap(len);
  if len>0 then
    inc(p,4); // 'data'
  inc(p,4); // type?
  inc(p,4); // encoding?
  dec(len,8+8);
  if len>4 then len:=4;
  if len=2 then
  begin
    result:=p^*$100;
    inc(p);
    inc(result,p^);
    inc(p);
  end
  else
  begin
    result:=BSwap(pdword(p)^);
    inc(p,4);
  end;
end;

procedure ReadProp(var p:pbyte;var prop:pWideChar);
var
  len:integer;
  ltmp:PAnsiChar;
  c:byte;
begin
  len:=pdword(p)^;
  inc(p,4);
  len:=BSwap(len);
  if len>0 then
    inc(p,4); // 'data'
  inc(p,4); // type?
  inc(p,4); // encoding?
  dec(len,8+8);
  ltmp:=pointer(p);
  inc(p,len);
  c:=p^;
  p^:=0;
  UTF8ToWide(ltmp,prop);
  p^:=c;
end;

function ReadM4A(var Info:tSongInfo):boolean; cdecl;
var
  f:THANDLE;
  atom:mp4Atom;
  cursize,parentsize:integer;
  par,buf,p,pn,finish:pbyte;
  size:integer;
begin
  result:=false;
  f:=Reset(Info.mfile);
  if f=THANDLE(INVALID_HANDLE_VALUE) then
    exit;
  cursize:=0;
  parentsize:=FileSize(f);
  repeat
    ReadAtom(f,atom);
    if atom.name=at_moov then
    begin
      mGetMem(buf,atom.len);
      BlockRead(f,buf^,atom.len);
      p:=buf;
      finish:=pByte(PAnsiChar(p)+atom.len-SizeOf(atom));
      repeat
        GetAtom(p,atom);
        pn:=PByte(PAnsiChar(p)+atom.len-SizeOf(atom));

        if atom.name=at_cmov then
        begin
          size:=SetTree(atom,p,'cmvd',par);
          if size>0 then
          begin
            ZDecompressBuf(PAnsiChar(p)+4,size-SizeOf(mp4Atom),
                pointer(pn),size,BSwap(pdword(p)^));
            mFreeMem(buf);
            buf:=pn;
            p:=buf;
            GetAtom(p,atom); //must be 'moov'
            finish:=PByte(PAnsiChar(p)+atom.len-SizeOf(atom));
            continue;
          end;
        end;

        if atom.name=at_mvhd then
        begin
          if pmvhd(p)^.TimeScale<>0 then
            Info.total:=BSwap(pmvhd(p)^.Duration) div BSwap(pmvhd(p)^.TimeScale);
        end;
        if atom.name=at_udta then
        begin
          size:=SetTree(atom,p,'meta.ilst',par);
          if size>0 then
          begin
            cursize:=0;
            repeat
              GetAtom(p,atom);
              if      atom.name=atm_nam  then ReadProp(p,Info.title)
              else if atom.name=atm_ART  then ReadProp(p,Info.artist)
//            else if atom.name=atm_wrt  then ReadProp(p,Info.title)
              else if atom.name=atm_alb  then ReadProp(p,Info.album)
              else if atom.name=atm_day  then ReadProp(p,Info.year)
              else if atom.name=atm_cmt  then ReadProp(p,Info.comment)
//            else if atom.name=atm_gen  then ReadProp(p,Info.genre)
              else if atom.name=atm_gnre then Info.genre:=GenreName(ReadInt(p)-1)
              else if atom.name=atm_trkn then Info.track:=ReadInt(p)
              else
                inc(p,atom.len-SizeOf(mp4Atom));
              inc(cursize,atom.len);
            until cursize>=size;
          end;
        end;
        // video properties
        if atom.name=at_trak then
        begin
          if SetTree(atom,p,'mdia.minf.vmhd',par)>0 then
          begin
            p:=par;
            if SetTree(atom,p,'stbl.stsd',par)>0 then
            begin
              Info.width :=swap(pstsd(p)^.Width);
              Info.height:=swap(pstsd(p)^.Height);
              Info.codec :=pstsd(p)^.DataFormat;
            end;
          end
          // audio props
          else if SetTree(atom,p,'mdia.minf.smhd',par)>0 then
          begin
            p:=par;
            if SetTree(atom,p,'stbl.stsd',par)>0 then
            begin
              Info.khz:=(BSwap(pastsd(p)^.Samplerate) shr 16) div 1000;
              Info.channels:=swap(pastsd(p)^.ChannelCount);
            end;
            p:=par;
            if SetTree(atom,p,'stsz',par)>0 then
            begin
              if pdword(PAnsiChar(p)+4)^=0 then
                Info.vbr:=1;
            end;
          end;
        end;
        p:=pn;
      until PAnsiChar(p)>=PAnsiChar(finish);
      mFreeMem(buf);
      break;
    end
    else
      Skip(f,atom.len-SizeOf(mp4Atom));
    inc(cursize,atom.len);
  until cursize>=parentsize;
  CloseHandle(f);
end;

var
  LocalFormatLinkM4A,
  LocalFormatLinkMP4,
  LocalFormatLinkMOV,
  LocalFormatLink3GP:twFormat;

procedure InitLink;
begin
  LocalFormatLinkM4A.Next:=FormatLink;

  LocalFormatLinkM4A.This.proc :=@ReadM4A;
  LocalFormatLinkM4A.This.ext  :='M4A';
  LocalFormatLinkM4A.This.flags:=0;

  FormatLink:=@LocalFormatLinkM4A;

  LocalFormatLinkMP4.Next:=FormatLink;

  LocalFormatLinkMP4.This.proc :=@ReadM4A;
  LocalFormatLinkMP4.This.ext  :='MP4';
  LocalFormatLinkMP4.This.flags:=WAT_OPT_VIDEO;

  FormatLink:=@LocalFormatLinkMP4;

  LocalFormatLinkMOV.Next:=FormatLink;

  LocalFormatLinkMOV.This.proc :=@ReadM4A;
  LocalFormatLinkMOV.This.ext  :='MOV';
  LocalFormatLinkMOV.This.flags:=WAT_OPT_VIDEO;

  FormatLink:=@LocalFormatLinkMOV;

  LocalFormatLink3GP.Next:=FormatLink;

  LocalFormatLink3GP.This.proc :=@ReadM4A;
  LocalFormatLink3GP.This.ext  :='3GP';
  LocalFormatLink3GP.This.flags:=WAT_OPT_VIDEO;

  FormatLink:=@LocalFormatLink3GP;

end;

initialization
  InitLink;
end.