shithub: libtags

Download patch

ref: c074cc2a8b2d8e2721457f3c8723298dfd1fd212
parent: 1a9591bd4f932a6ca2fba0eb84cb6b790c8c7250
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Sun Mar 17 13:58:15 EDT 2024

redo parts of the API to fit more tag-kind-specific info - attached image type

--- a/examples/readtags.c
+++ b/examples/readtags.c
@@ -33,19 +33,48 @@
 
 static bool image;
 
+static const char *
+imagetype(int type)
+{
+	static const char *types[] = {
+		[ITother] = "Other",
+		[IT32x32_file_icon] = "32x32 pixels 'file icon' (PNG only)",
+		[ITother_file_icon] = "Other file icon",
+		[ITcover_front] = "Cover (front)",
+		[ITcover_back] = "Cover (back)",
+		[ITleaflet] = "Leaflet page",
+		[ITmedia] = "Media (e.g. label side of CD)",
+		[ITlead] = "Lead artist/lead performer/soloist",
+		[ITartist] = "Artist/performer",
+		[ITconductor] = "Conductor",
+		[ITband] = "Band/orchestra",
+		[ITcomposer] = "Composer",
+		[ITlyricist] = "Lyricist/text writer",
+		[ITlocation] = "Recording location",
+		[ITrecording] = "During recording",
+		[ITperformance] = "During performance",
+		[ITmovie_capture] = "Movie/video screen capture",
+		[ITfish] = "A bright coloured fish",
+		[ITillustration] = "Illustration",
+		[ITlogo_band] = "Band/artist logotype",
+		[ITlogo_publisher] = "Publisher/studio logotype",
+	};
+	return type >= 0 && type < ITnum ? types[type] : "???";
+}
+
 static void
