ref: 244d6dbe34069fb8f8c4cc61e5c4725537f0cfd6
dir: /m4a.c/
/* 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 && dtype == 13) /* jpeg cover image */
tagscallcb(ctx, Timage, "", "image/jpeg", ctx->seek(ctx, 0, 1), sz, nil);
else if(type == Timage && dtype == 14) /* png cover image */
tagscallcb(ctx, Timage, "", "image/png", ctx->seek(ctx, 0, 1), sz, nil);
}
return 0;
}