Image Sequence metadata support (colr/EXIF/XMP/transformations)
* trak boxes now optionally have a meta box containing EXIF/XMP
* Sample tables stsd boxes now carry property boxes (colr, transformations)
* Added proper read support for the above features
diff --git a/src/write.c b/src/write.c
index c5a59bc..95f5381 100644
--- a/src/write.c
+++ b/src/write.c
@@ -20,6 +20,15 @@
++ipma->count;
}
+// Used to store offsets in meta boxes which need to point at mdat offsets that
+// aren't known yet. When an item's mdat payload is written, all registered fixups
+// will have this now-known offset "fixed up".
+typedef struct avifOffsetFixup
+{
+ size_t offset;
+} avifOffsetFixup;
+AVIF_ARRAY_DECLARE(avifOffsetFixupArray, avifOffsetFixup, fixup);
+
static const char alphaURN[] = URN_ALPHA0;
static const size_t alphaURNSize = sizeof(alphaURN);
@@ -74,8 +83,7 @@
size_t infeNameSize;
const char * infeContentType;
size_t infeContentTypeSize;
- size_t infeOffsetOffset; // Stream offset where infe offset was written, so it can be properly set after mdat is written
- size_t stcoOffsetOffset; // Stream offset where stream chunk offset was written, so it can be properly set after mdat is written
+ avifOffsetFixupArray mdatFixups;
uint16_t irefToID; // if non-zero, make an iref from this id -> irefToID
const char * irefType;
@@ -126,6 +134,7 @@
item->infeName = infeName;
item->infeNameSize = infeNameSize;
item->encodeOutput = avifCodecEncodeOutputCreate();
+ avifArrayCreate(&item->mdatFixups, sizeof(avifOffsetFixup), 4);
return item;
}
@@ -138,6 +147,7 @@
}
avifCodecEncodeOutputDestroy(item->encodeOutput);
avifRWDataFree(&item->content);
+ avifArrayDestroy(&item->mdatFixups);
}
avifImageDestroy(data->imageMetadata);
avifArrayDestroy(&data->items);
@@ -145,6 +155,12 @@
avifFree(data);
}
+static void avifEncoderItemAddMdatFixup(avifEncoderItem * item, avifRWStream * s)
+{
+ avifOffsetFixup * fixup = (avifOffsetFixup *)avifArrayPushPtr(&item->mdatFixups);
+ fixup->offset = avifRWStreamOffset(s);
+}
+
// ---------------------------------------------------------------------------
avifEncoder * avifEncoderCreate(void)
@@ -239,6 +255,82 @@
}
}
+// Write unassociated metadata items (EXIF, XMP) to a small meta box inside of a trak box.
+// These items are implicitly associated with the track they are contained within.
+static void avifEncoderWriteTrackMetaBox(avifEncoder * encoder, avifRWStream * s)
+{
+ // Count how many non-av01 items (such as EXIF/XMP) are being written
+ uint32_t metadataItemCount = 0;
+ for (uint32_t itemIndex = 0; itemIndex < encoder->data->items.count; ++itemIndex) {
+ avifEncoderItem * item = &encoder->data->items.item[itemIndex];
+ if (memcmp(item->type, "av01", 4) != 0) {
+ ++metadataItemCount;
+ }
+ }
+ if (metadataItemCount == 0) {
+ // Don't even bother writing the trak meta box
+ return;
+ }
+
+ avifBoxMarker meta = avifRWStreamWriteFullBox(s, "meta", AVIF_BOX_SIZE_TBD, 0, 0);
+
+ avifBoxMarker hdlr = avifRWStreamWriteFullBox(s, "hdlr", AVIF_BOX_SIZE_TBD, 0, 0);
+ avifRWStreamWriteU32(s, 0); // unsigned int(32) pre_defined = 0;
+ avifRWStreamWriteChars(s, "pict", 4); // unsigned int(32) handler_type;
+ avifRWStreamWriteZeros(s, 12); // const unsigned int(32)[3] reserved = 0;
+ avifRWStreamWriteChars(s, "libavif", 8); // string name; (writing null terminator)
+ avifRWStreamFinishBox(s, hdlr);
+
+ avifBoxMarker iloc = avifRWStreamWriteFullBox(s, "iloc", AVIF_BOX_SIZE_TBD, 0, 0);
+ uint8_t offsetSizeAndLengthSize = (4 << 4) + (4 << 0); // unsigned int(4) offset_size;
+ // unsigned int(4) length_size;
+ avifRWStreamWrite(s, &offsetSizeAndLengthSize, 1); //
+ avifRWStreamWriteZeros(s, 1); // unsigned int(4) base_offset_size;
+ // unsigned int(4) reserved;
+ avifRWStreamWriteU16(s, (uint16_t)metadataItemCount); // unsigned int(16) item_count;
+ for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
+ avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
+ if (memcmp(item->type, "av01", 4) == 0) {
+ continue;
+ }
+
+ uint32_t contentSize = (uint32_t)item->content.size;
+ if (item->encodeOutput->samples.count > 0) {
+ contentSize = (uint32_t)item->encodeOutput->samples.sample[0].data.size;
+ }
+
+ avifRWStreamWriteU16(s, item->id); // unsigned int(16) item_ID;
+ avifRWStreamWriteU16(s, 0); // unsigned int(16) data_reference_index;
+ avifRWStreamWriteU16(s, 1); // unsigned int(16) extent_count;
+ avifEncoderItemAddMdatFixup(item, s); //
+ avifRWStreamWriteU32(s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset;
+ avifRWStreamWriteU32(s, (uint32_t)contentSize); // unsigned int(length_size*8) extent_length;
+ }
+ avifRWStreamFinishBox(s, iloc);
+
+ avifBoxMarker iinf = avifRWStreamWriteFullBox(s, "iinf", AVIF_BOX_SIZE_TBD, 0, 0);
+ avifRWStreamWriteU16(s, (uint16_t)metadataItemCount); // unsigned int(16) entry_count;
+ for (uint32_t trakItemIndex = 0; trakItemIndex < encoder->data->items.count; ++trakItemIndex) {
+ avifEncoderItem * item = &encoder->data->items.item[trakItemIndex];
+ if (memcmp(item->type, "av01", 4) == 0) {
+ continue;
+ }
+
+ avifBoxMarker infe = avifRWStreamWriteFullBox(s, "infe", AVIF_BOX_SIZE_TBD, 2, 0);
+ avifRWStreamWriteU16(s, item->id); // unsigned int(16) item_ID;
+ avifRWStreamWriteU16(s, 0); // unsigned int(16) item_protection_index;
+ avifRWStreamWrite(s, item->type, 4); // unsigned int(32) item_type;
+ avifRWStreamWriteChars(s, item->infeName, item->infeNameSize); // string item_name; (writing null terminator)
+ if (item->infeContentType && item->infeContentTypeSize) { // string content_type; (writing null terminator)
+ avifRWStreamWriteChars(s, item->infeContentType, item->infeContentTypeSize);
+ }
+ avifRWStreamFinishBox(s, infe);
+ }
+ avifRWStreamFinishBox(s, iinf);
+
+ avifRWStreamFinishBox(s, meta);
+}
+
static avifResult avifEncoderAddImage(avifEncoder * encoder, const avifImage * image, uint64_t durationInTimescales)
{
// -----------------------------------------------------------------------
@@ -481,7 +573,7 @@
avifRWStreamWriteU16(&s, item->id); // unsigned int(16) item_ID;
avifRWStreamWriteU16(&s, 0); // unsigned int(16) data_reference_index;
avifRWStreamWriteU16(&s, 1); // unsigned int(16) extent_count;
- item->infeOffsetOffset = avifRWStreamOffset(&s); //
+ avifEncoderItemAddMdatFixup(item, &s); //
avifRWStreamWriteU32(&s, 0 /* set later */); // unsigned int(offset_size*8) extent_offset;
avifRWStreamWriteU32(&s, (uint32_t)contentSize); // unsigned int(length_size*8) extent_length;
}
@@ -687,6 +779,10 @@
avifRWStreamFinishBox(&s, tref);
}
+ if (!item->alpha) {
+ avifEncoderWriteTrackMetaBox(encoder, &s);
+ }
+
avifBoxMarker mdia = avifRWStreamWriteBox(&s, "mdia", AVIF_BOX_SIZE_TBD);
avifBoxMarker mdhd = avifRWStreamWriteFullBox(&s, "mdhd", AVIF_BOX_SIZE_TBD, 1, 0);
@@ -722,9 +818,9 @@
avifBoxMarker stbl = avifRWStreamWriteBox(&s, "stbl", AVIF_BOX_SIZE_TBD);
avifBoxMarker stco = avifRWStreamWriteFullBox(&s, "stco", AVIF_BOX_SIZE_TBD, 0, 0);
- avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
- item->stcoOffsetOffset = avifRWStreamOffset(&s); //
- avifRWStreamWriteU32(&s, 1); // unsigned int(32) chunk_offset; (set later)
+ avifRWStreamWriteU32(&s, 1); // unsigned int(32) entry_count;
+ avifEncoderItemAddMdatFixup(item, &s); //
+ avifRWStreamWriteU32(&s, 1); // unsigned int(32) chunk_offset; (set later)
avifRWStreamFinishBox(&s, stco);
avifBoxMarker stsc = avifRWStreamWriteFullBox(&s, "stsc", AVIF_BOX_SIZE_TBD, 0, 0);
@@ -834,16 +930,10 @@
avifRWStreamWrite(&s, item->content.data, item->content.size);
}
- if (item->infeOffsetOffset != 0) {
+ for (uint32_t fixupIndex = 0; fixupIndex < item->mdatFixups.count; ++fixupIndex) {
+ avifOffsetFixup * fixup = &item->mdatFixups.fixup[fixupIndex];
size_t prevOffset = avifRWStreamOffset(&s);
- avifRWStreamSetOffset(&s, item->infeOffsetOffset);
- avifRWStreamWriteU32(&s, chunkOffset);
- avifRWStreamSetOffset(&s, prevOffset);
- }
-
- if (item->stcoOffsetOffset != 0) {
- size_t prevOffset = avifRWStreamOffset(&s);
- avifRWStreamSetOffset(&s, item->stcoOffsetOffset);
+ avifRWStreamSetOffset(&s, fixup->offset);
avifRWStreamWriteU32(&s, chunkOffset);
avifRWStreamSetOffset(&s, prevOffset);
}