ref: f68e997ecefd77b3d1cd33c21264d5ced6e5d021
dir: /flac.c/
/* https://xiph.org/flac/format.html */
#include "tagspriv.h"
#define beu3(d) ((d)[0]<<16 | (d)[1]<<8 | (d)[2]<<0)
int
tagflac(Tagctx *ctx)
{
int sz, last, type, seektbloff, seektblsz, off;
uint8_t *d;
uint64_t g;
d = (uint8_t*)ctx->buf;
/* 8 bytes for marker, block type, length. 18 bytes for the stream info */
if(ctx->read(ctx, d, 8+18) != 8+18 || memcmp(d, "fLaC\x00", 5) != 0)
return -1;
sz = beu3(&d[5]); /* size of the stream info */
ctx->samplerate = beu3(&d[18]) >> 4;
ctx->channels = ((d[20]>>1) & 7) + 1;
if(ctx->samplerate < 1 || ctx->channels < 1)
return -1;
g = (uint64_t)(d[21] & 0xf)<<32 | beu3(&d[22])<<8 | d[25];
ctx->duration = g * 1000 / ctx->samplerate;
/* skip the rest of the stream info */
if(ctx->seek(ctx, sz-18, 1) != ctx->restart+8+sz)
return -1;
for(last = 0; !last;){
if(ctx->read(ctx, d, 4) != 4)
return -1;
if((sz = beu3(&d[1])) < 0)
return -1;
if((d[0] & 0x80) != 0)
last = 1;
if((d[0] & 0x7f) == 3 && ctx->toc != nil){ /* 3 = seek table */
seektbloff = ctx->seek(ctx, 0, 1);
seektblsz = sz;
if(ctx->seek(ctx, sz, 1) <= 0)
return -1;
}else if((d[0] & 0x7f) == 6){ /* 6 = picture */
int n, offset;
char *mime;
if(sz < 8+4+20 || ctx->read(ctx, d, 8) != 8) /* type, mime length */
return -1;
sz -= 8;
type = beuint(d); /* type */
n = beuint(&d[4]); /* mime length */
mime = ctx->buf+20;
if(n < 0 || n >= sz-4-20 || n >= ctx->bufsz-20 || ctx->read(ctx, mime, n) != n)
return -1;
sz -= n;
mime[n] = 0;
ctx->read(ctx, d, 4); /* description */
sz -= 4;
offset = beuint(d) + ctx->seek(ctx, 0, 1) + 20;
ctx->read(ctx, d, 20);
sz -= 20;
if((n = beuint(&d[16])) < 0)
return -1;
if(n > 0){
tagscallcb(ctx, Timage, &(Tag){.image = {
.mime = mime,
.offset = offset,
.size = n,
.type = type,
}});
}
if(ctx->seek(ctx, sz, 1) <= 0)
return -1;
}else if((d[0] & 0x7f) == 4){ /* 4 = vorbis comment */
int i, numtags, tagsz, vensz;
char *k, *v;
if(sz < 12 || ctx->read(ctx, d, 4) != 4)
return -1;
sz -= 4;
vensz = leuint(d);
if(vensz < 0 || vensz > sz-4)
return -1;
/* skip vendor, read the number of tags */
if(ctx->seek(ctx, vensz, 1) < 0 || ctx->read(ctx, d, 4) != 4)
return -1;
sz -= vensz + 4;
numtags = leuint(d);
for(i = 0; i < numtags && sz > 4; i++){
if(ctx->read(ctx, d, 4) != 4)
return -1;
tagsz = leuint(d);
sz -= 4;
if(tagsz < 0 || tagsz > sz)
return -1;
/* if it doesn't fit, ignore it */
if(tagsz == 0 || tagsz >= ctx->bufsz){
if(ctx->seek(ctx, tagsz, 1) < 0)
return -1;
continue;
}
k = ctx->buf;
if(ctx->read(ctx, k, tagsz) != tagsz)
return -1;
/* some tags have a stupid '\r'; ignore */
if(k[tagsz-1] == '\r')
k[tagsz-1] = 0;
k[tagsz] = 0;
if((v = strchr(k, '=')) != nil){
*v++ = 0;
cbvorbiscomment(ctx, k, v);
}
}
}else if(ctx->seek(ctx, sz, 1) <= 0)
return -1;
}
if(ctx->toc == nil)
return 0;
off = ctx->seek(ctx, 0, 1);
if(seektbloff <= 0 || off <= seektbloff || ctx->seek(ctx, seektbloff, 0) != seektbloff)
return 0;
for(; seektblsz >= 18; seektblsz -= 18){
if(ctx->read(ctx, d, 18) != 18)
break;
/* sample offset */
g = (uint64_t)beuint(d+0)<<32 | beuint(d+4);
if(g == ~0ULL) /* placeholder */
break;
g = g * 1000 / ctx->samplerate;
if(g > INT_MAX)
break;
int ms = g;
/* frame offset */
g = off + ((uint64_t)beuint(d+8)<<32 | beuint(d+12));
if(g > INT_MAX)
break;
ctx->toc(ctx, ms, g);
}
return 0;
}