shithub: libtags

ref: f68e997ecefd77b3d1cd33c21264d5ced6e5d021
dir: /m4a.c/

View raw version
/* http://wiki.multimedia.cx/?title=QuickTime_container */
/* https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html */
#include "tagspriv.h"

#define beuint16(d) (uint16_t)((d)[0]<<8 | (d)[1]<<0)

int
tagm4a(Tagctx *ctx)
{
	uint64_t duration;
	uint x;
	uint8_t *d;
	char *k, *v;
	int sz, type, dtype, i, skip, n;

	d = (uint8_t*)ctx->buf;
	/* 4 bytes for atom size, 4 for type, 4 for data - exect "ftyp" to come first */
	if(ctx->read(ctx, d, 4+4+4) != 4+4+4 || memcmp(d+4, "ftypM4A ", 8) != 0)
		return -1;
	sz = beuint(d) - 4; /* already have 8 bytes */

	for(;;){
		if(sz < 0 || ctx->seek(ctx, sz, 1) < 0)
			return -1;
		if(ctx->read(ctx, d, 4) != 4) /* size */
			break;
		sz = beuint(d);
		if(sz == 0)
			continue;
		if(ctx->read(ctx, d, 4) != 4) /* type */
			return -1;
		if(sz < 8)
			continue;

		d[4] = 0;

		if(memcmp(d, "meta", 4) == 0){
			sz = 4;
			continue;
		}else if(
			memcmp(d, "udta", 4) == 0 ||
			memcmp(d, "ilst", 4) == 0 ||
			memcmp(d, "trak", 4) == 0 ||
			memcmp(d, "mdia", 4) == 0 ||
			memcmp(d, "minf", 4) == 0 ||
			memcmp(d, "moov", 4) == 0 ||
			memcmp(d, "trak", 4) == 0 ||
			memcmp(d, "stbl", 4) == 0 ||
			memcmp(d, "----", 4) == 0){
			sz = 0;
			continue;
		}else if(memcmp(d, "stsd", 4) == 0){
			sz -= 8;
			if(ctx->read(ctx, d, 8) != 8)
				return -1;
			sz -= 8;

			for(i = beuint(&d[4]); i > 0 && sz > 0; i--){
				if(ctx->read(ctx, d, 8) != 8) /* size + format */
					return -1;
				sz -= 8;
				skip = beuint(d) - 8;
				if(skip < 0)
					return -1;

				if(memcmp(&d[4], "mp4a", 4) == 0){ /* audio */
					n = 6+2 + 2+4+2 + 2+2 + 2+2 + 4; /* read a bunch at once */
					/* reserved+id, ver+rev+vendor, channels+bps, ?+?, sample rate */
					if(ctx->read(ctx, d, n) != n)
						return -1;
					skip -= n;
					sz -= n;
					ctx->channels = beuint16(&d[16]);
					ctx->samplerate = beuint(&d[24])>>16;
				}

				if(ctx->seek(ctx, skip, 1) < 0)
					return -1;
				sz -= skip;
			}
			continue;
		}

		sz -= 8;
		type = -1;
		if(memcmp(d, "\251nam", 4) == 0)
			type = Ttitle;
		else if(memcmp(d, "\251alb", 4) == 0)
			type = Talbum;
		else if(memcmp(d, "\251ART", 4) == 0)
			type = Tartist;
		else if(memcmp(d, "aART", 4) == 0)
			type = Talbumartist;
		else if(memcmp(d, "\251gen", 4) == 0 || memcmp(d, "gnre", 4) == 0)
			type = Tgenre;
		else if(memcmp(d, "\251day", 4) == 0)
			type = Tdate;
		else if(memcmp(d, "covr", 4) == 0)
			type = Timage;
		else if(memcmp(d, "trkn", 4) == 0)
			type = Ttrack;
		else if(memcmp(d, "\251wrt", 4) == 0)
			type = Tcomposer;
		else if(memcmp(d, "\251cmt", 4) == 0)
			type = Tcomment;
		else if(memcmp(d, "mdhd", 4) == 0){
			if(ctx->read(ctx, d, 4) != 4)
				return -1;
			sz -= 4;
			duration = 0;
			if(d[0] == 0){ /* version 0 */
				if(ctx->read(ctx, d, 16) != 16)
					return -1;
				sz -= 16;
				if((x = beuint(&d[8])) > 0)
					duration = beuint(&d[12]) / x;
			}else if(d[1] == 1){ /* version 1 */
				if(ctx->read(ctx, d, 28) != 28)
					return -1;
				sz -= 28;
				if((x = beuint(&d[16])) > 0)
					duration = ((uint64_t)beuint(&d[20])<<32 | beuint(&d[24])) / (uint64_t)x;
			}
			ctx->duration = duration * 1000;
			continue;
		}else if(memcmp(d, "name", 4) == 0){
			if(sz <= 16 || sz >= ctx->bufsz-1) /* 1 for '\0' */
				continue;
			if(ctx->read(ctx, d, sz) != sz)
				return -1;
			// FIXME - do first 4 bytes mean anything?
			k = (char*)d + 4;
			d[sz] = 0;
			n = sz + 1;
			v = (char*)d + n;

			if(ctx->read(ctx, d, 4) != 4) /* size */
				return -1;
			sz = beuint(d);
			if(sz <= 16 || 12 > ctx->bufsz-n || sz-16 >= ctx->bufsz-n-1){
				sz -= 4;
				continue;
			}
			sz -= 4+4+4+4;
			if(ctx->read(ctx, v, 12) != 12) /* "data", data type (should be text) and 4 more bytes */
				return -1;
			if(memcmp(v, "data", 4) != 0 || beuint(v+4) != 1)
				continue;
			if(ctx->read(ctx, v, sz) != sz)
				return -1;
			v[sz] = 0;
			cbvorbiscomment(ctx, k, v);
			sz = 0;
			continue;
		}

		if(type < 0)
			continue;

		if(sz < 16 || ctx->seek(ctx, 8, 1) < 0) /* skip size and "data" */
			return -1;
		sz -= 8;
		if(ctx->read(ctx, d, 8) != 8) /* read data type and 4 bytes of whatever else */
			return -1;
		sz -= 8;
		d[0] = 0;
		dtype = beuint(d);

		if(type == Ttrack){
			if(ctx->read(ctx, d, 4) != 4)
				return -1;
			sz -= 4;
			snprintf((char*)d, ctx->bufsz, "%d", beuint(d));
			txtcb(ctx, type, "", d);
		}else if(type == Tgenre && dtype != 1){
			if(ctx->read(ctx, d, 2) != 2)
				return -1;
			sz -= 2;
			if((i = d[1]-1) >= 0 && i < Numgenre)
				txtcb(ctx, type, "", id3genres[i]);
		}else if(dtype == 1){ /* text */
			if(sz >= ctx->bufsz) /* skip tags that can't fit into memory. ">=" because of '\0' */
				continue;
			if(sz < 0 || ctx->read(ctx, d, sz) != sz)
				return -1;
			d[sz] = 0;
			txtcb(ctx, type, "", d);
			sz = 0;
		}else if(type == Timage){
			tagscallcb(ctx, Timage, &(Tag){.image = {
				.mime = dtype == 13 ?
					"image/jpeg" :
					(dtype == 14 ? "image/png" : ""),
				.type = ITcover_front,
				.offset = ctx->seek(ctx, 0, 1),
				.size = sz,
			}});
		}
	}

	return 0;
}