{WMA file format} unit fmt_WMA; {$include compilers.inc} interface uses wat_api; function ReadWMA(var Info:tSongInfo):boolean; cdecl; implementation uses windows,common,io,srv_format,utils; const ASF_Header_Object :tGUID='{75B22630-668E-11CF-A6D9-00AA0062CE6C}'; ASF_Header_Extension_Object :tGUID='{5FBF03B5-A92E-11CF-8EE3-00C00C205365}'; ASF_Content_Description_Object :tGUID='{75B22633-668E-11CF-A6D9-00AA0062CE6C}'; ASF_Extended_Content_Description_Object:tGUID='{D2D0A440-E307-11D2-97F0-00A0C95EA850}'; ASF_File_Properties_Object :tGUID='{8CABDCA1-A947-11CF-8EE4-00C00C205365}'; ASF_Stream_Properties_Object :tGUID='{B7DC0791-A9B7-11CF-8EE6-00C00C205365}'; ASF_Metadata_Library_Object :tGUID='{44231C94-9498-49D1-A141-1D134E457054}'; ASF_Audio_Media :tGUID='{F8699E40-5B4D-11CF-A8FD-00805F5C442B}'; ASF_Video_Media :tGUID='{BC19EFC0-5B4D-11CF-A8FD-00805F5C442B}'; type tSize=Int64; function CompareGUID(const guid1,guid2:tGUID):boolean; var i:integer; p1,p2:PAnsiChar; begin p1:=PAnsiChar(@guid1); p2:=PAnsiChar(@guid2); for i:=0 to 15 do begin if p1^<>p2^ then begin result:=false; exit; end; inc(p1); inc(p2); end; result:=true; end; function ReadGUID(var buf:PAnsiChar; var guid:pGUID):dword; var size:tSize; begin guid:=pointer(buf); inc(buf,SizeOf(tGUID)); move(buf^,size,SizeOf(size)); inc(buf,SizeOf(size)); result:=size-SizeOf(tGUID)-SizeOf(size); end; procedure ReadWMATagStr(var dst:pWideChar;ptr:PAnsiChar;alen:word); begin if pword(ptr)^<>0 then begin mGetMem(dst,alen); move(pWideChar(ptr{+2})^,dst^,alen); end; end; function ReadWMATagStr1(var dst:pWideChar;var ptr:PAnsiChar;value:boolean=true):integer; var len,typ:word; begin if value then begin typ:=pword(ptr)^; inc(ptr,2); //value type end else typ:=0; len:=pword(ptr)^; result:=-1; dst:=nil; if len<>0 then begin if typ=0 then begin mGetMem(dst,len); move(PAnsiChar(ptr+2)^,PAnsiChar(dst)^,len); end else begin result:=pword(ptr+2)^; if typ<5 then result:=pword(ptr+4)^*$10000+result; end; end; inc(ptr,len+2); end; procedure ProcessPicture(ptr:PAnsiChar;var Info:tSongInfo); var extw:int64; aSize:dword; begin if Info.cover<>nil then exit; case ptr^ of #0,#3,#4,#6: ; else exit; end; inc(ptr); aSize:=pdword(ptr)^; inc(ptr,4); extw:=GetImageTypeW(nil,pWideChar(ptr)); while pWideChar(ptr)^<>#0 do inc(ptr,2); inc(ptr,2); // mime while pWideChar(ptr)^<>#0 do inc(ptr,2); inc(ptr,2); // descr if extw=0 then extw:=GetImageTypeW(pByte(ptr)); Info.cover:=SaveTemporaryW(ptr,aSize,pWideChar(@extw)); end; procedure ReadHdrExtended(ptr:PAnsiChar;size:dword;var Info:tSongInfo); var buf:PAnsiChar; ls:pWideChar; cnt,tmp:integer; tmpguid:pGUID; lsize:dword; begin inc(ptr,SizeOf(tGUID)+2); size:=pdword(ptr)^; inc(ptr,4); while size>0 do begin if Info.cover<>nil then break; lsize:=ReadGUID(ptr,tmpguid); dec(size,lsize+SizeOf(tGUID)+SizeOf(tSize)); if CompareGUID(tmpguid^,ASF_Metadata_Library_Object) then begin buf:=ptr; cnt:=pdword(buf)^; inc(buf,2); while cnt>0 do begin inc(buf,4); // lang & stream {tmp:=pword (buf)^;} inc(buf,2); // namelen {tmp:=pword (buf)^;} inc(buf,2); // datatype tmp:=pdword(buf)^; inc(buf,4); // datalen ls:=PWideChar(buf); while pWideChar(buf)^<>#0 do inc(buf,2); inc(buf,2); if lstrcmpiw(ls,'WM/Picture')=0 then begin ProcessPicture(buf,Info); inc(buf,tmp); end; dec(cnt); end; end; inc(ptr,lsize); end; end; procedure ReadExtended(ptr:PAnsiChar;size:dword;var Info:tSongInfo); var ls,ls1,ls2:pWideChar; cnt,tmp:integer; begin cnt:=pword(ptr)^; inc(ptr,2); while cnt>0 do begin dec(cnt); ReadWMATagStr1(ls,ptr,false); if lstrcmpiw(ls,'WM/AlbumTitle')=0 then ReadWMATagStr1(Info.album,ptr) else if (Info.lyric=nil) and (lstrcmpiw(ls,'WM/Lyrics')=0) then ReadWMATagStr1(Info.lyric,ptr) else if (Info.lyric=nil) and (lstrcmpiw(ls,'WM/Lyrics_Synchronised')=0) then begin inc(ptr,2+2); inc(ptr); // timestamp type if ptr^=#1 then // lyric begin inc(ptr); tmp:=pdword(ptr)^; inc(ptr,4); mGetMem(ls2,tmp); Info.lyric:=ls2; ls1:=pWideChar(ptr); inc(ptr,tmp); while ls1^<>#0 do // description begin inc(ls1); dec(tmp,SizeOf(WideChar)); end; inc(ls1); dec(tmp,SizeOf(WideChar)); while tmp>0 do begin if PAnsiChar(ls1)^=#$0A then begin inc(PAnsiChar(ls1)); ls2^:=#$0A; dec(tmp); inc(ls2); end; while ls1^<>#0 do begin ls2^:=ls1^; inc(ls2); inc(ls1); dec(tmp,SizeOf(WideChar)); end; inc(ls1,1+2); // terminator + timestamp dec(tmp,SizeOf(WideChar)+4); end; ls2^:=#0; // ptr:=PAnsiChar(ls1); end end else if lstrcmpiw(ls,'WM/Genre')=0 then ReadWMATagStr1(Info.genre,ptr) else if lstrcmpiw(ls,'WM/Year')=0 then begin tmp:=ReadWMATagStr1(Info.year,ptr); if tmp<>-1 then IntToStr(Info.year,tmp); end else if lstrcmpiw(ls,'WM/Track')=0 then begin tmp:=ReadWMATagStr1(ls1,ptr); if tmp=-1 then begin Info.track:=StrToInt(ls1)+1; mFreeMem(ls1); end else Info.track:=tmp; end else if lstrcmpiw(ls,'WM/TrackNumber')=0 then begin tmp:=ReadWMATagStr1(ls1,ptr); if tmp=-1 then begin Info.track:=StrToInt(ls1); mFreeMem(ls1); end else Info.track:=tmp; end else if lstrcmpiw(ls,'WM/Picture')=0 then begin inc(ptr,2); // data type tmp:=pword(ptr)^; inc(ptr,2); ProcessPicture(ptr,Info); inc(ptr,tmp); end else inc(ptr,4+pword(ptr+2)^); mFreeMem(ls); end; end; procedure ReadFileProp(ptr:PAnsiChar;var Info:tSongInfo); type pFileProp = ^tFileProp; tFileProp = packed record FileGUID :tGUID; FileSize :tSize; Creation :tSize; Packets :tSize; Play :tSize; Send :tSize; PreRoll :tSize; Flags :dword; minpacket :dword; maxpacket :dword; maxbitrate:dword; end; begin Info.total:=pFileProp(ptr)^.Play div 10000000; end; procedure ReadStreamProp(ptr:PAnsiChar;size:dword;var Info:tSongInfo); type pAudio = ^tAudio; tAudio=packed record // WAVEFORMATEX Codec :word; Channels :word; Samples :dword; AvgBPS :dword; BlockAlign :word; BitsPerSample:word; size :word; end; pVideo = ^tVideo; tVideo = packed record width :dword; height :dword; reserved:byte; size :word; bitmap :BITMAPINFOHEADER; end; tPrefix = packed record StreamType :tGUID; ECGUID :tGUID; // Error Correction TimeOffset :int64; DataLength :dword; ECDataLength:dword; Flags :word; Reserved :dword; end; var tmpguid:pGUID; begin tmpguid:=pointer(ptr); inc(ptr,SizeOf(tPrefix)); //ofset to Type-Specific Data if CompareGUID(tmpguid^,ASF_Audio_Media) then begin Info.channels:=pAudio(ptr)^.Channels; Info.khz :=pAudio(ptr)^.Samples div 1000; Info.kbps :=(pAudio(ptr)^.AvgBPS*8) div 1000; end else if CompareGUID(tmpguid^,ASF_Video_Media) then begin Info.width :=pVideo(ptr)^.bitmap.biWidth; // pVideo(ptr)^.width Info.height:=pVideo(ptr)^.bitmap.biHeight; // pVideo(ptr)^.height Info.codec :=pVideo(ptr)^.bitmap.biCompression; end end; procedure ReadContent(ptr:PAnsiChar;var Info:tSongInfo); type pContent = ^tContent; tContent = packed record TitleLength :word; AuthorLength :word; CopyrightLength :word; DescriptionLength:word; RatingLength :word; end; var cont:pContent; begin cont:=pointer(ptr); inc(ptr,SizeOf(tContent)); if cont^.TitleLength>0 then //title begin ReadWMATagStr(Info.title,ptr,cont^.TitleLength); inc(ptr,cont^.TitleLength); end; if cont^.AuthorLength>0 then //artist begin ReadWMATagStr(Info.artist,ptr,cont^.AuthorLength); inc(ptr,cont^.AuthorLength); end; inc(ptr,cont^.CopyrightLength); //copyright if cont^.DescriptionLength>0 then //comment ReadWMATagStr(Info.comment,ptr,cont^.DescriptionLength); end; function ReadWMA(var Info:tSongInfo):boolean; cdecl; var f:THANDLE; tmpguid:pGUID; size:int64; buf1,buf2:PAnsiChar; HdrObjects:dword; base:tGUID; begin result:=false; f:=Reset(Info.mfile); if f=THANDLE(INVALID_HANDLE_VALUE) then exit; BlockRead(f,base,SizeOf(tGUID)); if CompareGUID(base,ASF_Header_Object) then begin BlockRead(f,size,SizeOf(size)); dec(size,SizeOf(tGUID)+SizeOf(size)); GetMem(buf1,size); buf2:=buf1; BlockRead(f,buf1^,size); HdrObjects:=pdword(buf2)^; inc(buf2,6); while HdrObjects>0 do begin size:=ReadGUID(buf2,tmpguid); if CompareGUID(tmpguid^,ASF_Content_Description_Object) then ReadContent(buf2,Info) else if CompareGUID(tmpguid^,ASF_Extended_Content_Description_Object) then ReadExtended(buf2,size,Info) else if CompareGUID(tmpguid^,ASF_Header_Extension_Object) then ReadHdrExtended(buf2,size,Info) else if CompareGUID(tmpguid^,ASF_File_Properties_Object) then ReadFileProp(buf2,Info) else if CompareGUID(tmpguid^,ASF_Stream_Properties_Object) then ReadStreamProp(buf2,size,Info); inc(buf2,size); dec(HdrObjects); end; FreeMem(buf1); result:=true; end; CloseHandle(f); end; var LocalFormatLinkWMA, LocalFormatLinkWMV, LocalFormatLinkASF:twFormat; procedure InitLink; begin LocalFormatLinkWMA.Next:=FormatLink; LocalFormatLinkWMA.This.proc :=@ReadWMA; LocalFormatLinkWMA.This.ext :='WMA'; LocalFormatLinkWMA.This.flags:=0; FormatLink:=@LocalFormatLinkWMA; LocalFormatLinkWMV.Next:=FormatLink; LocalFormatLinkWMV.This.proc :=@ReadWMA; LocalFormatLinkWMV.This.ext :='WMV'; LocalFormatLinkWMV.This.flags:=WAT_OPT_VIDEO; FormatLink:=@LocalFormatLinkWMV; LocalFormatLinkASF.Next:=FormatLink; LocalFormatLinkASF.This.proc :=@ReadWMA; LocalFormatLinkASF.This.ext :='ASF'; LocalFormatLinkASF.This.flags:=WAT_OPT_VIDEO; FormatLink:=@LocalFormatLinkASF; end; initialization InitLink; end.