-tag(Tagctx *ctx, int t, const char *k, const char *v, int offset, int size, Tagread f)
+tag(Tagctx *ctx, int type, Tag *tag)
 {
-	USED(k); USED(f);
 	if(image){
-		if(t != Timage)
+		if(type != Timage)
 			return;
+		int size = tag->image.size;
 		char *raw = malloc(size);
 		Aux *aux = ctx->aux;
 		int prevoffset = lseek(aux->fd, 0, 1);
-		if(lseek(aux->fd, offset, 0) != offset ||
+		if(lseek(aux->fd, tag->image.offset, 0) != tag->image.offset ||
 		   read(aux->fd, raw, size) != size ||
-		   (f != NULL && f(raw, &size) != 0)){
+		   (tag->image.decode != NULL && tag->image.decode(raw, &size) != 0)){
 			fprintf(stderr, "failed to read the image\n");
 			exit(1);
 		}
@@ -54,12 +83,10 @@
 		exit(0);
 		return;
 	}
-	if(t == Timage)
-		printf("%-12s %s %d %d\n", t2s[t], v, offset, size);
-	else if(t == Tunknown)
-		printf("%-12s %s\n", k, v);
+	if(type == Timage)
+		printf("%-12s %s %d %d (%s)\n", t2s[type], tag->image.mime, tag->image.offset, tag->image.size, imagetype(tag->image.type));
 	else
-		printf("%-12s %s\n", t2s[t], v);
+		printf("%-12s %s\n", type == Tunknown ? tag->text.k : t2s[type], tag->text.v);
 }
 
 static void
@@ -118,7 +145,6 @@
 		else{
 			if(tagsget(&ctx) != 0){
 				fprintf(stderr, "no tags or failed to read tags\n");
-				return 1;
 			}else if(image){
 				fprintf(stderr, "no images found\n");
 				return 1;
--- a/flac.c
+++ b/flac.c
@@ -7,7 +7,7 @@
 tagflac(Tagctx *ctx)
 {
 	uint8_t *d;
-	int sz, last;
+	int sz, last, type;
 	uint64_t g;
 
 	d = (uint8_t*)ctx->buf;
@@ -44,7 +44,8 @@
 			if(sz < 8+4+20 || ctx->read(ctx, d, 8) != 8) /* type, mime length */
 				return -1;
 			sz -= 8;
-			n = beuint(&d[4]);
+			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;
@@ -57,8 +58,14 @@
 			sz -= 20;
 			if((n = beuint(&d[16])) < 0)
 				return -1;
-			if(n > 0)
-				tagscallcb(ctx, Timage, "", mime, offset, n, nil);
+			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 */
--- a/harness.c
+++ b/harness.c
@@ -1,6 +1,7 @@
 #include <assert.h>
 #include <stdint.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include "tags.h"
@@ -28,9 +29,22 @@
 __AFL_FUZZ_INIT()
 
 static void
-tag(Tagctx *ctx, int t, const char *k, const char *v, int offset, int size, Tagread f)
+tag(Tagctx *ctx, int t, Tag *tag)
 {
-	USED(ctx); USED(t);  USED(k); USED(v); USED(offset); USED(size); USED(f);
+	if(t == Timage){
+		static uint8_t *buf;
+		static int bufsz;
+		int size = tag->image.size;
+		if(bufsz < size){
+			buf = realloc(buf, size);
+			bufsz = size;
+		}
+		Aux *aux = ctx->aux;
+		memcpy(buf, aux->in+tag->image.offset, size);
+		if(tag->image.decode != NULL)
+			tag->image.decode(buf, &size);
+		assert(size >= 0 && size <= bufsz);
+	}
 }
 
 static void
--- a/id3v2.c
+++ b/id3v2.c
@@ -159,11 +159,11 @@
 {
 	int n, offset;
 	char *b, *tag;
-	Tagread f;
+	Tagdecode decf;
 
 	tag = ctx->buf;
 	n = 0;
-	f = unsync ? unsyncread : nil;
+	decf = unsync ? unsyncread : nil;
 	if(strcmp((char*)d, "APIC") == 0){
 		offset = ctx->seek(ctx, 0, 1);
 		if((n = ctx->read(ctx, tag, 255)) == 255){ /* APIC mime and description should fit */
@@ -180,12 +180,20 @@
 					break;
 				}
 			}
-			tagscallcb(ctx, Timage, "APIC", b, offset+n, tsz-n, f);
-			n = 256;
+			if(tsz > n){
+				tagscallcb(ctx, Timage, &(Tag){.image = {
+					.type = b[strlen(b)+1],
+					.mime = b,
+					.offset = offset+n,
+					.size = tsz-n,
+					.decode = decf,
+				}});
+			}
+			n = 255;
 		}
 	}else if(strcmp((char*)d, "PIC") == 0){
 		offset = ctx->seek(ctx, 0, 1);
-		if((n = ctx->read(ctx, tag, 256)) == 256){ /* PIC description should fit */
+		if((n = ctx->read(ctx, tag, 255)) == 255){ /* PIC description should fit */
 			b = tag + 1; /* mime type */
 			for(n = 5; n < 253; n++){
 				if(tag[0] == 0 || tag[0] == 3){ /* one zero byte */
@@ -198,9 +206,16 @@
 					break;
 				}
 			}
-			if(tsz > n)
-				tagscallcb(ctx, Timage, "PIC", strcmp(b, "JPG") == 0 ? "image/jpeg" : "image/png", offset+n, tsz-n, f);
-			n = 256;
+			if(tsz > n){
+				tagscallcb(ctx, Timage, &(Tag){.image = {
+					.type = b[strlen(b)+1],
+					.mime = strcmp(b, "JPG") == 0 ? "image/jpeg" : "image/png",
+					.offset = offset+n,
+					.size = tsz-n,
+					.decode = decf,
+				}});
+			}
+			n = 255;
 		}
 	}else if(strcmp((char*)d, "RVA2") == 0 && tsz >= 6+5){
 		/* replay gain. 6 = "track\0", 5 = other */
--- a/m4a.c
+++ b/m4a.c
@@ -186,10 +186,16 @@
 			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);
+		}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;
--- a/tags.c
+++ b/tags.c
@@ -24,25 +24,24 @@
 };
 
 void
-tagscallcb(Tagctx *ctx, int type, const char *k, char *s, int offset, int size, Tagread f)
+tagscallcb(Tagctx *ctx, int type, Tag *tag)
 {
-	char *e;
+	char *s, *e;
 
-	if(f == nil && size == 0){
-		while((uint8_t)*s <= ' ' && *s)
-			s++;
+	if(type != Timage){
+		for(s = tag->text.v; (uint8_t)*s <= ' ' && *s; s++);
 		e = s + strlen(s);
 		while(e != s && (uint8_t)e[-1] <= ' ')
 			e--;
 		if(*e != 0)
-		  *e = 0;
+			*e = 0;
+		if(*s == 0)
+			return;
+		tag->text.v = s;
 	}
-	if(*s){
-		ctx->tag(ctx, type, k, s, offset, size, f);
-		if(type != Tunknown){
-			ctx->found |= 1<<type;
-		}
-	}
+	ctx->tag(ctx, type, tag);
+	if(type != Tunknown)
+		ctx->found |= 1<<type;
 }
 
 int
--- a/tags.h
+++ b/tags.h
@@ -4,8 +4,9 @@
 extern "C" {
 #endif
 
+typedef union Tag Tag;
 typedef struct Tagctx Tagctx;
-typedef int (*Tagread)(void *buf, int *cnt);
+typedef int (*Tagdecode)(void *buf, int *cnt);
 
 /* Tag type. */
 enum {
@@ -54,6 +55,57 @@
 	Fogg __attribute__((deprecated("use Fvorbis instead"))) = Fvorbis,
 };
 
+/* Image types, according to id3v2. */
+enum {
+	ITother,
+	IT32x32_file_icon,
+	ITother_file_icon,
+	ITcover_front,
+	ITcover_back,
+	ITleaflet,
+	ITmedia,
+	ITlead,
+	ITartist,
+	ITconductor,
+	ITband,
+	ITcomposer,
+	ITlyricist,
+	ITlocation,
+	ITrecording,
+	ITperformance,
+	ITmovie_capture,
+	ITfish,
+	ITillustration,
+	ITlogo_band,
+	ITlogo_publisher,
+
+	ITnum,
+};
+
+/* Tag itself, either text or image, depending on the "type" passed to the tag callback. */
+union Tag {
+	/* Any tag type except Timage */
+	struct {
+		char *k; /* "TPE1", "replaygain_album_peak" etc */
+		char *v; /* value */
+	}text;
+
+	/* Tag type Timage */
+	struct {
+		/* If not NULL, reading the image cover requires additional decoding of the data.
+		 * In that case you will need to read the raw data (specified by "offset" and "size")
+		 * and call this function on the buffer (tag->decode(offset, &size)).
+		 * "Size" will be updated with the actual image size after decoding if the function
+		 * returned 0, else there was an error.
+		 */
+		Tagdecode decode;
+		const char *mime; /* "image/png", "image/jpeg" etc */
+		int offset; /* offset from the beginning of the file */
+		int size; /* number of bytes occupied by the raw image data (not decoded) */
+		int type; /* type of the image (ITcover_front, ...) */
+	}image;
+};
+
 /* Tag parser context. You need to set it properly before parsing an audio file using libtags. */
 struct Tagctx {
 	/* Read function. This is what libtags uses to read the file. */
@@ -63,19 +115,16 @@
 	int (*seek)(Tagctx *ctx, int offset, int whence);
 
 	/* Callback that is used by libtags to inform about the tags of a file.
-	 * "type" is the tag's type (Tartist, ...) or Tunknown if libtags doesn't know how to map a tag kind to
-	 * any of these. "k" is the raw key like "TPE1", "TPE2", etc. "s" is the null-terminated string unless "type" is
-	 * Timage. "offset" and "size" define the placement and size of the image cover ("type" = Timage)
-	 * inside the file, and "f" is not NULL in case reading the image cover requires additional
-	 * operations on the data, in which case you need to read the image cover as a stream and call this
-	 * function to apply these operations on the contents read.
+	 * "type" is the tag's type (Tartist, ...) or Tunknown if libtags doesn't
+	 * know how to map a tag kind to any of these.
+	 * ANY tag type that isn't Timage is considered a text tag.
 	 */
-	void (*tag)(Tagctx *ctx, int type, const char *k, const char *s, int offset, int size, Tagread f);
+	void (*tag)(Tagctx *ctx, int type, Tag *tag);
 
 	/* Approximate millisecond-to-byte offsets within the file, if available. This callback is optional. */
 	void (*toc)(Tagctx *ctx, int ms, int offset);
 
-	/* Auxiliary data. Not used by libtags. */
+	/* Auxiliary data. Not used by libtags, left for the library user. */
 	void *aux;
 
 	/* Memory buffer to work in. */
--- a/tagspriv.h
+++ b/tagspriv.h
@@ -65,11 +65,12 @@
 /*
  * METADATA_BLOCK_PICTURE reader function.
  */
-int mbpdec(void *buf, int *cnt);
+int cbmbp(Tagctx *ctx, char *v, int ssz, int off, int picsz);
 
-void tagscallcb(Tagctx *ctx, int type, const char *k, char *s, int offset, int size, Tagread f);
+void tagscallcb(Tagctx *ctx, int type, Tag *tag);
 
-#define txtcb(ctx, type, k, s) tagscallcb(ctx, type, k, (char*)s, 0, 0, nil)
+#define txtcb(ctx, type, k_, v_) \
+	tagscallcb(ctx, type, &(Tag){.text = {.k = (k_), .v = (char*)(v_)}})
 
 int tagflac(Tagctx *ctx);
 int tagid3v1(Tagctx *ctx);
--- a/vorbis.c
+++ b/vorbis.c
@@ -43,7 +43,7 @@
 		txtcb(ctx, Tunknown, k, v);
 }
 
-int
+static int
 mbpdec(void *buf, int *cnt)
 {
 	int sz, n;
@@ -53,7 +53,7 @@
 	if((n = debase64(v, *cnt, v, *cnt)) <= 0)
 		return -1;
 
-	beuint(v); /* id3v2 APIC type */
+	/* skip id3v2 APIC type */
 	v += 4; n -= 4;
 	sz = beuint(v); /* mime size */
 	v += 4; n -= 4;
@@ -77,11 +77,48 @@
 }
 
 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, *mime;
+	char *v;
 	uint8_t *d, h[4];
-	int sz, picsz, numtags, i, npages, pgend, skip, off, n;
+	int sz, numtags, i, npages, pgend, skip;
 
 	d = (uint8_t*)ctx->buf;
 	/* need to find vorbis frame with type=3 */
@@ -148,32 +185,13 @@
 			*v++ = 0;
 			if(strcasecmp(ctx->buf, "metadata_block_picture") != 0)
 				cbvorbiscomment(ctx, ctx->buf, v);
-			else{
-				/* off and picsz will point at the base64-encoded picture block */
-				off = ctx->seek(ctx, 0, 1) - sz + (v - ctx->buf);
-				picsz = sz + skip - (v - ctx->buf);
-				n = sz - (v - ctx->buf); /* 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){
-					v = ctx->buf;
-					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, "", mime, off, picsz, mbpdec);
-				}
-			}
+			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;
 		}