parent
52217b637d
commit
a363986076
@ -0,0 +1,233 @@ |
|||||||
|
#include <math.h> |
||||||
|
#include "tagspriv.h" |
||||||
|
|
||||||
|
#define leu16int(d) (u16int)(((uchar*)(d))[1]<<8 | ((uchar*)(d))[0]<<0) |
||||||
|
|
||||||
|
enum |
||||||
|
{ |
||||||
|
HeaderSize = 32, |
||||||
|
FooterSize = HeaderSize, |
||||||
|
|
||||||
|
MagicOffset = 0, |
||||||
|
VersionOffset = 8, |
||||||
|
SizeOffset = 12, |
||||||
|
CountOffset = 16, |
||||||
|
FlagsOffset = 20, |
||||||
|
|
||||||
|
WvHeaderSize = 32, |
||||||
|
WvMagicOffset = 0, |
||||||
|
WvVersionOffset = 8, |
||||||
|
WvSamplesHighOffset = 11, |
||||||
|
WvSamplesLowOffset = 12, |
||||||
|
WvFlagsOffset = 24, |
||||||
|
WvSampleRateMask = 0xf << 23, |
||||||
|
WvSampleRateShift = 23, |
||||||
|
WvCustomSampleRate = 16, |
||||||
|
WvMonoMask = 4, |
||||||
|
}; |
||||||
|
|
||||||
|
typedef enum |
||||||
|
{ |
||||||
|
TagUTF8, |
||||||
|
TagBinary, |
||||||
|
TagExternal, |
||||||
|
TagReserved, |
||||||
|
|
||||||
|
TagInvalid, |
||||||
|
} TagType; |
||||||
|
|
||||||
|
static int |
||||||
|
isWavpack(Tagctx *ctx) |
||||||
|
{ |
||||||
|
uchar header[WvHeaderSize]; |
||||||
|
int size; |
||||||
|
u16int version; |
||||||
|
u32int flags, samplerate; |
||||||
|
uvlong samples; |
||||||
|
|
||||||
|
if(ctx->seek(ctx, 0, 0) < 0) |
||||||
|
return 0; |
||||||
|
if(ctx->read(ctx, header, WvHeaderSize) != WvHeaderSize) |
||||||
|
return 0; |
||||||
|
if(memcmp(header+WvMagicOffset, "wvpk", 4)) |
||||||
|
return 0; |
||||||
|
version = leu16int(header+WvVersionOffset); |
||||||
|
if(version<0x402 || version>0x410) |
||||||
|
return 0; |
||||||
|
samples = (uvlong)(*(uchar*)(header+WvSamplesHighOffset))<<32 | leuint(header+WvSamplesLowOffset); |
||||||
|
flags = leuint(header+WvFlagsOffset); |
||||||
|
if((flags&WvSampleRateMask)>>WvSampleRateShift != WvCustomSampleRate){ |
||||||
|
const u32int samplerates[] = {6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000}; |
||||||
|
samplerate = samplerates[(flags&WvSampleRateMask)>>WvSampleRateShift]; |
||||||
|
ctx->samplerate = samplerate; |
||||||
|
uvlong duration = round((double)samples/samplerate*1000); |
||||||
|
ctx->duration = duration; |
||||||
|
} |
||||||
|
ctx->channels = flags&WvMonoMask ? 1 : 2; |
||||||
|
if(ctx->seek(ctx, 0, 0) < 0) |
||||||
|
return 0; |
||||||
|
if(size = ctx->seek(ctx, 0, 2), size < 0) |
||||||
|
return 0; |
||||||
|
ctx->bitrate = (double)size*8.0/(samples/samplerate)/1000; |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
detectFormat(Tagctx *ctx) |
||||||
|
{ |
||||||
|
if(isWavpack(ctx)) |
||||||
|
return Fwavpack; |
||||||
|
return Funknown; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
tagHasHeader(u32int tags) |
||||||
|
{ |
||||||
|
return (tags&(1<<31)) >> 31; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
tagIsHeader(u32int tags) |
||||||
|
{ |
||||||
|
return (tags&(1<<29)) >> 29; |
||||||
|
} |
||||||
|
|
||||||
|
static TagType |
||||||
|
tagGetType(u32int tags) |
||||||
|
{ |
||||||
|
switch((tags&(1<<1))>>1 | (tags&(1<<2))>>1 | (tags&(1<<3))>>1 | (tags&(1<<4))>>1){ |
||||||
|
case 0: |
||||||
|
return TagUTF8; |
||||||
|
case 1: |
||||||
|
return TagBinary; |
||||||
|
case 2: |
||||||
|
return TagExternal; |
||||||
|
case 3: |
||||||
|
return TagReserved; |
||||||
|
default: |
||||||
|
return TagInvalid; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
tagTagType(char *name) |
||||||
|
{ |
||||||
|
if(!strcmp(name, "Album")) |
||||||
|
return Talbum; |
||||||
|
else if(!strcmp(name, "Album Artist")) |
||||||
|
return Talbumartist; |
||||||
|
else if(!strcmp(name, "Artist")) |
||||||
|
return Tartist; |
||||||
|
else if(!strcmp(name, "Comment")) |
||||||
|
return Tcomment; |
||||||
|
else if(!strcmp(name, "Composer")) |
||||||
|
return Tcomposer; |
||||||
|
else if(!strncmp(name, "Cover Art (", 11)){ |
||||||
|
if(name[strlen(name)-1] == ')') |
||||||
|
return Timage; |
||||||
|
}else if(!strcmp(name, "Genre")) |
||||||
|
return Tgenre; |
||||||
|
else if(!strcmp(name, "Replaygain_Album_Gain")) |
||||||
|
return Talbumgain; |
||||||
|
else if(!strcmp(name, "Replaygain_Album_Peak")) |
||||||
|
return Talbumpeak; |
||||||
|
else if(!strcmp(name, "Replaygain_Track_Gain")) |
||||||
|
return Ttrackgain; |
||||||
|
else if(!strcmp(name, "Replaygain_Track_Peak")) |
||||||
|
return Ttrackpeak; |
||||||
|
else if(!strcmp(name, "Title")) |
||||||
|
return Ttitle; |
||||||
|
else if(!strcmp(name, "Track")) |
||||||
|
return Ttrack; |
||||||
|
else if(!strcmp(name, "Year")) |
||||||
|
return Tdate; |
||||||
|
return Tunknown; |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
tagape(Tagctx *ctx) |
||||||
|
{ |
||||||
|
uchar footer[FooterSize]; |
||||||
|
u32int i, count; |
||||||
|
|
||||||
|
ctx->format = detectFormat(ctx); |
||||||
|
|
||||||
|
if(ctx->seek(ctx, -FooterSize, 2) < 0) |
||||||
|
return -1; |
||||||
|
if(ctx->read(ctx, footer, FooterSize) != FooterSize) |
||||||
|
return -1; |
||||||
|
if(memcmp(footer+MagicOffset, "APETAGEX", 8)) |
||||||
|
return -1; |
||||||
|
if(leuint(footer+VersionOffset) != 2000) |
||||||
|
return -1; |
||||||
|
if(tagIsHeader(leuint(footer+FlagsOffset))) |
||||||
|
return -1; |
||||||
|
|
||||||
|
if(ctx->seek(ctx, -FooterSize-leuint(footer+SizeOffset), 2) < 0) |
||||||
|
return -1; |
||||||
|
if(tagHasHeader(leuint(footer+FlagsOffset))){ |
||||||
|
uchar header[HeaderSize]; |
||||||
|
if(ctx->read(ctx, header, HeaderSize) != HeaderSize) |
||||||
|
return -1; |
||||||
|
if(memcmp(header, footer, 23)) |
||||||
|
return -1; |
||||||
|
if(!tagHasHeader(leuint(header+FlagsOffset))) |
||||||
|
return -1; |
||||||
|
if(!tagIsHeader(leuint(header+FlagsOffset))) |
||||||
|
return -1; |
||||||
|
}else if(ctx->seek(ctx, HeaderSize, 1) < 0) |
||||||
|
return -1; |
||||||
|
|
||||||
|
for(i = 0, count = leuint(footer+CountOffset); i < count; i++){ |
||||||
|
int valueOffset = 0; |
||||||
|
char c; |
||||||
|
u32int d, length, flags; |
||||||
|
|
||||||
|
if(ctx->read(ctx, &d, 4) != 4) |
||||||
|
return -1; |
||||||
|
length = leuint(&d); |
||||||
|
if(ctx->read(ctx, &d, 4) != 4) |
||||||
|
return -1; |
||||||
|
flags = leuint(&d); |
||||||
|
|
||||||
|
do{ |
||||||
|
if(valueOffset == ctx->bufsz) |
||||||
|
return -1; |
||||||
|
if(ctx->read(ctx, &c, 1) != 1) |
||||||
|
return -1; |
||||||
|
if(c<' ' || c>'~') |
||||||
|
if(c != '\0') |
||||||
|
return -1; |
||||||
|
ctx->buf[valueOffset++] = c; |
||||||
|
}while(c != '\0'); |
||||||
|
if(valueOffset+1+(int)length>ctx->bufsz && tagTagType(ctx->buf)!=Timage){ |
||||||
|
if(ctx->seek(ctx, length, 1) < 0) |
||||||
|
return -1; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
switch(tagGetType(flags)){ |
||||||
|
u32int keyOffset; |
||||||
|
case TagUTF8: |
||||||
|
if(ctx->read(ctx, ctx->buf+valueOffset, length) != (int)length) |
||||||
|
return -1; |
||||||
|
(ctx->buf+valueOffset)[length] = '\0'; |
||||||
|
for(keyOffset = 0; keyOffset != length;){ |
||||||
|
txtcb(ctx, tagTagType(ctx->buf), ctx->buf, ctx->buf+valueOffset+keyOffset); |
||||||
|
if(keyOffset += strlen(ctx->buf+valueOffset+keyOffset), keyOffset != length) |
||||||
|
keyOffset++; |
||||||
|
} |
||||||
|
break; |
||||||
|
case TagBinary: |
||||||
|
if(tagTagType(ctx->buf) == Timage) |
||||||
|
tagscallcb(ctx, Timage, ctx->buf, ctx->buf, ctx->seek(ctx, 0, 1), length, NULL); |
||||||
|
if(ctx->seek(ctx, length, 1) < 0) |
||||||
|
return -1; |
||||||
|
break; |
||||||
|
default: |
||||||
|
if(ctx->seek(ctx, length, 1) < 0) |
||||||
|
return -1; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
Loading…
Reference in new issue