shithub: libtags

ref: f68e997ecefd77b3d1cd33c21264d5ced6e5d021
dir: /flac.c/

View raw version
/* 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;
}