From 864081102a5f252415f41950b3039a896b4ae9c5 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Mon, 8 Oct 2012 18:43:29 +0000 Subject: Awkwars's plugins - welcome to our trunk git-svn-id: http://svn.miranda-ng.org/main/trunk@1822 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/Watrack/formats/fmt_aac.pas | 93 ++++++ plugins/Watrack/formats/fmt_ape.pas | 137 +++++++++ plugins/Watrack/formats/fmt_avi.pas | 295 ++++++++++++++++++ plugins/Watrack/formats/fmt_dummy.pas | 46 +++ plugins/Watrack/formats/fmt_flv.pas | 334 +++++++++++++++++++++ plugins/Watrack/formats/fmt_m4a.pas | 378 +++++++++++++++++++++++ plugins/Watrack/formats/fmt_mkv.pas | 235 +++++++++++++++ plugins/Watrack/formats/fmt_mp3.pas | 460 ++++++++++++++++++++++++++++ plugins/Watrack/formats/fmt_mpc.pas | 90 ++++++ plugins/Watrack/formats/fmt_ofr.pas | 74 +++++ plugins/Watrack/formats/fmt_ogg.pas | 522 ++++++++++++++++++++++++++++++++ plugins/Watrack/formats/fmt_real.pas | 335 +++++++++++++++++++++ plugins/Watrack/formats/fmt_tta.pas | 65 ++++ plugins/Watrack/formats/fmt_wav.pas | 146 +++++++++ plugins/Watrack/formats/fmt_wma.pas | 438 +++++++++++++++++++++++++++ plugins/Watrack/formats/tag_apev2.inc | 124 ++++++++ plugins/Watrack/formats/tag_id3v1.inc | 175 +++++++++++ plugins/Watrack/formats/tag_id3v2.inc | 545 ++++++++++++++++++++++++++++++++++ plugins/Watrack/formats/tags.pas | 21 ++ 19 files changed, 4513 insertions(+) create mode 100644 plugins/Watrack/formats/fmt_aac.pas create mode 100644 plugins/Watrack/formats/fmt_ape.pas create mode 100644 plugins/Watrack/formats/fmt_avi.pas create mode 100644 plugins/Watrack/formats/fmt_dummy.pas create mode 100644 plugins/Watrack/formats/fmt_flv.pas create mode 100644 plugins/Watrack/formats/fmt_m4a.pas create mode 100644 plugins/Watrack/formats/fmt_mkv.pas create mode 100644 plugins/Watrack/formats/fmt_mp3.pas create mode 100644 plugins/Watrack/formats/fmt_mpc.pas create mode 100644 plugins/Watrack/formats/fmt_ofr.pas create mode 100644 plugins/Watrack/formats/fmt_ogg.pas create mode 100644 plugins/Watrack/formats/fmt_real.pas create mode 100644 plugins/Watrack/formats/fmt_tta.pas create mode 100644 plugins/Watrack/formats/fmt_wav.pas create mode 100644 plugins/Watrack/formats/fmt_wma.pas create mode 100644 plugins/Watrack/formats/tag_apev2.inc create mode 100644 plugins/Watrack/formats/tag_id3v1.inc create mode 100644 plugins/Watrack/formats/tag_id3v2.inc create mode 100644 plugins/Watrack/formats/tags.pas (limited to 'plugins/Watrack/formats') diff --git a/plugins/Watrack/formats/fmt_aac.pas b/plugins/Watrack/formats/fmt_aac.pas new file mode 100644 index 0000000000..db8e03b153 --- /dev/null +++ b/plugins/Watrack/formats/fmt_aac.pas @@ -0,0 +1,93 @@ +{AAC file process} +unit fmt_AAC; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadAAC(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +const + SampleRates:array [0..15] of dword = ( + 96000,88200,64000,48000,44100,32000,24000,22050, + 16000,12000,11025,8000,0,0,0,0); + +procedure ReadADIFheader(f:THANDLE;var Info:tSongInfo); +var + buf:array [0..29] of byte; + bs,sf_idx,skip:dword; +begin + BlockRead(f,buf,30); + if (buf[0] and $80)<>0 then + skip:=9 + else + skip:=0; + Info.kbps:=(((buf[0+skip] and $0F) shl 19)+(buf[1+skip] shl 11)+ + (buf[2+skip] shl 3)+{or}((buf[3+skip] and $E0){shr 5})) div 1000; + bs:=buf[0+skip] and $10; + if bs=0 then + sf_idx:=(buf[7+skip] and $78) shr 3 + else + sf_idx:=((buf[4+skip] and $07) shl 1)+((buf[5+skip] and $80) shr 7); + Info.khz:=SampleRates[sf_idx]; +end; + +procedure ReadADTSheader(var Info:tSongInfo;sign:dword); +type + l2b=record + b:array [0..3] of byte; + end; +var + sr_idx:integer; +begin + Info.channels:=((l2b(sign).b[2] and $01) shl 2)+ + ((l2b(sign).b[3] and $C0) shr 6); + sr_idx:=(l2b(sign).b[2] and $3C) shr 2; + Info.khz:=SampleRates[sr_idx] div 1000; +end; + +function ReadAAC(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + sign:dword; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + BlockRead(f,sign,4); + Info.khz:=44; + Info.kbps:=128; + Info.channels:=2; + if (lobyte(sign)=$FF) and ((hibyte(sign) and $F6)=$F0) then + ReadADTSheader(Info,sign) + else if sign=$46494441 then // 'ADIF' + ReadADIFheader(f,Info); + + ReadAPEv2(f,Info); + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLink:twFormat; + +procedure InitLink; +begin + LocalFormatLink.Next:=FormatLink; + + LocalFormatLink.This.proc :=@ReadAAC; + LocalFormatLink.This.ext :='AAC'; + LocalFormatLink.This.flags:=0; + + FormatLink:=@LocalFormatLink; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_ape.pas b/plugins/Watrack/formats/fmt_ape.pas new file mode 100644 index 0000000000..fbabdac19c --- /dev/null +++ b/plugins/Watrack/formats/fmt_ape.pas @@ -0,0 +1,137 @@ +{APE file} +unit fmt_APE; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadAPE(var Info:tSongInfo):boolean; cdecl; + +implementation + +uses windows,common,io,tags,srv_format; + +const + defID = $2043414D; +type +(* Old Version ? + tMonkeyHeader = record + ID :dword; { Always "MAC " } + VersionID :word; { Version number * 1000 (3.91 = 3910) } + CompressionID :word; { Compression level code } + Flags :word; { Any format flags } + Channels :word; { Number of channels } + SampleRate :dword; { Sample rate (hz) } + HeaderBytes :dword; { Header length (without header ID) } + TerminatingBytes:dword; { Extended data } + Frames :dword; { Number of frames in the file } + FinalSamples :dword; { Number of samples in the final frame } + PeakLevel :dword; { Peak level (if stored) } + SeekElements :dword; { Number of seek elements (if stored) } + end; +*) + tMonkeyHeader = packed record + ID :dword; // should equal 'MAC ' + VersionID :dword; // version number * 1000 (3.81 = 3810) + nDescriptorBytes :dword; // descriptor bytes + nHeaderBytes :dword; // APEHeader bytes + nSeekTableBytes :dword; // bytes of the seek table + nHeaderDataBytes :dword; // header data bytes (from original file) + nFrameDataBytes :dword; // bytes of APE frame data + nFrameDataBytesHi:dword; // the high order number of APE frame data bytes + nTerminatingBytes:dword; // the terminating data of the file (w/o tag data) + cFileMD5:array [0..15] of byte; + end; +type + tAPEHeader = packed record + nCompressionLevel:word; // the compression level + nFormatFlags :word; // any format flags (for future use) + nBlocksPerFrame :dword; // the number of audio blocks in one frame + nFinalFrameBlocks:dword; // the number of audio blocks in the final frame + nTotalFrames :dword; // the total number of frames + nBitsPerSample :word; // the bits per sample (typically 16) + nChannels :word; // the number of channels (1 or 2) + nSampleRate :dword; // the sample rate (typically 44100) + end; + +const + MONKEY_COMPRESSION_FAST = 1000; // Fast (poor) + MONKEY_COMPRESSION_NORMAL = 2000; // Normal (good) + MONKEY_COMPRESSION_HIGH = 3000; // High (very good) + MONKEY_COMPRESSION_EXTRA_HIGH = 4000; // Extra high (best) +const + MONKEY_FLAG_8_BIT = 1; // Audio 8-bit + MONKEY_FLAG_CRC = 2; // New CRC32 error detection + MONKEY_FLAG_PEAK_LEVEL = 4; // Peak level stored + MONKEY_FLAG_24_BIT = 8; // Audio 24-bit + MONKEY_FLAG_SEEK_ELEMENTS = 16; // Number of seek elements stored + MONKEY_FLAG_WAV_NOT_STORED = 32; // WAV header not stored + +function ReadAPE(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + hdr:tMonkeyHeader; + hdr1:tAPEHeader; + blocks:dword; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + BlockRead(f,hdr ,SizeOf(tMonkeyHeader)); + BlockRead(f,hdr1,SizeOf(tAPEHeader)); //hdr.nHeaderBytes + if hdr1.nTotalFrames=0 then + blocks:=0 + else + blocks:=(hdr1.nTotalFrames-1)*hdr1.nBlocksPerFrame+hdr1.nFinalFrameBlocks; + Info.khz :=hdr1.nSampleRate div 1000; + if hdr1.nSampleRate<>0 then + Info.total :=blocks div hdr1.nSampleRate; + Info.channels:=hdr1.nChannels; +// Info.kbps:=Info.khz*deep*Info.channels/1152 +// Info.kbps:=(blocks*Info.channels*hdr1.nBitsPerSample) div (Info.total*8000); +// Info.kbps :=((hdr1.nBitsPerSample div 8)*hdr1.nSamplerate) div 1000; +(* Old version ? + if (hdr.ID<>DefID) or (hdr.SampleRate=0) or (hdr.Channels=0) then + exit; + if (hdr.VersionID>=3900) or + ((hdr.VersionID>=3800) and + (hdr.CompressionID=MONKEY_COMPRESSION_EXTRA_HIGH)) then + tmp:=73728 + else + tmp:=9216; + tmp:=(hdr.Frames-1)*tmp+hdr.FinalSamples; + Info.total :=tmp div hdr.SampleRate; + Info.khz :=hdr.SampleRate div 1000; + Info.channels:=hdr.Channels; + + Info.kbps:=tmp;//samples + if (hdr.Flags and MONKEY_FLAG_8_BIT)<>0 then tmp:=8 + else if (hdr.Flags and MONKEY_FLAG_24_BIT)<>0 then tmp:=24 + else tmp:=16; + Info.kbps:=((Info.kbps*tmp*hdr.Channels) div Info.Total) div 1000; +*) + ReadAPEv2(f,Info); + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLink:twFormat; + +procedure InitLink; +begin + LocalFormatLink.Next:=FormatLink; + + LocalFormatLink.This.proc :=@ReadAPE; + LocalFormatLink.This.ext :='APE'; + LocalFormatLink.This.flags:=0; + + FormatLink:=@LocalFormatLink; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_avi.pas b/plugins/Watrack/formats/fmt_avi.pas new file mode 100644 index 0000000000..3646236cf7 --- /dev/null +++ b/plugins/Watrack/formats/fmt_avi.pas @@ -0,0 +1,295 @@ +{AVI file format} +unit fmt_AVI; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadAVI(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,srv_format; + +type + FOURCC = array [0..3] of AnsiChar; +type + tChunkHeader = packed record + case byte of + 0: (Lo,Hi:dword); {Common} + 1: (ID:FOURCC; {RIFF} + Length:dword); + end; + +const + sRIFF = $46464952; + sLIST = $5453494C; + savih = $68697661; { avi header } + sstrf = $66727473; { stream format } + sstrh = $68727473; { stream header } +const + smovi = $69766F6D; {movi list type} +const + svids = $73646976; {video} + sauds = $73647561; {audio} +const + sIART = $54524149; {director} + sICMT = $544D4349; {comment} + sICRD = $44524349; {creation date} + sIGNR = $524E4749; {genre} + sINAM = $4D414E49; {title} + sIPRT = $54525049; {part} + sIPRO = $4F525049; {produced by} + sISBJ = $4A425349; {subject description} + +type + tWaveFormatEx = packed record + wFormatTag :word; + nChannels :word; + nSamplesPerSec :dword; + nAvgBytesPerSec:dword; + nBlockAlign :word; + wBitsPerSample :word; + cbSize :word; + + Reserved1 :word; + wID :word; + fwFlags :word; + nBlockSize :word; + nFramesPerBlock:word; + nCodecDelay :word; {ms} + end; + +type + tMainAVIHeader = packed record {avih} + dwMicroSecPerFrame :dword; + dwMaxBytesPerSec :dword; + dwPaddingGranularity :dword; + dwFlags :dword; + dwTotalFrames :dword; { # frames in first movi list} + dwInitialFrames :dword; + dwStreams :dword; + dwSuggestedBufferSize:dword; + dwWidth :dword; + dwHeight :dword; + dwScale :dword; + dwRate :dword; + dwStart :dword; + dwLength :dword; + end; + +type + TAVIExtHeader = packed record {dmlh} + dwGrandFrames:dword; {total number of frames in the file} + dwFuture:array[0..60] of dword; + end; + +type + tAVIStreamHeader = packed record {strh} + fccType :FOURCC; {vids|auds} + fccHandler :FOURCC; + dwFlags :dword; + wPriority :word; + wLanguage :word; + dwInitialFrames :dword; + dwScale :dword; + dwRate :dword; + dwStart :dword; + dwLength :dword; + dwSuggestedBufferSize:dword; + dwQuality :dword; + dwSampleSize :dword; + rcFrame: packed record + left :word; + top :word; + right :word; + bottom:word; + end; + end; + +var + vora:dword; + +procedure Skip(f:THANDLE;bytes:dword); +var + i:dword; +begin + i:=FilePos(f); + if bytes=0 then + begin + if odd(i) then + Seek(f,i+1); + end + else + Seek(f,i+bytes+(bytes mod 2)); +end; + +procedure ProcessVideoFormat(f:THANDLE;Size:dword;var Info:tSongInfo); +var + bih:BitmapInfoHeader; +begin + BlockRead(f,bih,SizeOf(bih)); + Info.codec :=bih.biCompression; + Info.width :=bih.biWidth; + Info.height:=bih.biHeight; + Skip(f,Size-SizeOf(bih)); +end; + +procedure ProcessAudioFormat(f:THANDLE;Size:dword;var Info:tSongInfo); +{WAVEFORMATEX or PCMWAVEFORMAT} +var + AF:tWaveFormatEx; +begin + BlockRead(f,AF,SizeOf(AF)); + Info.channels:=AF.nChannels; + Info.khz :=AF.nSamplesPerSec div 1000; + Info.kbps :=(AF.nAvgBytesPerSec*8) div 1000; + Skip(f,Size-SizeOf(AF)); +end; + +function ProcessASH(f:THANDLE;var Info:tSongInfo):dword; +var + ASH:tAVIStreamHeader; +begin + BlockRead(f,ASH,SizeOf(ASH)); + with ASH do + begin + if dword(fccType)=svids then + begin + if ASH.dwScale<>0 then + Info.fps:=(ASH.dwRate*100) div ASH.dwScale; + if Info.fps<>0 then + Info.total:=(ASH.dwLength*100) div Info.fps; + ProcessASH:=1 + end + else if dword(fccType)=sauds then ProcessASH:=2 + else ProcessASH:=0; + end; +end; + +procedure ProcessMAH(f:THANDLE;var Info:tSongInfo); +var + MAH:tMainAVIHeader; +begin + BlockRead(f,MAH,SizeOf(MAH)); +// Info.width:=MAH.dwWidth; +// Info.height:=MAH.dwHeight; +// Info.fps:=100000000 div MAH.dwMicroSecPerFrame; +end; + +function ProcessChunk(f:THANDLE;var Info:tSongInfo):dword; +var + lTotal:dword; + Chunk:tChunkHeader; + cType:FOURCC; + ls:PAnsiChar; +begin + Skip(f,0); + if (BlockRead(f,Chunk,SizeOF(Chunk))=0) or (Chunk.Lo=0) then + begin + result:=FileSize(f); + Seek(f,FileSize(f)); + exit; + end; + result:=Chunk.Length+SizeOf(Chunk); + case Chunk.Lo of + sRIFF,sLIST: begin + BlockRead(f,cType,SizeOf(cType)); + if dword(cType)=smovi then + Skip(f,Chunk.Length-SizeOf(cType)) // result:=FileSize(f) + else + begin + lTotal:=SizeOf(FOURCC); + while lTotal0 do + begin + + len:=Reverse(pWord(ptr)^,2); inc(ptr,2); // key name length + key:=ptr; inc(ptr,len); // key name + + result:=ProcessValue(ptr,key,Info); + + if result=0 then break + else if result<0 then + begin + result:=1; + break; + end; + dec(i); + end; + end; + + // Object end marker, UI8[3]=0,0,9 + 9: begin + result:=-1; + inc(ptr,3); + end; + + 10: // array, 4 bytes - num of elements, elements + begin + i:=pdword(ptr)^; inc(ptr,4); + i:=Reverse(i,4); + while i>0 do + begin + result:=ProcessValue(ptr,nil,Info); + if result=0 then exit + else if result<0 then + begin + result:=1; + break; + end; + dec(i); + end; + end; + + // Date, Double + UI16 (UTC) + 11: begin + inc(ptr,8); + inc(ptr,2); + end; + + // LongString, 4 bytes = len, len - string + 12: begin + i:=pdword(ptr)^; inc(ptr,4); + i:=Reverse(i,4); + + inc(ptr,i); + end; + + end; +end; + +function ReadFLV(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + codec:transform; + FLVHdr:tFLVHeader; + StrmHdr:tFLVStream; + i,len:integer; + buf,pp,p,endbuf:PAnsiChar; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + + mGetMem(buf,BufSize); + endbuf:=buf+BlockRead(f,buf^,BufSize); + p:=buf; + CloseHandle(f); + move(p^,FLVHdr,SizeOf(tFLVHeader)); + if (FLVHdr.Signature[0]='F') and (FLVHdr.Signature[1]='L') and + (FLVHdr.Signature[2]='V') and (FLVHdr.Version=1) then + begin + inc(p,SizeOf(tFLVHeader)); + result:=true; + while (p0) do + begin + move(p^,StrmHdr,SizeOf(tFLVStream)); + inc(p,SizeOf(tFLVStream)); + len:=(StrmHdr.BodyLength[0] shl 16)+(StrmHdr.BodyLength[1] shl 8)+ + StrmHdr.BodyLength[2]; + pp:=p; + case StrmHdr.TagType of + + FLV_AUDIO: begin + Info.channels:=(ord(p^) and 1)+1; + // samplesize is (S_Byte and 2) shr 1 = 8 or 16 + case (ord(p^) and $C) shr 2 of + 0: Info.khz:=5; + 1: Info.khz:=11; + 2: Info.khz:=22; + 3: Info.khz:=44; + end; + FLVHdr.flags:=FLVHdr.flags and not 4; + end; + + FLV_VIDEO: begin + case ord(p^) and $0F of + 2: codec.txt:='H263'; + 3: codec.txt:='Scrn'; + 4,5: codec.txt:='VP6 '; + 6: codec.txt:='Src2'; + 7: codec.txt:='AVC '; + end; + Info.codec:=codec.num; + FLVHdr.flags:=FLVHdr.flags and not 1; + end; + + FLV_META: begin + if (StrmHdr.TagType and $40)=0 then // not encripted + begin + if pByte(p)^=2 then // string + begin + Inc(p); + i:=Reverse(pWord(p)^,2); inc(p,2); + if StrCmp(p,'onMetaData',i)=0 then // Metadata processing start + begin + inc(p,i); + ProcessValue(p,nil,Info); // metadata, no need key name, our info + // checking for video + if Info.codec<>0 then + FLVHdr.flags:=FLVHdr.flags and not 1; + // checking for audio + if (Info.khz<>0) and (Info.channels<>0) then + FLVHdr.flags:=FLVHdr.flags and not 4; + // break; // if metainfo is enough + end; + end; + end; + end; + + end; + p:=pp+len; + end; + end; + mFreeMem(buf); +end; + +var + LocalFormatLink:twFormat; + +procedure InitLink; +begin + LocalFormatLink.Next:=FormatLink; + + LocalFormatLink.This.proc :=@ReadFLV; + LocalFormatLink.This.ext :='FLV'; + LocalFormatLink.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLink; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_m4a.pas b/plugins/Watrack/formats/fmt_m4a.pas new file mode 100644 index 0000000000..1bfcf2309a --- /dev/null +++ b/plugins/Watrack/formats/fmt_m4a.pas @@ -0,0 +1,378 @@ +{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. diff --git a/plugins/Watrack/formats/fmt_mkv.pas b/plugins/Watrack/formats/fmt_mkv.pas new file mode 100644 index 0000000000..df01a82f59 --- /dev/null +++ b/plugins/Watrack/formats/fmt_mkv.pas @@ -0,0 +1,235 @@ +{MKV file process} +unit fmt_MKV; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadMKV(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,srv_format; + +const + idEBML = $A45DFA3; + idSegment = $8538067; + idInfo = $549A966; + idTimecodeScale = $AD7B1; + idDuration = $489; + idTracks = $654AE6B; + idTrackEntry = $2E; + idTrackType = $3; + idCodecPrivate = $23A2; + idName = $136E; + idVideo = $60; + idAudio = $61; + idPixelWidth = $30; + idPixelHeight = $3A; + idDefaultDuration = $3E383; + idSamplingFrequency = $35; + idChannels = $1F; + idCluster = $F43B675; + +function GetNumber(var ptr:pbyte):int64; +begin + if (ptr^ and $80)<>0 then + result:=ptr^ and $7F + else if (ptr^ and $40)<>0 then + begin + result:=(ptr^ and $3F) shl 8; inc(ptr); + result:=result+ptr^; + end + else if (ptr^ and $20)<>0 then + begin + result:=(ptr^ and $1F) shl 16; inc(ptr); + result:=result+(ptr^ shl 8); inc(ptr); + result:=result+ptr^; + end + else if (ptr^ and $10)<>0 then + begin + result:=(ptr^ and $0F) shl 24; inc(ptr); + result:=result+(ptr^ shl 16); inc(ptr); + result:=result+(ptr^ shl 8); inc(ptr); + result:=result+ptr^; + end + else if (ptr^ and $08)<>0 then + begin + result:=int64(ptr^ and $07) shl 32; inc(ptr); + result:=result+(ptr^ shl 24); inc(ptr); + result:=result+(ptr^ shl 16); inc(ptr); + result:=result+(ptr^ shl 8); inc(ptr); + result:=result+ptr^; + end + else if (ptr^ and $04)<>0 then + begin + result:=int64(ptr^ and $03) shl 40; inc(ptr); + result:=result+(int64(ptr^) shl 32); inc(ptr); + result:=result+(ptr^ shl 24); inc(ptr); + result:=result+(ptr^ shl 16); inc(ptr); + result:=result+(ptr^ shl 8); inc(ptr); + result:=result+ptr^; + end + else if (ptr^ and $02)<>0 then + begin + result:=int64(ptr^ and $01) shl 48; inc(ptr); + result:=result+(int64(ptr^) shl 40); inc(ptr); + result:=result+(int64(ptr^) shl 32); inc(ptr); + result:=result+(ptr^ shl 24); inc(ptr); + result:=result+(ptr^ shl 16); inc(ptr); + result:=result+(ptr^ shl 8); inc(ptr); + result:=result+ptr^; + end + else if (ptr^ and $01)<>0 then + begin + inc(ptr); + result:= (int64(ptr^) shl 48); inc(ptr); + result:=result+(int64(ptr^) shl 40); inc(ptr); + result:=result+(int64(ptr^) shl 32); inc(ptr); + result:=result+(ptr^ shl 24); inc(ptr); + result:=result+(ptr^ shl 16); inc(ptr); + result:=result+(ptr^ shl 8); inc(ptr); + result:=result+ptr^; + end + else + result:=0; + inc(ptr); +end; + +function GetInt(var ptr:pbyte;len:integer):int64; +var + i:integer; +begin + result:=0; + for i:=0 to len-1 do + begin + result:=(result shl 8)+ptr^; + inc(ptr); + end; +end; + +function GetFloat(var ptr:pbyte):single; +var + i:dword; + f:single absolute i; +begin + i:=( ptr^ shl 24); inc(ptr); + inc(i,ptr^ shl 16); inc(ptr); + inc(i,ptr^ shl 8); inc(ptr); + inc(i,ptr^); inc(ptr); + result:=f; +end; + +function ReadMKV(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + id,len:integer; + ptr:pByte; + buf:array [0..16383] of byte; + trktype,scale:integer; + ls:PAnsiChar; + tmp:integer; + lTotal:real; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + BlockRead(f,buf,SizeOf(buf)); + ptr:=@buf; + trktype:=0; + lTotal:=0; + scale:=1; + repeat + id :=GetNumber(ptr); + len:=GetNumber(ptr); + if id=idEBML then // just check + begin + result:=true; + inc(ptr,len); + end + else if id=idCluster then + break + else if id=idSegment then // do nothing + else if id=idInfo then // do nothing + else if id=idTracks then // do nothing + else if id=idTrackEntry then // do nothing + else if id=idVideo then // do nothing + else if id=idAudio then // do nothing + else if id=idTimecodeScale then + scale:=GetInt(ptr,len) + else if id=idDuration then + lTotal:=GetFloat(ptr) + else if id=idTrackType then + begin + tmp:=trktype; + trktype:=GetInt(ptr,len); // 1-video,2-audio + if (tmp=2) and (trktype=2) then + break; + end + else if (id=idCodecPrivate) and (trktype=1) then + begin + inc(ptr,16); + // 4 - ? (40=size included?) + // width,height + // 2 - ? + // 2 - bitperpixel? + Info.codec:=ptr^; inc(ptr); + Info.codec:=Info.codec+(ptr^ shl 8 ); inc(ptr); + Info.codec:=Info.codec+(ptr^ shl 16); inc(ptr); + Info.codec:=Info.codec+(ptr^ shl 24); + inc(ptr,len-19); + end + else if (id=idName) and (Info.title=NIL) then + begin + mGetMem(ls,len+1); + move(ptr^,ls^,len); + ls[len]:=#0; + AnsiToWide(ls,Info.title); + mFreeMem(ls); + inc(ptr,len); + end + else if id=idPixelWidth then + Info.width:=GetInt(ptr,len) + else if id=idPixelHeight then + Info.height:=GetInt(ptr,len) + else if id=idDefaultDuration then + begin + if trktype=1 then + begin + Info.fps:=(GetInt(ptr,len) div 1000); + if Info.fps<>0 then + Info.fps:=100000000 div Info.fps; + end + else + begin + GetInt(ptr,len); + end; + end + else if id=idSamplingFrequency then + Info.khz:=round(GetFloat(ptr)) div 1000 + else if id=idChannels then + Info.channels:=GetInt(ptr,len) + else + inc(ptr,len); + until pAnsiChar(ptr)>=(PAnsiChar(@buf)+SizeOf(buf)); + Info.total:=trunc(lTotal/(1000000000/scale)); + CloseHandle(f); +end; + +var + LocalFormatLink:twFormat; + +procedure InitLink; +begin + LocalFormatLink.Next:=FormatLink; + + LocalFormatLink.This.proc :=@ReadMKV; + LocalFormatLink.This.ext :='MKV'; + LocalFormatLink.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLink; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_mp3.pas b/plugins/Watrack/formats/fmt_mp3.pas new file mode 100644 index 0000000000..5d6c94745a --- /dev/null +++ b/plugins/Watrack/formats/fmt_mp3.pas @@ -0,0 +1,460 @@ +{MP3 file process} +unit fmt_MP3; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadMP3(var Info:tSongInfo):boolean; cdecl; +function ReadMPG(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +const + ScanSize = 16*1024; // block size to search header +type + tMP3FrameHdr = record + Version :integer; + Layer :cardinal; + Bitrate :cardinal; + Samplerate:cardinal; + Channel :cardinal; //Stereo, Joint, Dual, Mono + Length :cardinal; + CRC :boolean; + _Private :boolean; + Copyright :boolean; + Original :boolean; + isVBR :boolean; + end; + +// ........ ........ 111..... 11111111 syncword +// ........ ........ ...xx... ........ version (11=1, 10=2, 00=2.5) +// ........ ........ .....xx. ........ layer (01=III, 10=II, 11=I) +// ........ ........ .......x ........ crc (0=yes, 1=no) +// xx...... ........ ........ ........ mode (00=stereo, 10=joint, 01=dual, 11=mono) +// ..xx.... ........ ........ ........ mode ext (only for joint stereo) +// ....x... ........ ........ ........ copyright (0=no, 1=yes) +// .....x.. ........ ........ ........ original (0=orig, 1=copy) +// ......xx ........ ........ ........ emphasis (not 10) +// ........ xxxx.... ........ ........ bitrate (not 0000 nor 1111) +// ........ ....xx.. ........ ........ sampling rate (not 11) +// ........ ......x. ........ ........ padded (0=no, 1=yes) +// ........ .......x ........ ........ private bit + +const + btable:array [0..1,0..2,0..15] of word = ( + ( //MPEG 2 & 2.5 + (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0), //Layer III + (0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0), //Layer II + (0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,0) //Layer I + ),( //MPEG 1 + (0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,0), //Layer III + (0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,0), //Layer II + (0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,0) //Layer I + ) + ); + stable: array [0..3,0..2] of word = ( + (32000, 16000, 8000), //MPEG 2.5 + ( 0, 0, 0), //reserved + (22050, 24000, 16000), //MPEG 2 + (44100, 48000, 32000) //MPEG 1 + ); + +procedure TranslateFrameHdr(const block:array of byte;var MP3FrameHdr:tMP3FrameHdr); +begin + FillChar(MP3FrameHdr,SizeOf(MP3FrameHdr),0); + if block[0]=$FF then + begin + with MP3FrameHdr do + begin + Version :=(block[1] and $18) shr 3; + Layer :=(block[1] and $06) shr 1; + CRC :=not Odd(block[1]); + Bitrate :=btable[Version and 1][Layer-1][block[2] shr 4]; + Samplerate:=stable[Version][(block[2] and $0C) shr 2]; + _Private :=odd(block[2]); + Channel :=block[3] shr 6; + Copyright :=((block[3] and $08) shr 3)<>0; + Original :=((block[3] and $04) shr 2)<>0; + end; + end; +end; + +procedure CheckVBR(f:THANDLE; var hdr:tMP3FrameHdr); +var + pos,apos:cardinal; + sign:longint; + frames:longint; +begin + pos:=FilePos(f); + hdr.Length:=0; + if hdr.Version=3 then + begin + if hdr.Channel=3 then + apos:=17 + else + apos:=32; + end + else if hdr.Channel=3 then + apos:=9 + else + apos:=17; + Skip(f,apos); + BlockRead(f,sign,4); + hdr.isVBR:=sign=$676E6958; //Xing +//calculate length + if hdr.isVBR then + begin + if hdr.Samplerate<>0 then + begin +// Seek(f,pos+36); + BlockRead(f,sign,4); + if (sign and $01000000)<>0 then + begin + BlockRead(f,frames,4); + frames:=BSwap(frames); + hdr.Length:=Round((1152/hdr.Samplerate)*frames/(4-hdr.Version)); //! + end; + end; + end + else if hdr.Bitrate<>0 then + hdr.Length:=((8*(FileSize(f)-(pos-4))) div 1000) div hdr.Bitrate; +end; + +function SearchStart(f:THANDLE; var l:array of byte):Boolean; +var + CurPos:longint; + Buf:array [0..ScanSize] of byte; + i,j:integer; +begin + CurPos:=FilePos(f)-4; + Seek(f,CurPos); + j:=BlockRead(f,Buf,ScanSize); + i:=0; + while i$F0) then + begin + Seek(f,CurPos+i); + BlockRead(f,l,4); + result:=true; + Exit; + end; + inc(i); + end; + result:=false; +end; + +function ReadMP3(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + l:array [0..3] of byte; + hdr:tMP3FrameHdr; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + BlockRead(f,l,4); +// if l[0]<>$FF then + if not SearchStart(f,l) then + Exit; + TranslateFrameHdr(l,hdr); + CheckVBR(f,hdr); + Info.kbps :=hdr.Bitrate; + Info.khz :=hdr.Samplerate div 1000; + Info.total:=hdr.Length; + if hdr.Channel=3 then + Info.channels:=1 + else + Info.channels:=2; + Info.vbr:=ord(hdr.isVBR); + + ReadAPEv2(f,Info); + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +const + mpgAudio = 1; + mpgVideo = 2; + mpgVersion = 4; + +type + l2b=array [0..3] of byte; + +function ReadDWord(var p:pAnsiChar;endptr:pAnsiChar):integer; +begin + if (p+4)0; + if i=1 then + begin + result:=ReadByte(p,endptr) or $100; + exit; + end; + end; + until p>=endptr; + result:=0; +end; + +const + BufSize = 256*1024; + +function ReadMPG(var Info:tSongInfo):boolean; cdecl; +var + endptr,buf,p:PAnsiChar; + f:THANDLE; + BlockType:integer; + l:dword; + w:word; + b:byte; + flag:integer; + version,layer:integer; +// vbitrate:integer; +// FrmCnt:integer; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + flag:=mpgAudio+mpgVideo+mpgVersion; + + mGetMem(buf,BufSize); + endptr:=buf+BlockRead(f,buf^,BufSize); + CloseHandle(f); + p:=buf; +// FrmCnt:=0; + while (flag<>0) and (p0 then + begin + flag:=flag and not mpgVersion; + if (ReadByte(p,endptr) and $C0)=$40 then + Info.codec:=$3247504D // MPG2 + else + Info.codec:=$4745504D; // MPEG + end; + end; + $1B3: begin // Video + if (flag and mpgVideo)<>0 then + begin + l:=ReadDWord(p,endptr); + flag:=flag and not mpgVideo; + Info.width :=((l2b(l)[1] and $F0) shr 4)+(l2b(l)[0] shl 4); + Info.height:=((l2b(l)[1] and $0F) shl 8)+l2b(l)[2]; + case l2b(l)[3] and $F of + 1: Info.fps:=2397; + 2: Info.fps:=2400; + 3: Info.fps:=2500; + 4: Info.fps:=2997; + 5: Info.fps:=3000; + 6: Info.fps:=5000; + 7: Info.fps:=5994; + 8: Info.fps:=6000; + end; +// BlockRead(f,l,4); +// vbitrate:=(l2b(l)[0] shl 10)+(l2b(l)[1] shl 2)+(l2b(l)[2] shr 6); + end; + end; + 0,$1B7,$1B9: break; +{ + $1E0: begin + BlockRead(f,w,2); + w:=swap(w); + mGetMem(buf,w); + BlockRead(f,buf^,w); + p:=buf; + for l:=0 to w-4 do + begin + if pdword(p)^=$00010000 then + begin + inc(FrmCnt); + inc(p,4); + end + else + inc(p); + end; + mFreeMem(buf); + end; +} + $1C0: begin // audio + w:=swap(ReadWord(p,endptr)); + if flag and mpgAudio<>0 then + begin + flag:=flag and not mpgAudio; + b:=ReadByte(p,endptr); + dec(w); + if (b and $C0)=$80 then + begin + b:=ReadByte(p,endptr); + l:=ReadByte(p,endptr); + dec(w,2); + if (b and $80)<>0 then + begin + inc(p,5); + dec(w,5); + dec(l,5); + if (b and $40)<>0 then + begin + inc(p,5); + dec(w,5); + dec(l,5); + end; + end; + if l>0 then + begin + inc(p,l); + dec(w,l); + end; + end + else + begin + while (b and $80)<>0 do + begin + dec(w); + if w=0 then break; + b:=ReadByte(p,endptr); + end; + if (b and $40)<>0 then + begin + inc(p); + b:=ReadByte(p,endptr); + dec(w,2); + end; + if (b and $20)<>0 then + begin + inc(p,4); + dec(w,4); + if (b and $10)<>0 then + begin + inc(p,5); + dec(w,5); + end; + end; + end; + l:=ReadDWord(p,endptr); + version:=(l2b(l)[1] and $18) shr 3; + layer :=(l2b(l)[1] and $06) shr 1; + Info.kbps :=btable[version and 1][layer-1][l2b(l)[2] shr 4]; + Info.khz :=(stable[version][(l2b(l)[2] and $0C) shr 2]) div 1000; + Info.channels:=l2b(l)[3] shr 6; + if Info.channels=3 then + Info.channels:=1 + else + Info.channels:=2; +// if w>0 then inc(p,w); + end; +// else + inc(p,w); + end; +{ + $1B5: begin + BlockRead(f,l,4); + if (l2b(l)[0] and $F0)=$10 then + begin + vbitrate:=vbitrate+ + ((((l2b(l)[2] and $1F) shl 7)+(l2b(l)[3] shr 1)) shl 18); + end; + end; +} +{ + $1BD: begin + end; +} + $1C1..$1DF, // audio +//?? $1E0, + $1E1..$1EF, // video + $1BB{,$1BD},$1BE,$1BF: begin // system,private,padding,private + inc(p,swap(ReadWord(p,endptr))); + end; + end; + end; +// vbitrate:=(vbitrate*400) div 1000; +// Info.total:=(FrmCnt*100) div Info.fps; + mFreeMem(buf); + result:=true; +end; + +var + LocalFormatLinkMP3, + LocalFormatLinkMPG, + LocalFormatLinkMPEG:twFormat; + +procedure InitLink; +begin + LocalFormatLinkMP3.Next:=FormatLink; + + LocalFormatLinkMP3.This.proc :=@ReadMP3; + LocalFormatLinkMP3.This.ext :='MP3'; + LocalFormatLinkMP3.This.flags:=0; + + FormatLink:=@LocalFormatLinkMP3; + + LocalFormatLinkMPG.Next:=FormatLink; + + LocalFormatLinkMPG.This.proc :=@ReadMPG; + LocalFormatLinkMPG.This.ext :='MPG'; + LocalFormatLinkMPG.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLinkMPG; + + LocalFormatLinkMPEG.Next:=FormatLink; + + LocalFormatLinkMPEG.This.proc :=@ReadMPG; + LocalFormatLinkMPEG.This.ext :='MPEG'; + LocalFormatLinkMPEG.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLinkMPEG; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_mpc.pas b/plugins/Watrack/formats/fmt_mpc.pas new file mode 100644 index 0000000000..7d8671dde6 --- /dev/null +++ b/plugins/Watrack/formats/fmt_mpc.pas @@ -0,0 +1,90 @@ +{MPC file format} +unit fmt_MPC; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadMPC(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +const + DefID = $002B504D;// 'MP+' + +function ReadMPC(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + tmp:array [0..5] of dword; + samples,TotalFrames:dword; + lastframe:dword; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + + BlockRead(f,tmp,SizeOf(tmp)); + if ((tmp[0] and $FFFFFF)=DefID) and + (((tmp[0] shr 24) and $0F)>=7) then // sv7-sv8 + begin + Info.kbps:=0; + if (tmp[2] and 2)<>0 then + Info.channels:=2 + else + Info.channels:=1; + case (tmp[2] and $3000) shr 12 of //C000-14? + 00: Info.khz:=44100; + 01: Info.khz:=48000; + 02: Info.khz:=37800; + 03: Info.khz:=32000; + end; + lastframe:=(tmp[5] and $FFF) shr 1; + samples:=tmp[1]*1152+lastframe; + end + else + begin //4-6 + if not ((tmp[0] and $1FFF) and $3FF) in [4..6] then + exit; + Info.khz:=44100; + Info.kbps:=tmp[1] and $1F; + if ((tmp[0] and $1FFF) and $3FF)=4 then + TotalFrames:=loword(tmp[2]) + else + TotalFrames:=tmp[2]; + samples:=TotalFrames*1152; + end; + + if Info.khz<>0 then + Info.total:=samples div Info.khz; + Info.khz:=Info.khz div 1000; + if (Info.kbps=0) and (samples<>0) then +// if fs=samples*channels*deep/8 then kbps=khz*deep*channels/1152 +// Info.kbps:=(Info.khz*8)*taginfo.FileSize/1152/samples; + + Info.kbps:=(Info.khz div 8)*FileSize(f) div samples; //!! + ReadAPEv2(f,Info); + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLink:twFormat; + +procedure InitLink; +begin + LocalFormatLink.Next:=FormatLink; + + LocalFormatLink.This.proc :=@ReadMPC; + LocalFormatLink.This.ext :='MPC'; + LocalFormatLink.This.flags:=0; + + FormatLink:=@LocalFormatLink; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_ofr.pas b/plugins/Watrack/formats/fmt_ofr.pas new file mode 100644 index 0000000000..73d58b68ff --- /dev/null +++ b/plugins/Watrack/formats/fmt_ofr.pas @@ -0,0 +1,74 @@ +{OFR file} +unit fmt_OFR; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadOFR(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +type + tMain = packed record + ID :dword; // 'OFR ' + Size :dword; //15 + SamplesLo :dword; + SamplesHi :word; + SampleType :byte; + ChannelsMap:byte; + Samplerate :dword; + Encoder :word; + Compression:byte; + end; + +function ReadOFR(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + Hdr:tMain; + Samples:int64; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + BlockRead(f,Hdr,SizeOf(Hdr)); + Samples:=Hdr.SamplesLo+Hdr.SamplesHi*$10000; + Info.channels:=Hdr.ChannelsMap+1; + Info.khz :=Hdr.Samplerate div 1000; + Info.total :=(Samples div Info.channels)*Info.khz; + + ReadAPEv2(f,Info); + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLinkOFR, + LocalFormatLinkOFS:twFormat; + +procedure InitLink; +begin + LocalFormatLinkOFR.Next:=FormatLink; + + LocalFormatLinkOFR.This.proc :=@ReadOFR; + LocalFormatLinkOFR.This.ext :='OFR'; + LocalFormatLinkOFR.This.flags:=0; + + FormatLink:=@LocalFormatLinkOFR; + + LocalFormatLinkOFS.Next:=FormatLink; + + LocalFormatLinkOFS.This.proc :=@ReadOFR; + LocalFormatLinkOFS.This.ext :='OFS'; + LocalFormatLinkOFS.This.flags:=0; + + FormatLink:=@LocalFormatLinkOFS; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_ogg.pas b/plugins/Watrack/formats/fmt_ogg.pas new file mode 100644 index 0000000000..4b05b80c2c --- /dev/null +++ b/plugins/Watrack/formats/fmt_ogg.pas @@ -0,0 +1,522 @@ +{OGG, SPX and FLAC file formats} +unit fmt_OGG; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadOGG(var Info:tSongInfo):boolean; cdecl; +function ReadSPX(var Info:tSongInfo):boolean; cdecl; +function ReadfLaC(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format,base64,utils; + +const + OGGSign = $5367674F; //OggS +const + SpeexID = 'Speex '; +type + tSPEXHeader = packed record + speex_string :array [0..7] of AnsiChar; + speex_version :array [0..19] of AnsiChar; + speex_version_id:dword; + header_size :dword; //sizeof(tSPEXHeader) + rate :dword; + mode :dword; + bitstrm_version :dword; + nb_channels :dword; + bitrate :dword; + frame_size :dword; + vbr :dword; + fpp :dword; //frames_per_packet + extra_headers :dword; + reserved1 :dword; + reserved2 :dword; + end; +type + pOGGHdr = ^tOGGHdr; + tOGGHdr = packed record + ID :dword; + Version :byte; + HdrType :byte; + Granule :Int64; // absolute position + BitStrmSN:dword; + PageSeqN :dword; + CRC :dword; + PageSegs :byte; + end; +const + strmOGG = 1; + strmOGM = 2; +const + VideoD = $65646976; + VideoW = $006F; + VorbisD = $62726F76; + VorbisW = $7369; +type + tOGMInfo = packed record + padding :word; // 0 + codec :dword; + size :dword; + time_unit :int64; // 1/10000000 sec + samples_per_unit:int64; // fps = 10000000*spu/time_unit + default_len :dword; // 1 + buffersize :dword; + bit_per_sample :dword; + width :dword; + height :dword; + dummy :dword; // 0 + end; + +//const VorbisStream:array [0..5] of byte = ($76,$6F,$72,$62,$69,$73); // 'vorbis' + +type + tOGGInfo = packed record + version :dword; + Channels :byte; + samplerate:dword; + maxkbps :dword; + nominal :dword; + minkbps :dword; + BlockSizes:byte; + dummy :byte; + end; + +//--------------- fLaC section --------------- +const + fLaCSign = $43614C66; //fLaC +{ +0 : STREAMINFO +1 : PADDING +2 : APPLICATION +3 : SEEKTABLE +4 : VORBIS_COMMENT +5 : CUESHEET +} +type + MetaHdr = packed record + blocktype:byte; + blocklen:array [0..2] of byte; + end; +type + StreamInfo = packed record + MinBlockSize:word; + MaxBlocksize:word; + MinFrameSize:array [0..2] of byte; + MaxFrameSize:array [0..2] of byte; + heap:array [0..7] of byte; + MD5:array [0..15] of byte; + end; + +procedure OGGGetComment(ptr:PAnsiChar;size:integer;var Info:tSongInfo); +var + clen,alen,len,values:dword; + ls:PAnsiChar; + value:PAnsiChar; + cover:pByte; + ext:dword; + extw:int64; + c:AnsiChar; +begin + inc(ptr,pdword(ptr)^+4); //vendor + values:=pdword(ptr)^; inc(ptr,4); + ext:=0; + cover:=nil; + clen:=0; + while values>0 do + begin + len:=pdword(ptr)^; + if len>cardinal(size) then + break; + dec(size,len); + inc(ptr,4); + ls:=ptr; + c:=ls[len]; + ls[len]:=#0; + alen:=StrScan(ls,'=')-ls+1; + if alen>0 then + begin + ls[alen-1]:=#0; + value:=ls+alen; + + if (Info.title =nil) and (lstrcmpia(ls,'TITLE' )=0) then UTF8ToWide(value,Info.title) + else if (Info.artist =nil) and (lstrcmpia(ls,'ARTIST' )=0) then UTF8ToWide(value,Info.artist) + else if (Info.album =nil) and (lstrcmpia(ls,'ALBUM' )=0) then UTF8ToWide(value,Info.album) + else if (Info.genre =nil) and (lstrcmpia(ls,'GENRE' )=0) then UTF8ToWide(value,Info.genre) + else if (Info.year =nil) and (lstrcmpia(ls,'DATE' )=0) then UTF8ToWide(value,Info.year) + else if (Info.comment=nil) and (lstrcmpia(ls,'COMMENT')=0) then UTF8ToWide(value,Info.comment) + else if (Info.lyric =nil) and (lstrcmpia(ls,'LYRICS' )=0) then UTF8ToWide(value,Info.lyric) + + else if (Info.track=0) and (lstrcmpia(ls,'TRACKNUMBER')=0) then Info.track:=StrToInt(value) + + else if (cover=nil) and (lstrcmpia(ls,'COVERART')=0) then clen:=Base64Decode(value,cover) + else if lstrcmpia(ls,'COVERARTMIME')=0 then ext:=GetImageType(nil,value); + end; + dec(values); + inc(ptr,len); + ptr^:=c; + end; + + if cover<>nil then + begin + if ext=0 then + ext:=GetImageType(cover); + if ext<>0 then + begin + FastAnsiToWideBuf(PAnsiChar(@ext),pWideChar(@extw)); + Info.cover:=SaveTemporaryW(cover,clen,PWideChar(@extw)); + end; + mFreeMem(cover); + end; + +end; + +function CalcSize(num:integer;var arr:array of byte):integer; +var + i:integer; +begin + result:=0; + for i:=0 to num-1 do + begin + inc(result,arr[i]); + if arr[i]<$FF then break; + end; +end; + +function ReadSPX(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + OGGHdr:tOGGHdr; + SPXHdr:tSPEXHeader; + buf:array [0..255] of byte; + ptr:PAnsiChar; + size:integer; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + BlockRead(f,OGGHdr,SizeOf(tOGGHdr)); + Skip(f,OGGHdr.PageSegs); + if OGGHdr.ID=OGGSign then + begin + BlockRead(f,SPXHdr,SizeOf(SPXHdr)); + if SPXHdr.speex_string<>SpeexID then + begin + CloseHandle(f); + exit; + end; + Info.khz:=SPXHdr.rate div 1000; + Info.vbr:=SPXHdr.vbr; + if integer(SPXHdr.bitrate)<>-1 then + Info.kbps:=SPXHdr.bitrate div 1000; + + BlockRead(f,OGGHdr,SizeOf(tOGGHdr)); + BlockRead(f,buf,OGGHdr.PageSegs); + size:=CalcSize(OGGHdr.PageSegs,buf); + GetMem(ptr,size+1); + BlockRead(f,ptr^,size); + OGGGetComment(ptr,size,Info); + FreeMem(ptr); + + result:=true; + end; + CloseHandle(f); +end; + +function Compare(const sign:array of byte):integer; +type + conv=packed record + d:dword;w:word; + end; +var + p:^conv; +begin + p:=@sign; + if (p^.d=VideoD) and (p^.w=VideoW) then + result:=strmOGM + else if (p^.d=VorbisD) and (p^.w=VorbisW) then + result:=strmOGG + else + result:=0; +end; + +function ReadOGG(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + OGGHdr:tOGGHdr; + tmp:packed record + paktype:byte; + sign:array [0..5] of byte; + end; + OGGInfo:tOGGInfo; + OGMInfo:tOGMInfo; + fpos:dword; + SPXHdr:tSPEXHeader; + i,j:integer; + DataIndex:integer; + buf:array [0..255] of byte; + fsize:dword; + done:integer; + ptr:PAnsiChar; + size:integer; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + tmp.paktype:=0; + fsize:=FileSize(f); + done:=0; + while (done<>3) and (tmp.paktype<>5) and (FilePos(f)0 then + Info.kbps :=OGGInfo.nominal div 1000; + Info.khz :=OGGInfo.samplerate; + Info.channels:=OGGInfo.Channels; + done:=done or 1; + end; + strmOGM: begin + BlockRead(f,OGMInfo,SizeOf(OGMInfo)); + Info.codec :=OGMInfo.codec; + Info.fps :=round(((10000000*OGMInfo.samples_per_unit) / OGMInfo.time_unit)*100); + Info.width :=OGMInfo.width; + Info.height:=OGMInfo.height; + done:=done or 1; + end; + end; + end + else if tmp.paktype=ORD('S') then //maybe SPX + begin + Seek(f,fpos); + BlockRead(f,SPXHdr,SizeOf(SPXHdr)); + if SPXHdr.speex_string<>SpeexID then + begin + CloseHandle(f); + exit; + end; + Info.khz:=SPXHdr.rate div 1000; + if integer(SPXHdr.bitrate)<>-1 then + Info.kbps:=SPXHdr.bitrate div 1000; + done:=done or 1; + end + else if tmp.paktype=3 then + begin + GetMem(ptr,size+1); + BlockRead(f,ptr^,size); + OGGGetComment(ptr,size,Info); + FreeMem(ptr); + done:=done or 2; + end + else + continue; + result:=true; + end; + end; + end; + // try to get length + DataIndex:=FileSize(f)-10; + for i:=1 to 50 do + begin + dec(DataIndex,SizeOf(buf)-10); + Seek(f,DataIndex); + BlockRead(f,buf,SizeOf(buf)); + { Get number of PCM samples from last Ogg packet header } + j:=SizeOf(buf)-10; + repeat + if pOGGHdr(@buf[j])^.ID=OGGSign then + begin + if j>(SizeOf(buf)-SizeOf(tOGGHdr)) then + begin + Seek(f,DataIndex+j); + BlockRead(f,buf,SizeOf(tOGGHdr)); + j:=0; + end; + if Info.fps>0 then + begin + Info.total:=(pOGGHdr(@buf[j])^.Granule*100) div Info.fps; + end + else if Info.khz<>0 then + Info.total:=pOGGHdr(@buf[j])^.Granule div Info.khz; + break; + end; + dec(j); + until j=0; + if Info.total>0 then break; + end; + Info.khz:=Info.khz div 1000; + CloseHandle(f); +end; + +function ReadfLaC(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + data64:int64; + hdr:MetaHdr; + frm:StreamInfo; + id:dword; + flag:integer; + size:dword; + buf,ptr:PAnsiChar; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + BlockRead(f,id,SizeOf(id)); + if id=fLaCSign then + begin + flag:=0; + repeat + BlockRead(f,hdr,SizeOf(hdr)); + size:=hdr.blocklen[2]+(hdr.blocklen[1] shl 8)+(hdr.blocklen[0] shl 16); + case (hdr.blocktype and $7F) of + 0: begin + if flag=0 then + begin + BlockRead(f,frm,SizeOf(frm)); + //samplerate eg.44100 + Info.khz:=((frm.heap[0] shl 12)+(frm.heap[1] shl 4)+(frm.heap[2] shr 4)); + Info.channels:=((frm.heap[2] and $F) shr 1)+1; + //bits per SAMPLE now + Info.kbps:=(frm.heap[2] and 1) shl 4+(frm.heap[3] shr 4)+1; + data64:=((frm.heap[3] and $F) shl 32)+(frm.heap[4] shl 24)+ + (frm.heap[5] shl 16)+(frm.heap[6] shl 8)+frm.heap[7]; + + if (data64<>0) and (Info.khz<>0) then + Info.total:=data64 div Info.khz; + Info.kbps:=Info.kbps*8; +Info.kbps:=trunc(FileSize(f)*8/1000); + Info.khz:=Info.khz div 1000; + flag:=1; + end; + end; + 4: begin + GetMem(buf,size); + BlockRead(f,buf^,size); + OGGGetComment(buf,size,Info); + FreeMem(buf); + end; + 6: begin + if Info.cover=nil then + begin + GetMem(buf,size); + BlockRead(f,buf^,size); + ptr:=buf; + id:=BSwap(pdword(ptr)^); + case id of + 0,3,4,6: begin + inc(ptr,4); + id:=BSwap(pdword(ptr)^); // mime size + inc(ptr,4); + flag:=GetImageType(nil,ptr); + inc(ptr,id+4*5); // width, height, depth etc. + id:=BSwap(pdword(ptr)^); // image size + inc(ptr,4); + if flag=0 then + flag:=GetImageType(pByte(ptr)); + FastAnsiToWideBuf(PAnsiChar(@flag),pWideChar(@data64)); + Info.cover:=SaveTemporaryW(ptr,id,PWideChar(@data64)); + end; + end; + FreeMem(buf); + end + else + Skip(f,size); + end + else + begin + if (hdr.blocktype and $80)<>0 then + break; + Skip(f,size); + end; + end; + until (hdr.blocktype and $80)<>0; + end; + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLinkOGG, + LocalFormatLinkOGA, + LocalFormatLinkOGM, + LocalFormatLinkSPX, + LocalFormatLinkFLA, + LocalFormatLinkFLAC:twFormat; + +procedure InitLink; +begin + LocalFormatLinkOGG.Next:=FormatLink; + + LocalFormatLinkOGG.This.proc :=@ReadOGG; + LocalFormatLinkOGG.This.ext :='OGG'; + LocalFormatLinkOGG.This.flags:=0; + + FormatLink:=@LocalFormatLinkOGG; + + LocalFormatLinkOGA.Next:=FormatLink; + + LocalFormatLinkOGA.This.proc :=@ReadOGG; + LocalFormatLinkOGA.This.ext :='OGA'; + LocalFormatLinkOGA.This.flags:=0; + + FormatLink:=@LocalFormatLinkOGA; + + LocalFormatLinkOGM.Next:=FormatLink; + + LocalFormatLinkOGM.This.proc :=@ReadOGG; + LocalFormatLinkOGM.This.ext :='OGM'; + LocalFormatLinkOGM.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLinkOGM; + + LocalFormatLinkSPX.Next:=FormatLink; + + LocalFormatLinkSPX.This.proc :=@ReadSPX; + LocalFormatLinkSPX.This.ext :='SPX'; + LocalFormatLinkSPX.This.flags:=0; + + FormatLink:=@LocalFormatLinkSPX; + + LocalFormatLinkFLA.Next:=FormatLink; + + LocalFormatLinkFLA.This.proc :=@ReadfLaC; + LocalFormatLinkFLA.This.ext :='FLA'; + LocalFormatLinkFLA.This.flags:=0; + + FormatLink:=@LocalFormatLinkFLA; + + LocalFormatLinkFLAC.Next:=FormatLink; + + LocalFormatLinkFLAC.This.proc :=@ReadfLaC; + LocalFormatLinkFLAC.This.ext :='FLAC'; + LocalFormatLinkFLAC.This.flags:=0; + + FormatLink:=@LocalFormatLinkFLAC; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_real.pas b/plugins/Watrack/formats/fmt_real.pas new file mode 100644 index 0000000000..8d5f5bf72d --- /dev/null +++ b/plugins/Watrack/formats/fmt_real.pas @@ -0,0 +1,335 @@ +{Real file} +unit fmt_Real; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadReal(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +const + blk_RMF = $464D522E; // '.RMF' + blkPROP = $504F5250; // 'PROP' + blkCONT = $544E4F43; // 'CONT' - content + blkMDPR = $5250444D; // 'MDPR' + blkDATA = $41544144; // 'DATA' + blkINDX = $58444E49; // 'INDX' + blkRMMD = $444D4D52; // 'RMMD' - comment block + blkRMJD = $444A4D52; // 'RMJD' + blkRMJE = $454A4D52; // 'RMJE' +type + tChunk = packed record + ID:dword; + Len:dword; //with Chunk; + end; + +type + pPROP = ^tPROP; + tPROP = packed record + w1 :word; + l1,l2 :dword; + l3,l4 :dword; + un1 :dword; // or 2+2 + filetotal :dword; // msec + l5 :dword; + InfoDataSize:dword; + Infosize :dword; + w2 :word; // always 2 ? + w :word; // chunks+1? + end; + +procedure SkipStr(var p:PAnsiChar;alen:integer); +var + len:integer; +begin + if alen=2 then + len:=(ord(p[0]) shl 8)+ord(p[1]) + else + len:=ord(p[0]); + inc(p,alen); +// if len>0 then + inc(p,len); +end; + +function ReadStr(var p:PAnsiChar;alen:integer):PAnsiChar; +var + len:integer; +begin + if alen=2 then + len:=(ord(p[0]) shl 8)+ord(p[1]) + else + len:=ord(p[0]); + inc(p,alen); + if len>0 then + begin + mGetMem(result,len+1); + move(p^,result^,len); + result[len]:=#0; + inc(p,len); + end + else + result:=nil; +end; + +function GetWord(var p:PAnsiChar):word; +begin + result:=(ord(p[0]) shl 8)+ord(p[1]); + inc(p,2); +end; + +function GetLong(var p:PAnsiChar):dword; +begin + result:=(ord(p[0]) shl 24)+(ord(p[1]) shl 16)+(ord(p[2]) shl 8)+ord(p[3]); + inc(p,4); +end; + +function ReadReal(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + chunk:tChunk; + p,buf:PAnsiChar; + ls:PAnsiChar; + ver:integer; + fsize:cardinal; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + fsize:=FileSize(f); + while FilePos(f)SizeOf(chunk) then // channels-1: ofs=$0A + break; + Skip(f,chunk.Len-SizeOf(chunk)); + end; + end; + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLinkRM, + LocalFormatLinkRA, + LocalFormatLinkRAM:twFormat; + +procedure InitLink; +begin + LocalFormatLinkRM.Next:=FormatLink; + + LocalFormatLinkRM.This.proc :=@ReadReal; + LocalFormatLinkRM.This.ext :='RM'; + LocalFormatLinkRM.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLinkRM; + + LocalFormatLinkRA.Next:=FormatLink; + + LocalFormatLinkRA.This.proc :=@ReadReal; + LocalFormatLinkRA.This.ext :='RA'; + LocalFormatLinkRA.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLinkRA; + + LocalFormatLinkRAM.Next:=FormatLink; + + LocalFormatLinkRAM.This.proc :=@ReadReal; + LocalFormatLinkRAM.This.ext :='RAM'; + LocalFormatLinkRAM.This.flags:=WAT_OPT_VIDEO; + + FormatLink:=@LocalFormatLinkRAM; +end; + +initialization + InitLink; + +end. diff --git a/plugins/Watrack/formats/fmt_tta.pas b/plugins/Watrack/formats/fmt_tta.pas new file mode 100644 index 0000000000..c13b329fe2 --- /dev/null +++ b/plugins/Watrack/formats/fmt_tta.pas @@ -0,0 +1,65 @@ +{TTA file} +unit fmt_TTA; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadTTA(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +const + TTA1_SIGN = $31415454; +type + tTTAHeader = packed record + id :dword; + format :word; + channels :word; + bitspersample:word; + samplerate :dword; + datalength :dword; + crc32 :dword; + end; + +function ReadTTA(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + hdr:tTTAHeader; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + ReadID3v2(f,Info); + BlockRead(f,hdr,SizeOf(tTTAHeader)); + if hdr.id<>TTA1_SIGN then + exit; + Info.channels:=hdr.channels; + Info.khz :=hdr.samplerate; + Info.kbps :=hdr.bitspersample div 1000; //!! + if hdr.samplerate<>0 then + Info.total:=hdr.datalength div hdr.samplerate; + ReadID3v1(f,Info); + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLink:twFormat; + +procedure InitLink; +begin + LocalFormatLink.Next:=FormatLink; + + LocalFormatLink.This.proc :=@ReadTTA; + LocalFormatLink.This.ext :='TTA'; + LocalFormatLink.This.flags:=0; + + FormatLink:=@LocalFormatLink; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_wav.pas b/plugins/Watrack/formats/fmt_wav.pas new file mode 100644 index 0000000000..98d8e18fb8 --- /dev/null +++ b/plugins/Watrack/formats/fmt_wav.pas @@ -0,0 +1,146 @@ +{WAV processing} +unit fmt_WAV; +{$include compilers.inc} + +interface +uses wat_api; + +function ReadWAV(var Info:tSongInfo):boolean; cdecl; + +implementation +uses windows,common,io,tags,srv_format; + +const + wavRIFF = $46464952; + wavWAVE = $45564157; + wavfmt_ = $20746D66; + wavfact = $74636166; + wavdata = $61746164; +type + tWAVChunk = packed record + id :dword; + size:dword; + end; +type + tWAVFormatChunk = packed record + Codec :word; + Channels :word; + SampleRate :dword; + AvgBPS :dword; + BlockAlign :word; + BitsPerSample:word; + end; + +const + WavPackID = $6B707677; +type +// ckID :dword; // "wvpk" +// ckSize :dword; // size of entire frame (minus 8, of course) + tWavPackHeader = packed record + version :word; // 0x403 for now + track_no :byte; // track number (0 if not used, like now) + index_no :byte; // track sub-index (0 if not used, like now) + total_samples:dword; // for entire file (-1 if unknown) + block_index :dword; // index of first sample in block (to file begin) + block_samples:dword; // # samples in This block + flags :dword; // various flags for id and decoding + crc :dword; // crc for actual decoded data + end; + +function ReadWAV(var Info:tSongInfo):boolean; cdecl; +var + f:THANDLE; + chunk:tWAVChunk; + fmtchunk:tWAVFormatChunk; + tmp:dword; + WPH:tWavPackHeader; + fsize:dword; +begin + result:=false; + f:=Reset(Info.mfile); + if f=THANDLE(INVALID_HANDLE_VALUE) then + exit; + BlockRead(f,chunk,SizeOf(chunk)); + if chunk.id=WavPackID then + begin + BlockRead(f,WPH,SizeOf(tWavPackHeader)); + BlockRead(f,tmp,2); //!! $1621 33,22 + BlockRead(f,chunk,SizeOf(chunk)); + end + else + begin + WPH.version:=0; + integer(WPH.total_samples):=-1; + end; + if chunk.id<>wavRIFF then + exit; + BlockRead(f,chunk,SizeOf(dword)); + if chunk.id<>wavWAVE then + exit; + BlockRead(f,chunk,SizeOf(chunk)); + if chunk.id<>wavfmt_ then + exit; + BlockRead(f,fmtchunk,SizeOf(tWAVFormatChunk)); + Info.channels:=fmtchunk.Channels; + Info.khz :=fmtchunk.SampleRate div 1000; + if chunk.size>SizeOf(tWAVFormatChunk) then + Skip(f,chunk.size-SizeOf(tWAVFormatChunk)); + fsize:=FileSize(f); + while FilePos(f)0 then + begin + ReadAPEv2(f,Info); + ReadID3v1(f,Info); + end; + if integer(WPH.total_samples)=-1 then + if (fmtchunk.BitsPerSample<>0) and (fmtchunk.Channels<>0) then + WPH.total_samples:=(tmp*8) div (fmtchunk.Channels*fmtchunk.BitsPerSample); + if fmtchunk.SampleRate<>0 then + Info.total:= WPH.total_samples div fmtchunk.SampleRate; + if Info.total<>0 then + Info.kbps:=tmp*8 div Info.total div 1000; + + CloseHandle(f); + result:=true; +end; + +var + LocalFormatLinkWAV, + LocalFormatLinkWV:twFormat; + +procedure InitLink; +begin + LocalFormatLinkWAV.Next:=FormatLink; + + LocalFormatLinkWAV.This.proc :=@ReadWAV; + LocalFormatLinkWAV.This.ext :='WAV'; + LocalFormatLinkWAV.This.flags:=0; + + FormatLink:=@LocalFormatLinkWAV; + + LocalFormatLinkWV.Next:=FormatLink; + + LocalFormatLinkWV.This.proc :=@ReadWAV; + LocalFormatLinkWV.This.ext :='WV'; + LocalFormatLinkWV.This.flags:=0; + + FormatLink:=@LocalFormatLinkWV; +end; + +initialization + InitLink; +end. diff --git a/plugins/Watrack/formats/fmt_wma.pas b/plugins/Watrack/formats/fmt_wma.pas new file mode 100644 index 0000000000..ed575147ac --- /dev/null +++ b/plugins/Watrack/formats/fmt_wma.pas @@ -0,0 +1,438 @@ +{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; + Prefix = 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(Prefix)); //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. diff --git a/plugins/Watrack/formats/tag_apev2.inc b/plugins/Watrack/formats/tag_apev2.inc new file mode 100644 index 0000000000..34ab7f2ad7 --- /dev/null +++ b/plugins/Watrack/formats/tag_apev2.inc @@ -0,0 +1,124 @@ +{APE tag} +{$IFDEF Interface} +function ReadAPEv2(buf:PAnsiChar;var Info:tSongInfo;count:integer=0):longint; overload; +function ReadAPEv2(f:THANDLE;var Info:tSongInfo):longint; overload; +{$ELSE} +const + APESign = 'APETAGEX'; +type + pAPEHeader = ^tAPEHeader; + tAPEHeader = packed record + ID:array [0..7] of AnsiChar; + Version:dword; + TagSize:dword; //footer + all items + ItemCount:dword; + TagFlags:dword; + Reserved:array [0..7] of byte; + end; + +procedure ReadAPEValue(const buf:PAnsiChar;var dst:pWideChar;ver:dword); +begin + if dst=nil then + if ver>1000 then + UTF8ToWide(buf,dst) + else + AnsiToWide(buf,dst); +end; + +function ReadAPEv2(buf:PAnsiChar;var Info:tSongInfo;count:integer=0):longint; +var + APE:pAPEHeader; + len:integer; + ptr,key:PAnsiChar; + flag:dword; + cf:THANDLE; + buf0,buf1:array [0..MAX_PATH-1] of AnsiChar; + b:AnsiChar; +// extw:array [0..7] of WideChar; +begin + result:=0; + APE:=pointer(buf); + if APE.ID=APESign then + begin + inc(buf,SizeOf(tAPEHeader)); + count:=APE.ItemCount; + end; + while count>0 do + begin + len :=pdword(buf)^; inc(buf,4); + flag:=pdword(buf)^; inc(buf,4); + key:=buf; + while buf^<>#0 do inc(buf); inc(buf); + + ptr:=buf+len; + b:=ptr^; + ptr^:=#0; + if lstrcmpia(key,'TITLE' )=0 then ReadAPEValue(buf,Info.title ,APE.Version) + else if lstrcmpia(key,'ARTIST' )=0 then ReadAPEValue(buf,Info.artist ,APE.Version) + else if lstrcmpia(key,'ALBUM' )=0 then ReadAPEValue(buf,Info.album ,APE.Version) + else if lstrcmpia(key,'COMMENT')=0 then ReadAPEValue(buf,Info.comment,APE.Version) + else if lstrcmpia(key,'GENRE' )=0 then ReadAPEValue(buf,Info.genre ,APE.Version) + else if lstrcmpia(key,'YEAR' )=0 then ReadAPEValue(buf,Info.year ,APE.Version) + else if lstrcmpia(key,'TRACK' )=0 then if Info.track=0 then Info.track:=StrToInt(buf) + else if lstrcmpia(key,'LYRICS' )=0 then ReadAPEValue(buf,Info.lyric ,APE.Version) + //!! must preserve multipart lyric + else if (lstrcmpia(key,'Cover Art (Front)')=0) or + (lstrcmpia(key,'Cover Art (Back)' )=0) or + (lstrcmpia(key,'APIC' )=0) then + begin + if Info.cover=nil then + begin + while buf^<>#0 do inc(buf); inc(buf); // point to data now + flag:=GetImageType(pByte(buf)); + if flag<>0 then + begin +{ + FastAnsiToWideBuf(PAnsiChar(@flag),pWideChar(@extw)); + Info.Cover:=SaveTemporaryW(buf,ptr-buf,PWideChar(@extw)); +} + GetTempPathA(SizeOf(buf0),buf0); + GetTempFileNameA(buf0,'wat',GetCurrentTime,buf1); + ChangeExt(buf1,PAnsiChar(@flag)); + + cf:=ReWrite(PAnsiChar(@buf1)); + BlockWrite(cf,buf^,ptr-buf); + CloseHandle(cf); + AnsiToWide(PAnsiChar(@buf1),Info.cover); + end; + end; + end; + ptr^:=b; + buf:=ptr; + dec(count); + end; +end; + +function ReadAPEv2(f:THANDLE;var Info:tSongInfo):longint; +var + APE:tAPEHeader; + buf:PAnsiChar; + fpos:dword; + TagID:array [1..3] of AnsiChar; +begin + result:=0; + fpos:=FileSize(f); + Seek(f,fpos-SizeOf(TID3v1Tag)); + BlockRead(f,TagID,3); + if TagID=TAG1Sign then + dec(fpos,SizeOf(TID3v1Tag)); + Seek(f,fpos-SizeOf(APE)); + BlockRead(f,APE,SizeOf(APE)); + // footer must be copied as header + if APE.ID=APESign then + begin + if (APE.TagFlags and $20000000)=0 then //Footer + begin + Seek(f,fpos-APE.TagSize{-SizeOf(APE)});// without header but with footer + GetMem(buf,APE.TagSize); + BlockRead(f,buf^,APE.TagSize); + result:=ReadAPEv2(buf,Info,APE.ItemCount); + FreeMem(buf); + end; + end; +end; +{$ENDIF} diff --git a/plugins/Watrack/formats/tag_id3v1.inc b/plugins/Watrack/formats/tag_id3v1.inc new file mode 100644 index 0000000000..bd1db906bb --- /dev/null +++ b/plugins/Watrack/formats/tag_id3v1.inc @@ -0,0 +1,175 @@ +{ID3v1 tag} +{$IFDEF Interface} +const + TAG1Sign = 'TAG'; +type + TID3v1Tag = packed record + ID: array [0..2] of AnsiChar; + Title: array [0..29] of AnsiChar; + Artist: array [0..29] of AnsiChar; + Album: array [0..29] of AnsiChar; + Year: array [0..3] of AnsiChar; + Comment: array [0..28] of AnsiChar; + Track: byte; + Genre: byte; + end; + +function ReadID3v1(f:THANDLE; var Info:tSongInfo):longint; +{$ELSE} +const + Lyric1End = 'LYRICSEND'; + LyricStart = 'LYRICSBEGIN'; + Lyric2End = 'LYRICS200'; + LyricEndLen = Length(Lyric1End); +const + fIND = $494E44; + fLYR = $4C5952; + fEAL = $45414C; + fEAR = $454152; + fETT = $455454; + fIMG = $494D47; + fINF = $494E46; + +procedure ID3v1_TagCorrect(var dst:pWideChar;const tag:array of AnsiChar); +var + i:integer; + s:array [0..31] of AnsiChar; +begin + i:=High(tag); + move(tag,s,i+1); + while (i>0) and (tag[i]<=' ') do dec(i); + if i>0 then + begin + s[i+1]:=#0; + AnsiToWide(s,dst); + end; +end; + +procedure ID3v1_GetField(ptr:PAnsiChar; var dst:pWideChar; len:integer); +var + txtfield:array [0..250] of AnsiChar; +begin + if dst=nil then + begin + move(ptr^,txtfield,len); + txtfield[len]:=#0; + AnsiToWide(txtfield,dst); + end; +end; + +procedure ID3v1_CheckLyric(var Info:tSongInfo;f:THANDLE;ofs:integer); +const + maxlen = 5100; +var + tagHdr:array [0..9] of AnsiChar; + buf:array [0..maxlen] of AnsiChar; + ptr,ptr1:PAnsiChar; + i,size:integer; + field:dword; + c:dword; +begin + Seek(f,ofs); + BlockRead(f,tagHdr,LyricEndLen); + tagHdr[9]:=#0; + if StrCmp(tagHdr,Lyric1End,LyricEndLen)=0 then + begin + if Info.lyric=nil then + begin + Seek(f,ofs-maxlen); + BlockRead(f,buf,maxlen); + buf[maxlen]:=#0; + ptr:=@buf; + for i:=0 to maxlen-Length(LyricStart) do + begin + if ptr^='L' then + if StrCmp(ptr,LyricStart,Length(LyricStart))=0 then + begin + AnsiToWide(ptr+Length(LyricStart),Info.lyric); + break; + end; + inc(ptr); + end; + end; + end + else if StrCmp(tagHdr,Lyric2End,LyricEndLen)=0 then + begin + Seek(f,ofs-6); + BlockRead(f,buf,6); + size:=StrToInt(buf); + if size=32 then Info.track:=0; + end; + dec(ofs,9); + result:=1; + end + else + inc(ofs,SizeOf(tag)-9); + ID3v1_CheckLyric(Info,f,ofs); // +skipAPEtag +end; +{$ENDIF} diff --git a/plugins/Watrack/formats/tag_id3v2.inc b/plugins/Watrack/formats/tag_id3v2.inc new file mode 100644 index 0000000000..b1f833ea2a --- /dev/null +++ b/plugins/Watrack/formats/tag_id3v2.inc @@ -0,0 +1,545 @@ +{ID3v2 tag} + +{$IFDEF Interface} +function ReadID3v2(f:THANDLE; var Info:tSongInfo):longint; +{$ELSE} +const + frmTRK = $4B5254; + frmTT2 = $325454; + frmTP1 = $315054; + frmTAL = $4C4154; + frmTYE = $455954; + frmCOM = $4D4F43; + frmTCO = $4F4354; +// frmTCM = $;'; New: 'TCOM'), +// frmTEN = $;'; New: 'TENC'), +// frmTCR = $;'; New: 'TCOP'), +// frmWXX = $;'; New: 'WXXX'), + frmTT1 = $315454; +// frmTLA = $;'; New: 'TLAN'), + frmTOA = $414F54; + frmULT = $544C55; + frmSLT = $544C53; + frmTXX = $585854; + frmPIC = $434950; + + frmTIT1 = $31544954; // Content group description + frmTIT2 = $32544954; // Title/songname/content description + frmTIT3 = $33544954; // Subtitle/Description refinement + frmTALB = $424C4154; // Album/Movie/Show title + frmTOAL = $4C414F54; // Original album/movie/show title + frmTRCK = $4B435254; // Track number/Position in set + frmTYER = $52455954; // Year + frmTDRC = $43524454; // Year + frmTORY = $59524F54; // Original release year + frmTPE1 = $31455054; // Lead performer(s)/Soloist(s) + frmTPE2 = $32455054; // Band/orchestra/accompaniment + frmTPE3 = $33455054; // Conductor/performer refinement + frmTPE4 = $34455054; // Interpreted, remixed, or otherwise modified by + frmTOPE = $45504F54; // Original artist(s)/performer(s) + frmTCON = $4E4F4354; // Content type + frmCOMM = $4D4D4F43; // Comments + frmUSLT = $544C5355; // Unsynchronised lyrics + frmSYLT = $544C5953; // Synchronised lyrics + frmTXXX = $58585854; // User defined text + frmAPIC = $43495041; // Attached picture +const + TAG2Sign = 'ID3'; +const + ExtIDHdrMask=$40; + FooterPresent=$10; +type + TID3v2TagHdr = packed record + ID :array [0..2] of AnsiChar; + Version:word; + Flags :byte; + TagSize:dword; + end; + PID3v2TagHdr = ^TID3v2TagHdr; +type + tID3v2FrameHdr = packed record + ID:dword; + Size:dword; + Flags:word; + end; + pID3v2FrameHdr = ^tID3v2FrameHdr; + tID3v2FrameHdrOld = packed record + ID : array [0..2] of byte; { Frame ID } + Size: array [0..2] of Byte; { Size excluding header } + end; + pID3v2FrameHdrOld = ^tID3v2FrameHdrOld; + +var + Unsync:boolean; + +function ID3v2_Correct(data:dword):dword; +type + l2b=packed record + b:array [0..3] of byte; + end; +begin + result:=l2b(data).b[3]; + inc(result,dword(l2b(data).b[0]) shl 21); + inc(result,dword(l2b(data).b[1]) shl 14); + inc(result,dword(l2b(data).b[2]) shl 7); +end; + +procedure ID3v2_ReadTagStr1(var dst:PWideChar;ptr:PAnsiChar;alen:integer;enc:integer); +var + buf:PAnsiChar; +begin + if (enc=0) or (enc=3) then // ANSI or UTF8 + begin + if ptr^=#0 then + alen:=0 + else + while (alen>0) and (ptr[alen-1]=#0) do dec(alen); + + if alen>0 then + begin +{ + if enc=0 then + begin + StrDup(buf,ptr,alen); + AnsiToWide(buf,dst) + mFreeMem(buf); + end + else + UTF8ToWide(buf,dst,alen); +} + StrDup(buf,ptr,alen); + if enc=0 then + AnsiToWide(buf,dst) + else + UTF8ToWide(buf,dst); + mFreeMem(buf); + end + end + else {if enc<3 then} //Unicode + begin + if pword(ptr)^>0 then + begin + alen:=alen div SizeOf(WideChar); + + StrDupW(dst,pWideChar(ptr),alen); + ChangeUnicode(dst); + end; + end; +end; + +procedure ID3v2_ReadTagStr(var dst:PWideChar;ptr:PAnsiChar;alen:integer); +var + enc:byte; +begin + enc:=ORD(ptr^); + inc(ptr); + dec(alen); + if alen>0 then + ID3v2_ReadTagStr1(dst,ptr,alen,enc) + else + dst:=nil; +end; + +procedure ID3v2_CheckLyric(tag:integer; var dst:PWideChar;ptr:PAnsiChar;len:integer); +var + org,org1:PAnsiChar; + orgw,ptrw:pWideChar; + buf:array [0..127] of AnsiChar; + enc:byte; +begin + if dst<>NIL then exit; + enc:=ord(ptr^); + inc(ptr); + if tag=frmUSLT then + begin + org:=ptr; + inc(ptr,3); // language + if (enc=0) or (enc=3) then + begin + while ptr^<>#0 do inc(ptr); + inc(ptr); + end + else + begin + while pWord(ptr)^<>0 do inc(ptr,2); + inc(ptr,2); + end; + dec(len,ptr-org); + ID3v2_ReadTagStr1(dst,ptr,len,enc); + end + else if tag=frmSYLT then + begin + inc(ptr,4); + if ptr^<>#1 then exit; // 1 - lyric + inc(ptr); + mGetMem(dst,len-6); + FillChar(dst^,len-6,0); + + if (enc=0) or (enc=3) then + begin + while ptr^<>#0 do + begin + inc(ptr); + dec(len); + end; + inc(ptr); + dec(len); + org:=PAnsiChar(dst); + while len>0 do + begin + while ptr^<>#0 do + begin + org^:=ptr^; inc(org); inc(ptr); + dec(len); + end; + inc(ptr,1+4); // terminator+timestamp + dec(len,1+4); + end; + org:=PAnsiChar(dst); + if enc=0 then + AnsiToWide(org,dst) + else + UTF8ToWide(org,dst); + mFreeMem(org); + end + else + begin + orgw:=dst; + ptrw:=pWideChar(ptr); + while ptrw^<>#0 do + begin + inc(ptrw); + dec(len,SizeOf(WideChar)); + end; + inc(ptrw); + dec(len,SizeOf(WideChar)); + while len>0 do + begin + while ptrw^<>#0 do + begin + orgw^:=ptrw^; inc(orgw); inc(ptrw); + dec(len,SizeOf(WideChar)); + end; + inc(ptrw,1+2); // terminator + timestamp + dec(len,SizeOf(WideChar)+4); + end; + end; + end + else if tag=frmTXXX then + begin + FillChar(buf,SizeOf(buf),0); + org1:=ptr; + if (enc=0) or (enc=3) then + begin + org:=@buf; + while ptr^<>#0 do + begin + org^:=ptr^; + inc(org); + inc(ptr); + end; + inc(ptr); + if StrCmp(buf,'LYRICS')<>0 then + exit; + end + else + begin + orgw:=@buf; + ptrw:=pWideChar(ptr); + while ptrw^<>#0 do + begin + orgw^:=ptrw^; + inc(orgw); + inc(ptrw); + end; + inc(ptrw); + if StrCmpW(pWideChar(@buf),'LYRICS')<>0 then + exit; + ptr:=PAnsiChar(ptrw); + end; + dec(len,ptr-org1); + ID3v2_ReadTagStr1(dst,ptr,len,enc); + end; +end; + +procedure ID3v2_CheckCover(tag:integer; var dst:pWideChar;ptr:PAnsiChar;len:integer); +var + org:PAnsiChar; + ext:dword; + extw:int64; + enc:byte; +begin + if dst<>nil then exit; + org:=ptr; + enc:=ord(ptr^); inc(ptr); + if (pdword(ptr)^ and $FFFFFF)=$3E2D2D then exit; // as '-->' + if tag=frmAPIC then + begin + ext:=GetImageType(nil,ptr); + repeat inc(ptr) until ptr^=#0; inc(ptr); + end + else + begin + ext:=pdword(ptr)^ and $FFFFFF; + inc(ptr,3); + end; + + if not ord(ptr^) in [0,3,4,6] then exit; + inc(ptr); + if (enc=0) or (enc=3) then + begin + while ptr^<>#0 do inc(ptr); + inc(ptr); + end + else + begin + while pWord(ptr)^<>0 do inc(ptr,2); + inc(ptr,2); + end; + dec(len,ptr-org); + + if ext=0 then + ext:=GetImageType(pByte(ptr)); + if ext<>0 then + begin + FastAnsiToWideBuf(PAnsiChar(@ext),pWideChar(@extw)); + dst:=SaveTemporaryW(ptr,len,PWideChar(@extw)); + end; +end; + +function ID3v2_PreReadTag(var frm:tID3v2FrameHdr;var src:PAnsiChar;ver:integer):PAnsiChar; +var + i:cardinal; + dst:PAnsiChar; +begin + mGetMem(result,frm.Size); + if Unsync or ((frm.Flags and $0200)<>0) then + begin + dst:=result; + i:=0; + while i0 then + begin + Frm.Size:=ID3v2_Correct(pdword(tag)^); + inc(tag,4); + end; + end; + end; + + if Frm.ID=0 then + break; + if Frm.Size=0 then + continue; + if (tag+Frm.Size)>lp then + break; + buf:=ID3v2_PreReadTag(Frm,tag,ver); + + enc:=ord(buf^); + case enc of // set priority + 0: enc:=1; + 1,2: enc:=3; + 3: enc:=3; // or 2 if you want + end; + case Frm.ID of + frmUSLT,frmULT: ID3v2_CheckLyric(frmUSLT,Info.lyric,buf,Frm.Size); + frmSYLT,frmSLT: ID3v2_CheckLyric(frmSYLT,Info.lyric,buf,Frm.Size); + frmTXX,frmTXXX: ID3v2_CheckLyric(frmTXXX,Info.lyric,buf,Frm.Size); + frmAPIC,frmPIC: ID3v2_CheckCover(Frm.ID ,Info.cover,buf,Frm.Size); + + frmTPE1,frmTP1: begin + if fArtist<(enc+10) then + begin + fArtist:=enc+10; + mFreeMem(Info.artist); + ID3v2_ReadTagStr(Info.artist,buf,Frm.Size); + end + end; + frmTIT2,frmTT2: begin + if fTitle<(enc+10) then + begin + fTitle:=enc+10; + mFreeMem(Info.title); + ID3v2_ReadTagStr(Info.title,buf,Frm.Size); + end + end; + frmTALB,frmTAL: begin + if fAlbum<(enc+10) then + begin + fAlbum:=enc+10; + mFreeMem(Info.album); + ID3v2_ReadTagStr(Info.album,buf,Frm.Size); + end + end; + frmTYER,frmTDRC,frmTYE: begin + if Info.year<>nil then + mFreeMem(Info.year); + ID3v2_ReadTagStr(Info.year,buf,Frm.Size); + end; + + frmTOPE,frmTPE2,frmTOA,frmTPE4: begin + if fArtistnil then + if Info.genre[0]='(' then + begin + tmp:=StrScanW(Info.genre,')')-Info.genre+1; + if tmp=integer(StrLenW(Info.genre)) then + begin + ls:=GenreName(StrToInt(Info.genre+1)); + mFreeMem(Info.genre); + Info.genre:=ls; + end + else if tmp>0 then + StrCopyW(Info.genre,Info.genre+tmp); + end; + end; + end; + frmCOMM,frmCOM: begin //!! + if Info.comment=nil then + begin + ptr:=buf; + inc(ptr,3+1); // language + if (buf^=#0) or (buf^=#3) then + begin + while ptr^<>#0 do inc(ptr); + inc(ptr); + end + else + begin + while pWord(ptr)^<>0 do inc(ptr,2); + inc(ptr,2); + end; + dec(Frm.Size,ptr-buf); + ID3v2_ReadTagStr1(Info.comment,ptr,Frm.Size,ord(buf^)); + end; + end; + frmTRCK,frmTRK: begin + if Info.track=0 then + begin + ID3v2_ReadTagStr(ls,buf,Frm.Size); + Info.track:=StrToInt(ls); + mFreeMem(ls); + end; + end; + end; + mFreeMem(buf); + end; +end; + +function ReadID3v2(f:THANDLE; var Info:tSongInfo):longint; +var + TagHdr:TID3v2TagHdr; + Tag2:PAnsiChar; + ExtTagSize:dword; +begin + BlockRead(f,TagHdr,SizeOf(TagHdr)); + if TagHdr.ID=TAG2Sign then + begin + TagHdr.TagSize:=ID3v2_Correct(TagHdr.TagSize); + Unsync:=(TagHdr.Flags and $80)<>0; + result:=TagHdr.TagSize; +// if TagHdr.Version>2 then + begin + GetMem(Tag2,TagHdr.TagSize); + BlockRead(f,Tag2^,TagHdr.TagSize); + ID3v2_ReadTag2(TagHdr.Version,Tag2,TagHdr.TagSize,Info); + FreeMem(Tag2); + end; + if (TagHdr.Flags and ExtIDHdrMask)<>0 then + begin + BlockRead(f,ExtTagSize,SizeOf(ExtTagSize)); + inc(result,4+ExtTagSize); + end; + if (TagHdr.Flags and FooterPresent)<>0 then + inc(result,10); + end + else + result:=0; + Seek(f,result); +end; +{$ENDIF} diff --git a/plugins/Watrack/formats/tags.pas b/plugins/Watrack/formats/tags.pas new file mode 100644 index 0000000000..fbe0576c59 --- /dev/null +++ b/plugins/Watrack/formats/tags.pas @@ -0,0 +1,21 @@ +unit tags; +{$include compilers.inc} +interface + +uses wat_api,windows; + +{$DEFINE Interface} +{$include tag_id3v2.inc} +{$include tag_id3v1.inc} +{$include tag_apev2.inc} + +implementation + +uses common,io,utils; + +{$UNDEF Interface} +{$include tag_id3v2.inc} +{$include tag_id3v1.inc} +{$include tag_apev2.inc} + +end. \ No newline at end of file -- cgit v1.2.3