ref: f68e997ecefd77b3d1cd33c21264d5ced6e5d021
dir: /vorbis.c/
/* * https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 * https://wiki.xiph.org/VorbisComment */ #include "tagspriv.h" static const struct { char *s; int type; }t[] = { {"album artist", Talbumartist}, // some legacy leftovers {"album", Talbum}, {"albumartist", Talbumartist}, {"artist", Tartist}, {"comment", Tcomment}, {"composer", Tcomposer}, {"date", Tdate}, {"genre", Tgenre}, {"r128_album_gain", Talbumgain}, {"r128_track_gain", Ttrackgain}, {"replaygain_album_gain", Talbumgain}, {"replaygain_album_peak", Talbumpeak}, {"replaygain_track_gain", Ttrackgain}, {"replaygain_track_peak", Ttrackpeak}, {"title", Ttitle}, {"tracknumber", Ttrack}, }; void cbvorbiscomment(Tagctx *ctx, char *k, char *v) { int i; if(*v == 0) return; for(i = 0; i < nelem(t); i++){ if(strcasecmp(k, t[i].s) == 0){ txtcb(ctx, t[i].type, k, v); break; } } if(i == nelem(t)) txtcb(ctx, Tunknown, k, v); } static int mbpdec(void *buf, int *cnt) { int sz, n; uint8_t *v; v = buf; if((n = debase64(v, *cnt, v, *cnt)) <= 0) return -1; /* skip id3v2 APIC type */ v += 4; n -= 4; sz = beuint(v); /* mime size */ v += 4; n -= 4; if(sz < 0 || sz >= n-4-4-4-4-4-4) return -1; v += sz; n -= sz; /* skip MIME */ sz = beuint(v); /* description size */ v += 4; n -= 4; if(sz < 0 || sz >= n-4-4-4-4-4) return -1; v += sz; n -= sz; /* skip description */ v += 4+4+4+4; n -= 4+4+4+4; /* skip width, height, depth, palette info */ sz = beuint(v); /* picture size */ v += 4; n -= 4; if(sz <= 0 || sz > n) return -1; memmove(buf, v, sz); *cnt = sz; return 0; } int cbmbp(Tagctx *ctx, char *v, int ssz, int off, int picsz) { char *mime; int type, n, sz; n = ssz; /* at most this amount is available */ n &= ~3; /* modulo 4 sextets, so debase64 gets complete bytes */ n = debase64((uint8_t*)v, n, (uint8_t*)ctx->buf, ctx->bufsz); /* https://xiph.org/flac/format.html#metadata_block_picture */ if(n <= 4+4+0+4+0+4+4+4+4+4+0) return 0; v = ctx->buf; type = beuint(v); /* id3v2 APIC type */ v += 4; n -= 4; sz = beuint(v); /* mime size */ v += 4; n -= 4; if(sz < 0 || sz >= n-4-4-4-4-4-4) return -1; mime = v; v += sz; n -= sz; /* skip MIME */ sz = beuint(v); /* description size */ v += 4; n -= 4; if(sz < 0 || sz >= n-4-4-4-4-4) return -1; *v = 0; /* null-terminate MIME */ tagscallcb(ctx, Timage, &(Tag){.image = { .mime = mime, .offset = off, .size = picsz, .type = type, .decode = mbpdec, }}); return 0; } int tagvorbis(Tagctx *ctx) { char *v; uint8_t *d, h[4]; int sz, numtags, i, npages, pgend, skip; d = (uint8_t*)ctx->buf; /* need to find vorbis frame with type=3 */ for(npages = pgend = 0; npages < 2; npages++){ /* vorbis comment is the second header */ int nsegs; if(ctx->read(ctx, d, 27) != 27) return -1; if(memcmp(d, "OggS", 4) != 0) return -1; /* calculate the size of the packet */ nsegs = d[26]; if(ctx->read(ctx, d, nsegs+1) != nsegs+1) return -1; for(sz = i = 0; i < nsegs; sz += d[i++]); if(d[nsegs] == 3){ /* comment */ /* FIXME - embedded pics make tags span multiple packets */ pgend = ctx->seek(ctx, 0, 1) + sz; break; } if(d[nsegs] == 1 && sz >= 28){ /* identification */ if(ctx->read(ctx, d, 28) != 28) return -1; sz -= 28; ctx->channels = d[10]; ctx->samplerate = leuint(&d[11]); if((ctx->bitrate = leuint(&d[15])) == 0) /* maximum */ ctx->bitrate = leuint(&d[19]); /* nominal */ } ctx->seek(ctx, sz-1, 1); } if(npages < 3) { if(ctx->read(ctx, &d[1], 10) != 10 || memcmp(&d[1], "vorbis", 6) != 0) return -1; sz = leuint(&d[7]); if(ctx->seek(ctx, sz, 1) < 0 || ctx->read(ctx, h, 4) != 4) return -1; numtags = leuint(h); for(i = 0; i < numtags; i++){ if(ctx->read(ctx, h, 4) != 4) return -1; if((sz = leuint(h)) < 0) return -1; /* FIXME - embedded pics make tags span multiple packets */ if(pgend < ctx->seek(ctx, 0, 1)+sz) break; skip = 0; if(sz > ctx->bufsz-1){ skip = sz - (ctx->bufsz-1); sz -= skip; } if(ctx->read(ctx, ctx->buf, sz) != sz) return -1; ctx->buf[sz] = 0; if((v = strchr(ctx->buf, '=')) == nil) return -1; *v++ = 0; if(strcasecmp(ctx->buf, "metadata_block_picture") != 0) cbvorbiscomment(ctx, ctx->buf, v); else if(cbmbp(ctx, v, sz - (v - ctx->buf), /* at most this amount is available */ ctx->seek(ctx, 0, 1) - sz + (v - ctx->buf), /* offset */ sz + skip - (v - ctx->buf) /* total pic size (still encoded) */ ) != 0) return -1; if(ctx->seek(ctx, skip, 1) < 0) return -1; } } /* calculate the duration */ if(ctx->samplerate > 0){ sz = ctx->bufsz <= 4096 ? ctx->bufsz : 4096; for(i = sz; i < 65536+16; i += sz - 16){ if(ctx->seek(ctx, -i, 2) <= 0) break; v = ctx->buf; if(ctx->read(ctx, v, sz) != sz) break; for(; v != nil && v < ctx->buf+sz;){ v = memchr(v, 'O', ctx->buf+sz - v - 14); if(v != nil && v[1] == 'g' && v[2] == 'g' && v[3] == 'S' && (v[5] & 4) == 4){ /* last page */ uint64_t g = leuint(v+6) | (uint64_t)leuint(v+10)<<32; ctx->duration = g * 1000 / ctx->samplerate; } if(v != nil) v++; } if(ctx->duration != 0) break; } } return 0; }